/**
 * @fileoverview SSE Authentication Handler - Secure Server-Sent Events with JWT
 * @version 1.0.0
 * @since 2025-08-04
 * @lastUpdated 2025-08-04
 * @module SSEAuthHandler
 * @description Utility for handling authenticated Server-Sent Events connections with automatic token refresh
 * @contributors Claude Code Agent
 * @dependencies AuthService, EventSource API
 * @requirements REQ-AUTH-015 (Authenticated SSE connections)
 * @testCoverage SSE connection management, token refresh, reconnection logic
 */

import { AuthService } from '../services/AuthService';

export interface SSEAuthOptions {
  url: string;
  withCredentials?: boolean;
  reconnectInterval?: number;
  maxReconnectAttempts?: number;
  onMessage?: (event: MessageEvent) => void;
  onError?: (event: Event) => void;
  onOpen?: (event: Event) => void;
  onClose?: (event: Event) => void;
  onAuthRequired?: () => void;
}

export interface SSEConnection {
  eventSource: EventSource | null;
  isConnected: boolean;
  reconnectAttempts: number;
  url: string;
}

/**
 * SSE Authentication Handler
 *
 * @description Manages authenticated Server-Sent Events connections with automatic
 *              token refresh, reconnection logic, and proper cleanup. Ensures
 *              SSE connections include valid JWT tokens and handles token expiration.
 *
 * @example
 * ```typescript
 * const sseHandler = new SSEAuthHandler({
 *   url: '/api/events',
 *   onMessage: (event) => console.log('Received:', event.data),
 *   onAuthRequired: () => authManager.showLoginModal()
 * });
 *
 * await sseHandler.connect();
 * ```
 *
 * @since 2025-08-04
 * @author Claude Code Agent
 * @requirements REQ-AUTH-015 (Authenticated SSE handler)
 * @accessibility Provides connection status announcements for screen readers
 */
export class SSEAuthHandler {
  private authService: AuthService;
  private options: SSEAuthOptions;
  private connection: SSEConnection;
  private reconnectTimer: number | null = null;
  private unsubscribeAuth: (() => void) | null = null;

  constructor(options: SSEAuthOptions) {
    this.authService = AuthService.getInstance();
    this.options = {
      withCredentials: true,
      reconnectInterval: 5000,
      maxReconnectAttempts: 5,
      ...options,
    };

    this.connection = {
      eventSource: null,
      isConnected: false,
      reconnectAttempts: 0,
      url: options.url,
    };

    this.setupAuthSubscription();
  }

  /**
   * Connect to SSE endpoint with authentication
   *
   * @description Establishes authenticated SSE connection with proper headers
   *
   * @returns {Promise<boolean>} Connection success status
   *
   * @throws {Error} When authentication is required but not available
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   * @requirements REQ-AUTH-016 (Authenticated SSE connection establishment)
   */
  async connect(): Promise<boolean> {
    // Check if authentication is required
    const authState = this.authService.getAuthState();
    if (!authState.isAuthenticated || !authState.token) {
      console.warn('SSE connection requires authentication');
      this.options.onAuthRequired?.();
      return false;
    }

    try {
      await this.establishConnection();
      return true;
    } catch (error) {
      console.error('Failed to establish SSE connection:', error);
      this.options.onError?.(error as Event);
      return false;
    }
  }

  /**
   * Disconnect from SSE endpoint
   *
   * @description Cleanly closes SSE connection and cleans up resources
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  disconnect(): void {
    this.clearReconnectTimer();

    if (this.connection.eventSource) {
      this.connection.eventSource.close();
      this.connection.eventSource = null;
    }

    this.connection.isConnected = false;
    this.connection.reconnectAttempts = 0;

    console.log('SSE connection disconnected');
  }

  /**
   * Check if currently connected
   *
   * @description Returns current connection status
   *
   * @returns {boolean} Connection status
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  isConnected(): boolean {
    return (
      this.connection.isConnected && this.connection.eventSource?.readyState === EventSource.OPEN
    );
  }

  /**
   * Get connection status
   *
   * @description Returns detailed connection information
   *
   * @returns {SSEConnection} Connection details
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  getConnectionStatus(): SSEConnection {
    return { ...this.connection };
  }

  /**
   * Establish SSE connection with authentication
   *
   * @description Creates EventSource with authenticated URL
   *
   * @throws {Error} When token is invalid or connection fails
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private async establishConnection(): Promise<void> {
    const authHeader = this.authService.getAuthHeader();
    if (!authHeader) {
      throw new Error('No authentication token available');
    }

    // Extract token from Bearer header
    const token = authHeader.replace('Bearer ', '');

    // Create authenticated URL with token as query parameter
    // Note: EventSource doesn't support custom headers, so we use query param
    const separator = this.options.url.includes('?') ? '&' : '?';
    const authenticatedUrl = `${this.options.url}${separator}token=${encodeURIComponent(token)}`;

    // Create EventSource connection
    this.connection.eventSource = new EventSource(authenticatedUrl, {
      withCredentials: this.options.withCredentials,
    });

    this.setupEventListeners();
  }

  /**
   * Setup EventSource event listeners
   *
   * @description Configures event handlers for SSE connection
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private setupEventListeners(): void {
    if (!this.connection.eventSource) return;

    this.connection.eventSource.onopen = event => {
      console.log('SSE connection established');
      this.connection.isConnected = true;
      this.connection.reconnectAttempts = 0;
      this.clearReconnectTimer();
      this.options.onOpen?.(event);
    };

    this.connection.eventSource.onmessage = event => {
      this.options.onMessage?.(event);
    };

    this.connection.eventSource.onerror = event => {
      console.error('SSE connection error:', event);
      this.connection.isConnected = false;

      // Check if it's an authentication error
      if (this.connection.eventSource?.readyState === EventSource.CLOSED) {
        this.handleConnectionError();
      }

      this.options.onError?.(event);
    };

    // Custom event handlers
    this.setupCustomEventHandlers();
  }

  /**
   * Setup custom event type handlers
   *
   * @description Configures handlers for specific SSE event types
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private setupCustomEventHandlers(): void {
    if (!this.connection.eventSource) return;

    // Handle authentication errors
    this.connection.eventSource.addEventListener('auth-error', event => {
      console.warn('SSE authentication error:', event);
      this.handleAuthError();
    });

    // Handle token refresh notifications
    this.connection.eventSource.addEventListener('token-refresh', event => {
      console.log('Token refresh requested by server');
      this.handleTokenRefresh();
    });

    // Handle connection close notifications
    this.connection.eventSource.addEventListener('close', event => {
      console.log('Server requested connection close');
      this.disconnect();
      this.options.onClose?.(event);
    });
  }

  /**
   * Handle connection errors with reconnection logic
   *
   * @description Manages reconnection attempts with exponential backoff
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private handleConnectionError(): void {
    if (this.connection.reconnectAttempts >= (this.options.maxReconnectAttempts || 5)) {
      console.error('Max reconnection attempts reached');
      this.options.onAuthRequired?.();
      return;
    }

    this.connection.reconnectAttempts++;
    const delay =
      this.options.reconnectInterval! * Math.pow(2, this.connection.reconnectAttempts - 1);

    console.log(
      `Attempting to reconnect in ${delay}ms (attempt ${this.connection.reconnectAttempts})`
    );

    this.reconnectTimer = window.setTimeout(() => {
      this.reconnect();
    }, delay);
  }

  /**
   * Handle authentication errors
   *
   * @description Responds to SSE authentication failures
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private handleAuthError(): void {
    console.warn('SSE authentication failed, requesting login');
    this.disconnect();
    this.options.onAuthRequired?.();
  }

  /**
   * Handle token refresh requests
   *
   * @description Refreshes token and reconnects SSE
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private async handleTokenRefresh(): Promise<void> {
    try {
      const refreshed = await this.authService.refreshToken();
      if (refreshed) {
        console.log('Token refreshed, reconnecting SSE');
        await this.reconnect();
      } else {
        console.error('Token refresh failed');
        this.handleAuthError();
      }
    } catch (error) {
      console.error('Token refresh error:', error);
      this.handleAuthError();
    }
  }

  /**
   * Reconnect to SSE endpoint
   *
   * @description Attempts to reestablish SSE connection
   *
   * @returns {Promise<boolean>} Reconnection success status
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private async reconnect(): Promise<boolean> {
    console.log('Attempting SSE reconnection');

    // Close existing connection
    if (this.connection.eventSource) {
      this.connection.eventSource.close();
    }

    try {
      await this.establishConnection();
      return true;
    } catch (error) {
      console.error('SSE reconnection failed:', error);
      this.handleConnectionError();
      return false;
    }
  }

  /**
   * Setup authentication service subscription
   *
   * @description Monitors auth state changes and manages connection accordingly
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private setupAuthSubscription(): void {
    this.unsubscribeAuth = this.authService.subscribe(authState => {
      if (!authState.isAuthenticated && this.connection.isConnected) {
        console.log('User logged out, disconnecting SSE');
        this.disconnect();
      }
    });
  }

  /**
   * Clear reconnection timer
   *
   * @description Cancels pending reconnection attempts
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private clearReconnectTimer(): void {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }

  /**
   * Add custom event listener
   *
   * @description Adds listener for specific SSE event types
   *
   * @param {string} type - Event type name
   * @param {Function} listener - Event handler function
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  addEventListener(type: string, listener: (event: MessageEvent) => void): void {
    this.connection.eventSource?.addEventListener(type, listener);
  }

  /**
   * Remove custom event listener
   *
   * @description Removes listener for specific SSE event types
   *
   * @param {string} type - Event type name
   * @param {Function} listener - Event handler function
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  removeEventListener(type: string, listener: (event: MessageEvent) => void): void {
    this.connection.eventSource?.removeEventListener(type, listener);
  }

  /**
   * Clean up resources
   *
   * @description Cleans up all resources and subscriptions
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  destroy(): void {
    this.disconnect();
    this.unsubscribeAuth?.();
    this.clearReconnectTimer();
  }
}

/**
 * Create authenticated SSE connection
 *
 * @description Factory function for creating authenticated SSE connections
 *
 * @param {SSEAuthOptions} options - SSE connection options
 * @returns {SSEAuthHandler} SSE handler instance
 *
 * @example
 * ```typescript
 * const sseConnection = createAuthenticatedSSE({
 *   url: '/api/quiz-events',
 *   onMessage: (event) => {
 *     const data = JSON.parse(event.data);
 *     console.log('Quiz event:', data);
 *   }
 * });
 *
 * await sseConnection.connect();
 * ```
 *
 * @since 2025-08-04
 * @author Claude Code Agent
 * @requirements REQ-AUTH-017 (SSE factory function)
 */
export function createAuthenticatedSSE(options: SSEAuthOptions): SSEAuthHandler {
  return new SSEAuthHandler(options);
}

/**
 * SSE Event Types
 *
 * @description Common SSE event type constants for type safety
 *
 * @since 2025-08-04
 * @author Claude Code Agent
 */
export const SSEEventTypes = {
  QUIZ_UPDATE: 'quiz-update',
  USER_PROGRESS: 'user-progress',
  SYSTEM_NOTIFICATION: 'system-notification',
  AUTH_ERROR: 'auth-error',
  TOKEN_REFRESH: 'token-refresh',
  CONNECTION_CLOSE: 'close',
} as const;

export type SSEEventType = (typeof SSEEventTypes)[keyof typeof SSEEventTypes];
