package jackbox

import (
	"fmt"
	"sync"
	"time"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/sirupsen/logrus"
)

// Manager handles the Jackbox integration lifecycle
type Manager struct {
	client          *Client
	webhookServer   *WebhookServer
	wsClient        *WebSocketClient
	config          config.Config
	log             *logrus.Entry
	enabled         bool
	useWebSocket    bool
	messageCallback func(string)
	muted           bool
	mu              sync.RWMutex
}

// NewManager creates a new Jackbox manager
func NewManager(cfg config.Config, log *logrus.Entry) *Manager {
	return &Manager{
		config: cfg,
		log:    log,
	}
}

// Initialize sets up the Jackbox client and webhook server or WebSocket client
func (m *Manager) Initialize() error {
	// Check if Jackbox integration is enabled
	m.enabled = m.config.Viper().GetBool("jackbox.Enabled")
	if !m.enabled {
		m.log.Info("Jackbox integration is disabled")
		return nil
	}

	m.log.Info("Initializing Jackbox integration...")

	// Get configuration values
	apiURL := m.config.Viper().GetString("jackbox.APIURL")
	adminPassword := m.config.Viper().GetString("jackbox.AdminPassword")
	m.useWebSocket = m.config.Viper().GetBool("jackbox.UseWebSocket")

	// Validate configuration
	if apiURL == "" {
		return fmt.Errorf("jackbox.APIURL is required when Jackbox integration is enabled")
	}
	if adminPassword == "" {
		return fmt.Errorf("jackbox.AdminPassword is required when Jackbox integration is enabled")
	}

	// Create Jackbox API client
	m.client = NewClient(apiURL, adminPassword, m.log)

	// Authenticate with the API
	if err := m.client.Authenticate(); err != nil {
		return fmt.Errorf("failed to authenticate with Jackbox API: %w", err)
	}

	m.log.Info("Jackbox integration initialized successfully")
	return nil
}

// StartWebhookServer starts the webhook server with the provided message callback
func (m *Manager) StartWebhookServer(messageCallback func(string)) error {
	if !m.enabled {
		return nil
	}

	// Use WebSocket if enabled, otherwise fall back to webhook
	if m.useWebSocket {
		return m.startWebSocketClient(messageCallback)
	}

	webhookPort := m.config.Viper().GetInt("jackbox.WebhookPort")
	webhookSecret := m.config.Viper().GetString("jackbox.WebhookSecret")

	if webhookSecret == "" {
		return fmt.Errorf("jackbox.WebhookSecret is required when using webhooks")
	}
	if webhookPort == 0 {
		webhookPort = 3001
	}

	// Wrap the callback to check mute status
	wrappedCallback := func(message string) {
		if m.IsMuted() {
			m.log.Debugf("Jackbox message suppressed (muted): %s", message)
			return
		}
		messageCallback(message)
	}

	m.webhookServer = NewWebhookServer(webhookPort, webhookSecret, wrappedCallback, m.log)
	return m.webhookServer.Start()
}

// startWebSocketClient starts the WebSocket client connection
func (m *Manager) startWebSocketClient(messageCallback func(string)) error {
	apiURL := m.config.Viper().GetString("jackbox.APIURL")
	
	// Store the callback for use in monitoring
	m.messageCallback = messageCallback
	
	// Wrap the callback to check mute status
	wrappedCallback := func(message string) {
		if m.IsMuted() {
			m.log.Debugf("Jackbox message suppressed (muted): %s", message)
			return
		}
		messageCallback(message)
	}
	
	// Set wrapped callback on client for vote broadcasts
	m.client.SetMessageCallback(wrappedCallback)
	
	// Get JWT token from client
	token := m.client.GetToken()
	if token == "" {
		return fmt.Errorf("no JWT token available, authentication may have failed")
	}

	// Get EnableRoomCodeImage setting from config (defaults to false)
	enableRoomCodeImage := m.config.Viper().GetBool("jackbox.EnableRoomCodeImage")

	// Get configurable delays for room code broadcast
	imageDelay := time.Duration(m.config.Viper().GetInt("jackbox.RoomCodeImageDelay")) * time.Second
	plaintextDelay := time.Duration(m.config.Viper().GetInt("jackbox.RoomCodePlaintextDelay")) * time.Second
	if plaintextDelay == 0 {
		plaintextDelay = 29 * time.Second // default
	}
	m.log.Infof("Room code delays: imageDelay=%v, plaintextDelay=%v (raw config: image=%d, plaintext=%d)",
		imageDelay, plaintextDelay,
		m.config.Viper().GetInt("jackbox.RoomCodeImageDelay"),
		m.config.Viper().GetInt("jackbox.RoomCodePlaintextDelay"))

	// Create WebSocket client (pass the API client for vote tracking)
	m.wsClient = NewWebSocketClient(apiURL, token, wrappedCallback, m.client, enableRoomCodeImage, imageDelay, plaintextDelay, m.log)

	// Connect to WebSocket
	if err := m.wsClient.Connect(); err != nil {
		return fmt.Errorf("failed to connect WebSocket: %w", err)
	}

	// Get active session and subscribe
	session, err := m.client.GetActiveSession()
	if err != nil {
		m.log.Warnf("Could not get active session: %v", err)
		m.log.Info("WebSocket connected but not subscribed to any session yet")
		return nil
	}

	if session != nil && session.ID > 0 {
		if err := m.wsClient.Subscribe(session.ID); err != nil {
			m.log.Warnf("Failed to subscribe to session %d: %v", session.ID, err)
		}
		
		// Set the active session on the client for vote tracking
		m.client.SetActiveSession(session.ID)
		
		// Only announce if this is a NEW session (no games played yet)
		// If games have been played, the bot is just reconnecting to an existing session
		if session.GamesPlayed == 0 {
			announcement := fmt.Sprintf("🎮 Game Night is starting! Session #%d", session.ID)
			if messageCallback != nil {
				messageCallback(announcement)
			}
		} else {
			m.log.Infof("Reconnected to existing session #%d (%d games already played)", session.ID, session.GamesPlayed)
		}
	} else {
		m.log.Info("No active session found, will subscribe when session becomes active")
		// Start a goroutine to periodically check for active sessions
		go m.monitorActiveSessions()
	}

	return nil
}

// monitorActiveSessions periodically checks for active sessions and subscribes
// This is a FALLBACK mechanism - only used when WebSocket events aren't working
// Normally, session.started and session.ended events should handle this
func (m *Manager) monitorActiveSessions() {
	ticker := time.NewTicker(30 * time.Second)
	defer ticker.Stop()
	
	wasSubscribed := false

	for {
		select {
		case <-ticker.C:
			// Skip polling if WebSocket is disconnected (no point in polling)
			if m.wsClient == nil || !m.wsClient.IsConnected() {
				m.log.Debug("WebSocket disconnected, skipping session poll")
				continue
			}

			session, err := m.client.GetActiveSession()
			if err != nil {
				m.log.Debugf("Error checking for active session: %v", err)
				continue
			}

			isSubscribed := m.wsClient.IsSubscribed()
			
			// Check if we need to subscribe to a new session (fallback if session.started wasn't received)
			if !isSubscribed && session != nil && session.ID > 0 {
				m.log.Warnf("Found active session %d via polling (session.started event may have been missed), subscribing...", session.ID)
				if err := m.wsClient.Subscribe(session.ID); err != nil {
					m.log.Warnf("Failed to subscribe to session %d: %v", session.ID, err)
				} else {
					m.client.SetActiveSession(session.ID)
					wasSubscribed = true
					
					// Only announce if this is a NEW session (no games played yet)
					if session.GamesPlayed == 0 && !m.IsMuted() {
						announcement := fmt.Sprintf("🎮 Game Night is starting! Session #%d", session.ID)
						if m.messageCallback != nil {
							m.messageCallback(announcement)
						}
					} else if session.GamesPlayed == 0 && m.IsMuted() {
						m.log.Debugf("Jackbox message suppressed (muted): 🎮 Game Night is starting! Session #%d", session.ID)
					}
				}
			}
			
			// Check if session ended (fallback if session.ended wasn't received)
			if wasSubscribed && (session == nil || session.ID == 0 || session.IsActive == 0) {
				m.log.Warn("Active session ended (detected via polling, session.ended event may have been missed)")
				if m.wsClient != nil {
					m.wsClient.AnnounceSessionEnd()
				}
				wasSubscribed = false
			}
		}
	}
}

// GetClient returns the Jackbox API client (may be nil if disabled)
func (m *Manager) GetClient() *Client {
	return m.client
}

// IsEnabled returns whether Jackbox integration is enabled
func (m *Manager) IsEnabled() bool {
	return m.enabled
}

// Shutdown stops the webhook server or WebSocket client
func (m *Manager) Shutdown() error {
	if m.wsClient != nil {
		if err := m.wsClient.Close(); err != nil {
			m.log.Errorf("Error closing WebSocket client: %v", err)
		}
	}
	if m.webhookServer != nil {
		return m.webhookServer.Stop()
	}
	return nil
}

// SetMuted sets the mute state for Jackbox announcements
func (m *Manager) SetMuted(muted bool) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.muted = muted
}

// ToggleMuted toggles the mute state and returns the new state
func (m *Manager) ToggleMuted() bool {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.muted = !m.muted
	return m.muted
}

// IsMuted returns the current mute state
func (m *Manager) IsMuted() bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.muted
}
