package nrc

import (
	"context"
	"encoding/json"
	"sync"
	"time"
)

const (
	// DefaultSessionTimeout is the default inactivity timeout for sessions.
	DefaultSessionTimeout = 30 * time.Minute
	// DefaultMaxSubscriptions is the default maximum subscriptions per session.
	DefaultMaxSubscriptions = 100
)

// Session represents an NRC client session through the tunnel.
type Session struct {
	// ID is the unique session identifier.
	ID string
	// ClientPubkey is the public key of the connected client.
	ClientPubkey []byte
	// ConversationKey is the NIP-44 conversation key for this session.
	ConversationKey []byte
	// DeviceName is the optional device identifier.
	DeviceName string
	// AuthMode is the authentication mode used.
	AuthMode AuthMode

	// CreatedAt is when the session was created.
	CreatedAt time.Time
	// LastActivity is the timestamp of the last activity.
	LastActivity time.Time

	// subscriptions maps client subscription IDs to internal subscription state.
	subscriptions map[string]*Subscription
	// subMu protects the subscriptions map.
	subMu sync.RWMutex

	// ctx is the session context.
	ctx context.Context
	// cancel cancels the session context.
	cancel context.CancelFunc

	// eventCh receives events from the local relay for this session.
	eventCh chan *SessionEvent
}

// Subscription represents a tunneled subscription.
type Subscription struct {
	// ID is the client's subscription ID.
	ID string
	// CreatedAt is when the subscription was created.
	CreatedAt time.Time
	// EventCount tracks how many events have been sent.
	EventCount int64
	// EOSESent indicates whether EOSE has been sent.
	EOSESent bool
}

// SessionEvent wraps a relay response for delivery to the client.
type SessionEvent struct {
	// Type is the response type (EVENT, OK, EOSE, NOTICE, CLOSED, COUNT, AUTH).
	Type string
	// Payload is the response payload array.
	Payload []any
	// RequestEventID is the ID of the request event this responds to (if applicable).
	RequestEventID string
}

// NewSession creates a new session.
func NewSession(id string, clientPubkey, conversationKey []byte, authMode AuthMode, deviceName string) *Session {
	ctx, cancel := context.WithCancel(context.Background())
	now := time.Now()
	return &Session{
		ID:              id,
		ClientPubkey:    clientPubkey,
		ConversationKey: conversationKey,
		DeviceName:      deviceName,
		AuthMode:        authMode,
		CreatedAt:       now,
		LastActivity:    now,
		subscriptions:   make(map[string]*Subscription),
		ctx:             ctx,
		cancel:          cancel,
		eventCh:         make(chan *SessionEvent, 100),
	}
}

// Context returns the session's context.
func (s *Session) Context() context.Context {
	return s.ctx
}

// Close closes the session and cleans up resources.
func (s *Session) Close() {
	s.cancel()
	close(s.eventCh)
}

// Events returns the channel for receiving events destined for this session.
func (s *Session) Events() <-chan *SessionEvent {
	return s.eventCh
}

// SendEvent sends an event to the session's event channel.
func (s *Session) SendEvent(ev *SessionEvent) bool {
	select {
	case s.eventCh <- ev:
		return true
	case <-s.ctx.Done():
		return false
	default:
		// Channel full, drop event
		return false
	}
}

// Touch updates the last activity timestamp.
func (s *Session) Touch() {
	s.LastActivity = time.Now()
}

// IsExpired checks if the session has been inactive too long.
func (s *Session) IsExpired(timeout time.Duration) bool {
	return time.Since(s.LastActivity) > timeout
}

// AddSubscription adds a new subscription to the session.
func (s *Session) AddSubscription(subID string) error {
	s.subMu.Lock()
	defer s.subMu.Unlock()

	if len(s.subscriptions) >= DefaultMaxSubscriptions {
		return ErrTooManySubscriptions
	}

	s.subscriptions[subID] = &Subscription{
		ID:        subID,
		CreatedAt: time.Now(),
	}
	return nil
}

// RemoveSubscription removes a subscription from the session.
func (s *Session) RemoveSubscription(subID string) {
	s.subMu.Lock()
	defer s.subMu.Unlock()
	delete(s.subscriptions, subID)
}

// GetSubscription returns a subscription by ID.
func (s *Session) GetSubscription(subID string) *Subscription {
	s.subMu.RLock()
	defer s.subMu.RUnlock()
	return s.subscriptions[subID]
}

// HasSubscription checks if a subscription exists.
func (s *Session) HasSubscription(subID string) bool {
	s.subMu.RLock()
	defer s.subMu.RUnlock()
	_, ok := s.subscriptions[subID]
	return ok
}

// SubscriptionCount returns the number of active subscriptions.
func (s *Session) SubscriptionCount() int {
	s.subMu.RLock()
	defer s.subMu.RUnlock()
	return len(s.subscriptions)
}

// MarkEOSE marks a subscription as having sent EOSE.
func (s *Session) MarkEOSE(subID string) {
	s.subMu.Lock()
	defer s.subMu.Unlock()
	if sub, ok := s.subscriptions[subID]; ok {
		sub.EOSESent = true
	}
}

// IncrementEventCount increments the event count for a subscription.
func (s *Session) IncrementEventCount(subID string) {
	s.subMu.Lock()
	defer s.subMu.Unlock()
	if sub, ok := s.subscriptions[subID]; ok {
		sub.EventCount++
	}
}

// SessionManager manages multiple NRC sessions.
type SessionManager struct {
	sessions map[string]*Session
	mu       sync.RWMutex
	timeout  time.Duration
}

// NewSessionManager creates a new session manager.
func NewSessionManager(timeout time.Duration) *SessionManager {
	if timeout == 0 {
		timeout = DefaultSessionTimeout
	}
	return &SessionManager{
		sessions: make(map[string]*Session),
		timeout:  timeout,
	}
}

// Get returns a session by ID.
func (m *SessionManager) Get(sessionID string) *Session {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.sessions[sessionID]
}

// GetOrCreate gets an existing session or creates a new one.
func (m *SessionManager) GetOrCreate(sessionID string, clientPubkey, conversationKey []byte, authMode AuthMode, deviceName string) *Session {
	m.mu.Lock()
	defer m.mu.Unlock()

	if session, ok := m.sessions[sessionID]; ok {
		session.Touch()
		return session
	}

	session := NewSession(sessionID, clientPubkey, conversationKey, authMode, deviceName)
	m.sessions[sessionID] = session
	return session
}

// Remove removes a session.
func (m *SessionManager) Remove(sessionID string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	if session, ok := m.sessions[sessionID]; ok {
		session.Close()
		delete(m.sessions, sessionID)
	}
}

// CleanupExpired removes expired sessions.
func (m *SessionManager) CleanupExpired() int {
	m.mu.Lock()
	defer m.mu.Unlock()

	var removed int
	for id, session := range m.sessions {
		if session.IsExpired(m.timeout) {
			session.Close()
			delete(m.sessions, id)
			removed++
		}
	}
	return removed
}

// Count returns the number of active sessions.
func (m *SessionManager) Count() int {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return len(m.sessions)
}

// Close closes all sessions.
func (m *SessionManager) Close() {
	m.mu.Lock()
	defer m.mu.Unlock()

	for _, session := range m.sessions {
		session.Close()
	}
	m.sessions = make(map[string]*Session)
}

// RequestMessage represents a parsed NRC request message.
type RequestMessage struct {
	Type    string // EVENT, REQ, CLOSE, AUTH, COUNT
	Payload []any
}

// ResponseMessage represents an NRC response message to be sent.
type ResponseMessage struct {
	Type    string // EVENT, OK, EOSE, NOTICE, CLOSED, COUNT, AUTH
	Payload []any
}

// ParseRequestContent parses the decrypted content of an NRC request.
func ParseRequestContent(content []byte) (*RequestMessage, error) {
	// Content format: {"type": "EVENT|REQ|...", "payload": [...]}
	// Parse as generic JSON
	var msg struct {
		Type    string `json:"type"`
		Payload []any  `json:"payload"`
	}

	if err := json.Unmarshal(content, &msg); err != nil {
		return nil, err
	}

	if msg.Type == "" {
		return nil, ErrInvalidMessageType
	}

	return &RequestMessage{
		Type:    msg.Type,
		Payload: msg.Payload,
	}, nil
}

// MarshalResponseContent marshals an NRC response for encryption.
func MarshalResponseContent(resp *ResponseMessage) ([]byte, error) {
	msg := struct {
		Type    string `json:"type"`
		Payload []any  `json:"payload"`
	}{
		Type:    resp.Type,
		Payload: resp.Payload,
	}
	return json.Marshal(msg)
}
