package aggregator

import (
	"context"
	"sync"

	"github.com/giantswarm/muster/internal/api"
	"github.com/giantswarm/muster/internal/events"
	"github.com/giantswarm/muster/pkg/logging"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EventHandler manages automatic MCP server registration based on service lifecycle events.
//
// The event handler bridges the gap between the muster service orchestrator and the
// aggregator by listening for service state changes and automatically registering or
// deregistering MCP servers as they become healthy or unhealthy.
//
// Key responsibilities:
//   - Subscribe to orchestrator service state changes
//   - Filter events to only process MCP service changes
//   - Automatically register healthy running MCP servers
//   - Automatically deregister unhealthy or stopped MCP servers
//   - Skip global registration for SSO-based servers (handled at session level)
//   - Maintain separation of concerns through callback functions
//
// The handler operates asynchronously and is designed to be resilient to temporary
// failures in the registration process.
type EventHandler struct {
	orchestratorAPI      api.OrchestratorAPI                 // Interface for receiving service events
	registerFunc         func(context.Context, string) error // Callback for server registration
	deregisterFunc       func(string) error                  // Callback for server deregistration
	isServerAuthRequired func(string) bool                   // Callback to check if server is in auth_required state
	isServerSSOBased     func(string) bool                   // Callback to check if server uses SSO token forwarding/exchange

	// Lifecycle management
	ctx        context.Context    // Context for coordinating shutdown
	cancelFunc context.CancelFunc // Function to cancel the context
	wg         sync.WaitGroup     // WaitGroup for coordinating goroutine shutdown
	mu         sync.RWMutex       // Protects the running state
	running    bool               // Indicates whether the event handler is active
}

// NewEventHandler creates a new event handler with the specified dependencies and callbacks.
//
// The event handler uses callback functions to maintain loose coupling with the
// aggregator manager. This design allows the handler to focus solely on event
// processing while delegating the actual registration logic to the caller.
//
// Args:
//   - orchestratorAPI: Interface for subscribing to service state changes
//   - registerFunc: Callback function to register a server by name
//   - deregisterFunc: Callback function to deregister a server by name
//   - isServerAuthRequired: Optional callback to check if server is in auth_required state
//   - isServerSSOBased: Optional callback to check if server uses SSO token forwarding/exchange
//
// Returns a configured but not yet started event handler.
func NewEventHandler(
	orchestratorAPI api.OrchestratorAPI,
	registerFunc func(context.Context, string) error,
	deregisterFunc func(string) error,
	isServerAuthRequired func(string) bool,
	isServerSSOBased func(string) bool,
) *EventHandler {
	return &EventHandler{
		orchestratorAPI:      orchestratorAPI,
		registerFunc:         registerFunc,
		deregisterFunc:       deregisterFunc,
		isServerAuthRequired: isServerAuthRequired,
		isServerSSOBased:     isServerSSOBased,
	}
}

// Start begins listening for orchestrator events and processing them asynchronously.
//
// This method subscribes to service state change events from the orchestrator and
// starts a background goroutine to process them. The method is idempotent - calling
// it multiple times has no additional effect.
//
// The event processing continues until the provided context is cancelled or the
// Stop method is called.
//
// Args:
//   - ctx: Context for controlling the event handler lifecycle
//
// Returns nil on successful startup. The method does not wait for event processing
// to complete, as that happens asynchronously.
func (eh *EventHandler) Start(ctx context.Context) error {
	eh.mu.Lock()
	defer eh.mu.Unlock()

	if eh.running {
		return nil
	}

	eh.ctx, eh.cancelFunc = context.WithCancel(ctx)
	eh.running = true

	// Subscribe to state changes from the orchestrator
	eventChan := eh.orchestratorAPI.SubscribeToStateChanges()

	eh.wg.Add(1)
	go eh.handleEvents(eventChan)

	logging.Info("Aggregator-EventHandler", "Started event handler for MCP service state changes")
	return nil
}

// Stop gracefully shuts down the event handler and waits for cleanup to complete.
//
// This method cancels the event processing context and waits for the background
// goroutine to finish processing any in-flight events. The method is idempotent
// and can be called multiple times safely.
//
// Returns nil after successful shutdown.
func (eh *EventHandler) Stop() error {
	eh.mu.Lock()
	if !eh.running {
		eh.mu.Unlock()
		return nil
	}

	eh.running = false
	cancelFunc := eh.cancelFunc
	eh.mu.Unlock()

	if cancelFunc != nil {
		cancelFunc()
	}

	// Wait for the event handling goroutine to finish
	eh.wg.Wait()

	logging.Info("Aggregator-EventHandler", "Stopped event handler")
	return nil
}

// IsRunning returns whether the event handler is currently active.
//
// This method is thread-safe and can be used to check the handler's status
// for monitoring or debugging purposes.
//
// Returns true if the event handler is currently processing events.
func (eh *EventHandler) IsRunning() bool {
	eh.mu.RLock()
	defer eh.mu.RUnlock()
	return eh.running
}

// handleEvents processes orchestrator events in a background goroutine.
//
// This method runs continuously until the context is cancelled or the event
// channel is closed. It filters incoming events to only process MCP service
// changes and delegates the actual registration/deregistration logic to the
// configured callback functions.
//
// The method automatically updates the running state when it exits to ensure
// accurate status reporting.
//
// Args:
//   - eventChan: Read-only channel for receiving service state change events
func (eh *EventHandler) handleEvents(eventChan <-chan api.ServiceStateChangedEvent) {
	defer eh.wg.Done()
	defer func() {
		// Mark as not running when goroutine exits
		eh.mu.Lock()
		eh.running = false
		eh.mu.Unlock()
	}()

	for {
		select {
		case <-eh.ctx.Done():
			logging.Debug("Aggregator-EventHandler", "Event handler context cancelled, stopping")
			return

		case event, ok := <-eventChan:
			if !ok {
				logging.Warn("Aggregator-EventHandler", "Event channel closed, stopping event handler")
				return
			}

			eh.processEvent(event)
		}
	}
}

// processEvent handles a single orchestrator event and determines the appropriate action.
//
// This method implements the core business logic for automatic server registration:
//   - Filters out non-MCP service events
//   - Registers servers that become healthy and running
//   - Deregisters servers that become unhealthy, stopped, or failed
//   - Generates Kubernetes events for MCPServer lifecycle operations
//
// The registration/deregistration operations are performed through callback functions
// to maintain separation of concerns.
//
// Args:
//   - event: Service state change event from the orchestrator
func (eh *EventHandler) processEvent(event api.ServiceStateChangedEvent) {
	// Filter for MCP service events only
	if !eh.isMCPServiceEvent(event) {
		return
	}

	logging.Debug("Aggregator-EventHandler", "Processing MCP service event: %s (state=%s, health=%s)",
		event.Name, event.NewState, event.Health)

	// Generate events for service state transitions
	eh.generateServiceStateEvents(event)

	// Register only servers that are both active (Running for stdio servers or
	// Connected for remote servers) and healthy.
	isHealthyAndActive := api.IsActiveState(api.ServiceState(event.NewState)) && api.HealthStatus(event.Health) == api.HealthHealthy

	if isHealthyAndActive {
		// Skip global registration for SSO-based servers (token forwarding or token exchange).
		// These servers are handled at the session level - each user's session creates its own
		// connection using their SSO token. The MCPServerService never creates a global MCP client
		// for these servers because they return 401 during initial connection.
		// See Issue #318 for details on this design decision.
		if eh.isServerSSOBased != nil && eh.isServerSSOBased(event.Name) {
			logging.Info("Aggregator-EventHandler", "Skipping global registration of %s - server uses SSO token forwarding/exchange (handled at session level)", event.Name)
			return
		}

		// Register the healthy running/connected server
		logging.Info("Aggregator-EventHandler", "Registering healthy MCP server: %s", event.Name)

		if err := eh.registerFunc(context.Background(), event.Name); err != nil {
			logging.Error("Aggregator-EventHandler", err, "Failed to register MCP server %s", event.Name)
			// Generate event for failed tool registration
			eh.generateEvent(event.Name, events.ReasonMCPServerToolsUnavailable, events.EventData{
				Error: err.Error(),
			})
		} else {
			// Generate event for successful tool discovery and registration
			eh.generateEvent(event.Name, events.ReasonMCPServerToolsDiscovered, events.EventData{})
		}
	} else {
		// Skip deregistration for servers in "waiting" or "auth_required" state.
		// These servers require OAuth authentication before they can connect.
		// The orchestrator will register them as pending auth servers.
		// We MUST check the event state directly, not the registry, because there's a race
		// condition where the event is processed before the Orchestrator registers the server.
		if api.ServiceState(event.NewState) == api.StateWaiting || api.ServiceState(event.NewState) == api.StateAuthRequired {
			logging.Debug("Aggregator-EventHandler", "Skipping deregistration of %s - server is in %s state (requires authentication)", event.Name, event.NewState)
			return
		}

		// Also check the registry as a fallback for servers already in auth_required state
		// (handles cases where the event state might not match the current registry state)
		if eh.isServerAuthRequired != nil && eh.isServerAuthRequired(event.Name) {
			logging.Debug("Aggregator-EventHandler", "Skipping deregistration of %s - server is in auth_required state (from registry)", event.Name)
			return
		}

		// Deregister for any other state/health combination
		// This includes: Stopped, Failed, Starting, Stopping, or Running+Unhealthy
		logging.Info("Aggregator-EventHandler", "Deregistering MCP server %s (state=%s, health=%s)",
			event.Name, event.NewState, event.Health)

		if err := eh.deregisterFunc(event.Name); err != nil {
			// Log as debug since deregistering a non-existent server is not critical
			logging.Debug("Aggregator-EventHandler", "Failed to deregister MCP server %s: %v", event.Name, err)
		} else {
			// Generate event for tools becoming unavailable
			eh.generateEvent(event.Name, events.ReasonMCPServerToolsUnavailable, events.EventData{})
		}
	}
}

// generateServiceStateEvents generates appropriate Kubernetes events based on service state transitions
func (eh *EventHandler) generateServiceStateEvents(event api.ServiceStateChangedEvent) {
	switch api.ServiceState(event.NewState) {
	case api.StateStarting:
		eh.generateEvent(event.Name, events.ReasonMCPServerStarting, events.EventData{})
	case api.StateRunning:
		if api.HealthStatus(event.Health) == api.HealthHealthy {
			eh.generateEvent(event.Name, events.ReasonMCPServerStarted, events.EventData{})
		}
	case api.StateStopped:
		eh.generateEvent(event.Name, events.ReasonMCPServerStopped, events.EventData{})
	case api.StateFailed:
		eventData := events.EventData{}
		if event.Error != nil {
			eventData.Error = event.Error.Error()
		}
		eh.generateEvent(event.Name, events.ReasonMCPServerFailed, eventData)
	}

	if api.ServiceState(event.NewState) == api.StateRunning && api.HealthStatus(event.Health) == api.HealthUnhealthy {
		eventData := events.EventData{}
		if event.Error != nil {
			eventData.Error = event.Error.Error()
		}
		eh.generateEvent(event.Name, events.ReasonMCPServerHealthCheckFailed, eventData)
	}

	if api.ServiceState(event.NewState) == api.StateStarting && (api.ServiceState(event.OldState) == api.StateStopped || api.ServiceState(event.OldState) == api.StateFailed) {
		eh.generateEvent(event.Name, events.ReasonMCPServerRestarting, events.EventData{})
	}
}

// generateEvent creates a Kubernetes event for an MCPServer service instance
func (eh *EventHandler) generateEvent(serviceName string, reason events.EventReason, data events.EventData) {
	eventManager := api.GetEventManager()
	if eventManager == nil {
		logging.Debug("Aggregator-EventHandler", "Event manager not available, skipping event generation for %s", serviceName)
		return
	}

	// Create an object reference for the MCPServer CRD
	// MCPServer lifecycle events should be associated with the MCPServer CRD resource
	objectRef := api.ObjectReference{
		Kind:      "MCPServer",
		Name:      serviceName,
		Namespace: metav1.NamespaceDefault, // TODO: Make configurable or derive from service
	}

	// Populate service-specific data
	data.Name = serviceName
	if data.Namespace == "" {
		data.Namespace = metav1.NamespaceDefault
	}

	err := eventManager.CreateEvent(context.Background(), objectRef, string(reason), "", string(events.EventTypeNormal))
	if err != nil {
		logging.Debug("Aggregator-EventHandler", "Failed to generate event for %s: %v", serviceName, err)
	} else {
		logging.Debug("Aggregator-EventHandler", "Generated event %s for MCPServer %s", string(reason), serviceName)
	}
}

// isMCPServiceEvent checks if the given event is related to an MCP service.
//
// This method filters events to ensure that only MCP server state changes are
// processed by the event handler. Other service types are ignored to avoid
// unnecessary processing overhead.
//
// Args:
//   - event: Service state change event to examine
//
// Returns true if the event is related to an MCP service, false otherwise.
func (eh *EventHandler) isMCPServiceEvent(event api.ServiceStateChangedEvent) bool {
	return event.ServiceType == "MCPServer"
}
