package streaming

import (
	"context"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
)

// Session represents a CometD client session
type Session struct {
	ID         string
	CreatedAt  time.Time
	LastAccess time.Time
	Channels   map[string]bool     // subscribed channels
	Messages   chan map[string]any // message buffer for long-polling
}

// SessionManager handles CometD client sessions with UUID-based IDs
type SessionManager struct {
	sessions map[string]*Session
	mu       sync.RWMutex
	timeout  time.Duration
	ctx      context.Context
	cancel   context.CancelFunc
}

// NewSessionManager creates a new session manager with cleanup
func NewSessionManager(sessionTimeout time.Duration) *SessionManager {
	ctx, cancel := context.WithCancel(context.Background())
	sm := &SessionManager{
		sessions: make(map[string]*Session),
		timeout:  sessionTimeout,
		ctx:      ctx,
		cancel:   cancel,
	}

	// Start background cleanup
	go sm.cleanupLoop()
	return sm
}

// DefaultSessionManager creates a manager with 30-minute timeout
func DefaultSessionManager() *SessionManager {
	return NewSessionManager(30 * time.Minute)
}

// Stop gracefully stops the session manager and cleanup loop
func (sm *SessionManager) Stop() {
	sm.cancel()
	log.Info().Msg("SessionManager stopped")
}

// CreateSession creates a new session and returns the client ID
func (sm *SessionManager) CreateSession() string {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	clientID := uuid.New().String()
	sm.sessions[clientID] = &Session{
		ID:         clientID,
		CreatedAt:  time.Now(),
		LastAccess: time.Now(),
		Channels:   make(map[string]bool),
		Messages:   make(chan map[string]any, 100), // Buffer size 100
	}

	return clientID
}

// GetSession retrieves a session by ID, updating last access time
func (sm *SessionManager) GetSession(clientID string) (*Session, bool) {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	session, exists := sm.sessions[clientID]
	if exists {
		session.LastAccess = time.Now()
	}
	return session, exists
}

// ValidateSession checks if a session exists and is valid
func (sm *SessionManager) ValidateSession(clientID string) bool {
	_, exists := sm.GetSession(clientID)
	return exists
}

// Subscribe adds a channel subscription to a session
func (sm *SessionManager) Subscribe(clientID, channel string) bool {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	session, exists := sm.sessions[clientID]
	if !exists {
		return false
	}

	session.Channels[channel] = true
	session.LastAccess = time.Now()
	return true
}

// Unsubscribe removes a channel subscription from a session
func (sm *SessionManager) Unsubscribe(clientID, channel string) bool {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	session, exists := sm.sessions[clientID]
	if !exists {
		return false
	}

	delete(session.Channels, channel)
	session.LastAccess = time.Now()
	return true
}

// DestroySession removes a session
func (sm *SessionManager) DestroySession(clientID string) {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	delete(sm.sessions, clientID)
}

// GetSubscribedChannels returns channels for a session
func (sm *SessionManager) GetSubscribedChannels(clientID string) []string {
	sm.mu.RLock()
	defer sm.mu.RUnlock()

	session, exists := sm.sessions[clientID]
	if !exists {
		return nil
	}

	channels := make([]string, 0, len(session.Channels))
	for ch := range session.Channels {
		channels = append(channels, ch)
	}
	return channels
}

// Broadcast sends a message to all sessions subscribed to the channel
func (sm *SessionManager) Broadcast(channel string, data interface{}) {
	sm.mu.RLock()
	defer sm.mu.RUnlock()

	for _, session := range sm.sessions {
		// Check subscription logic (exact match for now, could be glob)
		// For simplicity, we assume exact match or simple hierarchy if needed
		// But in CometD convention, exact match is typical for this implementation scope
		if subscribed := session.Channels[channel]; subscribed {
			msg := map[string]any{
				"channel": channel,
				"data":    data,
			}

			select {
			case session.Messages <- msg:
				// sent
			default:
				// channel full, drop message or log warning
				log.Warn().Str("client_id", session.ID).Str("channel", channel).Msg("Client message buffer full, dropping message")
			}
		}
	}
}

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

// cleanupLoop removes expired sessions periodically
func (sm *SessionManager) cleanupLoop() {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	for {
		select {
		case <-sm.ctx.Done():
			log.Debug().Msg("SessionManager cleanup loop stopped")
			return
		case <-ticker.C:
			sm.mu.Lock()
			now := time.Now()
			expired := 0
			for id, session := range sm.sessions {
				if now.Sub(session.LastAccess) > sm.timeout {
					delete(sm.sessions, id)
					expired++
				}
			}
			if expired > 0 {
				log.Debug().Int("expired", expired).Int("remaining", len(sm.sessions)).Msg("Cleaned up expired sessions")
			}
			sm.mu.Unlock()
		}
	}
}
