/**
 * SSE Hook for Project Events
 *
 * Provides real-time event streaming from the backend with:
 * - Automatic reconnection with exponential backoff
 * - Connection state management
 * - Type-safe event handling
 * - Integration with event store
 *
 * @module lib/hooks/useProjectEvents
 */

'use client';

import { useEffect, useRef, useCallback, useState } from 'react';
import { useAuth } from '@/lib/auth/AuthContext';
import { useEventStore, useProjectEventsFromStore } from '@/lib/stores/eventStore';
import type { ProjectEvent, ConnectionState, SSEError } from '@/lib/types/events';
import config from '@/config/app.config';

// ============================================================================
// Constants
// ============================================================================

/** Initial retry delay in milliseconds */
const INITIAL_RETRY_DELAY = 1000;

/** Maximum retry delay in milliseconds (30 seconds) */
const MAX_RETRY_DELAY = 30000;

/** Maximum number of retry attempts before giving up (0 = unlimited) */
const MAX_RETRY_ATTEMPTS = 0;

/** Backoff multiplier for exponential backoff */
const BACKOFF_MULTIPLIER = 2;

// ============================================================================
// Types
// ============================================================================

export interface UseProjectEventsOptions {
  /** Enable automatic connection on mount (default: true) */
  autoConnect?: boolean;
  /** Custom retry delay in milliseconds (default: 1000) */
  initialRetryDelay?: number;
  /** Maximum retry delay in milliseconds (default: 30000) */
  maxRetryDelay?: number;
  /** Maximum retry attempts (0 = unlimited, default: 0) */
  maxRetryAttempts?: number;
  /** Callback when event is received */
  onEvent?: (event: ProjectEvent) => void;
  /** Callback when connection state changes */
  onConnectionChange?: (state: ConnectionState) => void;
  /** Callback when error occurs */
  onError?: (error: SSEError) => void;
}

export interface UseProjectEventsResult {
  /** Events for the project (from store) */
  events: ProjectEvent[];
  /** Whether connection is established */
  isConnected: boolean;
  /** Current connection state */
  connectionState: ConnectionState;
  /** Current error, if any */
  error: SSEError | null;
  /** Current retry attempt count */
  retryCount: number;
  /** Manually trigger reconnection */
  reconnect: () => void;
  /** Disconnect from SSE */
  disconnect: () => void;
  /** Clear events for this project */
  clearEvents: () => void;
}

// ============================================================================
// Hook Implementation
// ============================================================================

/**
 * Hook for consuming real-time project events via SSE
 *
 * @param projectId - Project ID to subscribe to
 * @param options - Configuration options
 * @returns Event data and connection controls
 *
 * @example
 * ```tsx
 * const { events, isConnected, error, reconnect } = useProjectEvents('project-123');
 *
 * if (!isConnected) {
 *   return <ConnectionStatus state={connectionState} onReconnect={reconnect} />;
 * }
 *
 * return <EventList events={events} />;
 * ```
 */
export function useProjectEvents(
  projectId: string,
  options: UseProjectEventsOptions = {}
): UseProjectEventsResult {
  const {
    autoConnect = true,
    initialRetryDelay = INITIAL_RETRY_DELAY,
    maxRetryDelay = MAX_RETRY_DELAY,
    maxRetryAttempts = MAX_RETRY_ATTEMPTS,
    onEvent,
    onConnectionChange,
    onError,
  } = options;

  // Auth state
  const { accessToken, isAuthenticated } = useAuth();

  // Event store
  const events = useProjectEventsFromStore(projectId);
  const addEvent = useEventStore((state) => state.addEvent);
  const clearProjectEvents = useEventStore((state) => state.clearProjectEvents);

  // Local state
  const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');
  const [error, setError] = useState<SSEError | null>(null);
  const [retryCount, setRetryCount] = useState(0);

  // Refs for cleanup and reconnection logic
  const eventSourceRef = useRef<EventSource | null>(null);
  const retryTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const currentRetryDelayRef = useRef(initialRetryDelay);
  const isManualDisconnectRef = useRef(false);
  const mountedRef = useRef(true);
  const pingHandlerRef = useRef<(() => void) | null>(null);

  /**
   * Update connection state and notify callback
   */
  const updateConnectionState = useCallback(
    (state: ConnectionState) => {
      if (!mountedRef.current) return;
      setConnectionState(state);
      onConnectionChange?.(state);
    },
    [onConnectionChange]
  );

  /**
   * Handle SSE error
   */
  const handleError = useCallback(
    (message: string, code?: string) => {
      if (!mountedRef.current) return;

      const sseError: SSEError = {
        message,
        code,
        timestamp: new Date().toISOString(),
        retryAttempt: retryCount + 1,
      };

      setError(sseError);
      onError?.(sseError);
    },
    [retryCount, onError]
  );

  /**
   * Parse and validate event data
   */
  const parseEvent = useCallback((data: string): ProjectEvent | null => {
    try {
      const parsed = JSON.parse(data);

      // Validate required fields
      if (!parsed.id || !parsed.type || !parsed.timestamp || !parsed.project_id) {
        console.warn('[SSE] Invalid event structure:', parsed);
        return null;
      }

      return parsed as ProjectEvent;
    } catch (err) {
      console.warn('[SSE] Failed to parse event data:', err);
      return null;
    }
  }, []);

  /**
   * Close existing connection and clear retry timeout
   */
  const cleanup = useCallback(() => {
    if (retryTimeoutRef.current) {
      clearTimeout(retryTimeoutRef.current);
      retryTimeoutRef.current = null;
    }

    // Remove ping listener before closing to prevent memory leak
    if (eventSourceRef.current && pingHandlerRef.current) {
      eventSourceRef.current.removeEventListener('ping', pingHandlerRef.current);
      pingHandlerRef.current = null;
    }

    if (eventSourceRef.current) {
      eventSourceRef.current.close();
      eventSourceRef.current = null;
    }
  }, []);

  /**
   * Calculate next retry delay with exponential backoff
   */
  const getNextRetryDelay = useCallback(() => {
    const nextDelay = currentRetryDelayRef.current * BACKOFF_MULTIPLIER;
    currentRetryDelayRef.current = Math.min(nextDelay, maxRetryDelay);
    return currentRetryDelayRef.current;
  }, [maxRetryDelay]);

  /**
   * Schedule reconnection attempt
   */
  /* istanbul ignore next -- reconnection logic is difficult to test with mock EventSource */
  const scheduleReconnect = useCallback(() => {
    if (isManualDisconnectRef.current) return;
    if (maxRetryAttempts > 0 && retryCount >= maxRetryAttempts) {
      console.warn('[SSE] Max retry attempts reached');
      updateConnectionState('error');
      return;
    }

    const delay = getNextRetryDelay();

    if (config.debug.api) {
      console.log(`[SSE] Scheduling reconnect in ${delay}ms (attempt ${retryCount + 1})`);
    }

    retryTimeoutRef.current = setTimeout(() => {
      if (!mountedRef.current || isManualDisconnectRef.current) return;
      setRetryCount((prev) => prev + 1);
      connect();
    }, delay);
  }, [retryCount, maxRetryAttempts, getNextRetryDelay, updateConnectionState]);

  /**
   * Connect to SSE endpoint
   */
  const connect = useCallback(() => {
    // Prevent connection if not authenticated or no project ID
    /* istanbul ignore next -- early return guard, tested via connection state */
    if (!isAuthenticated || !accessToken || !projectId) {
      if (config.debug.api) {
        console.log('[SSE] Cannot connect: missing auth or projectId');
      }
      return;
    }

    // Clean up existing connection
    cleanup();
    isManualDisconnectRef.current = false;

    updateConnectionState('connecting');
    setError(null);

    // Build SSE URL with auth token
    const baseUrl = config.api.url;
    // Backend SSE endpoint is at /events/stream (not /events)
    const sseUrl = `${baseUrl}/api/v1/projects/${projectId}/events/stream`;

    // Note: EventSource doesn't support custom headers natively
    // Backend SSE endpoint accepts token as query parameter for EventSource compatibility
    const urlWithAuth = `${sseUrl}?token=${encodeURIComponent(accessToken)}`;

    try {
      const eventSource = new EventSource(urlWithAuth);
      eventSourceRef.current = eventSource;

      /* istanbul ignore next -- EventSource onopen handler */
      eventSource.onopen = () => {
        if (!mountedRef.current) return;

        if (config.debug.api) {
          console.log('[SSE] Connection opened');
        }

        updateConnectionState('connected');
        setRetryCount(0);
        currentRetryDelayRef.current = initialRetryDelay;
      };

      eventSource.onmessage = (event) => {
        if (!mountedRef.current) return;

        const parsedEvent = parseEvent(event.data);
        if (parsedEvent) {
          // Add to store
          addEvent(parsedEvent);
          // Notify callback
          onEvent?.(parsedEvent);
        }
      };

      // Handle specific event types from backend
      // Store handler reference for proper cleanup
      /* istanbul ignore next -- ping handler, tested via mock */
      const pingHandler = () => {
        // Keep-alive ping from server, no action needed
        if (config.debug.api) {
          console.log('[SSE] Received ping');
        }
      };
      pingHandlerRef.current = pingHandler;
      eventSource.addEventListener('ping', pingHandler);

      /* istanbul ignore next -- EventSource onerror handler */
      eventSource.onerror = (err) => {
        if (!mountedRef.current) return;

        console.error('[SSE] Connection error:', err);

        if (eventSource.readyState === EventSource.CLOSED) {
          handleError('Connection closed unexpectedly', 'CONNECTION_CLOSED');
          updateConnectionState('disconnected');
          scheduleReconnect();
        } else {
          handleError('Connection error', 'CONNECTION_ERROR');
          updateConnectionState('error');
          scheduleReconnect();
        }
      };
    } catch (err) {
      console.error('[SSE] Failed to create EventSource:', err);
      handleError('Failed to create connection', 'CREATION_FAILED');
      updateConnectionState('error');
      scheduleReconnect();
    }
  }, [
    isAuthenticated,
    accessToken,
    projectId,
    cleanup,
    updateConnectionState,
    handleError,
    parseEvent,
    addEvent,
    onEvent,
    scheduleReconnect,
    initialRetryDelay,
  ]);

  /**
   * Manually disconnect from SSE
   */
  const disconnect = useCallback(() => {
    isManualDisconnectRef.current = true;
    cleanup();
    updateConnectionState('disconnected');
    setRetryCount(0);
    currentRetryDelayRef.current = initialRetryDelay;
  }, [cleanup, updateConnectionState, initialRetryDelay]);

  /**
   * Manually trigger reconnection
   */
  const reconnect = useCallback(() => {
    disconnect();
    isManualDisconnectRef.current = false;
    connect();
  }, [disconnect, connect]);

  /**
   * Clear events for this project
   */
  const clearEvents = useCallback(() => {
    clearProjectEvents(projectId);
  }, [clearProjectEvents, projectId]);

  // Consolidated connection management effect
  // Handles both initial mount and auth state changes to prevent race conditions
  useEffect(() => {
    mountedRef.current = true;

    // Connect when authenticated with a project and not manually disconnected
    if (autoConnect && isAuthenticated && accessToken && projectId) {
      if (connectionState === 'disconnected' && !isManualDisconnectRef.current) {
        connect();
      }
    } else if (!isAuthenticated && connectionState !== 'disconnected') {
      // Disconnect when auth is lost
      disconnect();
    }

    return () => {
      mountedRef.current = false;
      cleanup();
    };
  }, [
    autoConnect,
    isAuthenticated,
    accessToken,
    projectId,
    connectionState,
    connect,
    disconnect,
    cleanup,
  ]);

  return {
    events,
    isConnected: connectionState === 'connected',
    connectionState,
    error,
    retryCount,
    reconnect,
    disconnect,
    clearEvents,
  };
}
