import { useEffect, useState, useRef, useCallback } from 'react';
import { apiClient } from '@/services/api/client';
import { logger } from '@/utils/logger';

/**
 * Interface pour un message WebSocket de broadcast
 * T0369: Create Playback Analytics Frontend Real-time Hook
 */
export interface BroadcastMessage {
  track_id: string;
  type:
    | 'analytics_update'
    | 'stats_update'
    | 'subscribed'
    | 'unsubscribed'
    | 'pong';
  data: any;
  timestamp: string;
}

/**
 * Interface pour un message WebSocket envoyé au serveur
 */
export interface WebSocketMessage {
  type: 'subscribe' | 'unsubscribe' | 'ping';
  track_id?: string;
  data?: any;
}

/**
 * Interface pour les analytics de lecture reçues en temps réel
 */
export interface PlaybackAnalytics {
  id: string;
  track_id: string;
  user_id: string;
  play_time: number;
  pause_count: number;
  seek_count: number;
  completion_rate: number;
  started_at: string;
  ended_at?: string | null;
  created_at: string;
}

/**
 * Interface pour les statistiques de lecture reçues en temps réel
 */
export interface PlaybackStats {
  total_sessions: number;
  total_play_time: number;
  average_play_time: number;
  total_pauses: number;
  average_pauses: number;
  total_seeks: number;
  average_seeks: number;
  average_completion: number;
  completion_rate: number;
}

/**
 * Options pour le hook usePlaybackRealtime
 */
export interface UsePlaybackRealtimeOptions {
  /**
   * Activer/désactiver la connexion automatique
   * @default true
   */
  autoConnect?: boolean;
  /**
   * Intervalle de ping en millisecondes pour maintenir la connexion
   * @default 30000 (30 secondes)
   */
  pingInterval?: number;
  /**
   * Nombre maximum de tentatives de reconnexion
   * @default 5
   */
  maxReconnectAttempts?: number;
  /**
   * Délai entre les tentatives de reconnexion en millisecondes
   * @default 3000 (3 secondes)
   */
  reconnectDelay?: number;
  /**
   * Callback appelé lors de la réception d'une mise à jour d'analytics
   */
  onAnalyticsUpdate?: (analytics: PlaybackAnalytics) => void;
  /**
   * Callback appelé lors de la réception d'une mise à jour de statistiques
   */
  onStatsUpdate?: (stats: PlaybackStats) => void;
  /**
   * Callback appelé lors de la connexion
   */
  onConnect?: () => void;
  /**
   * Callback appelé lors de la déconnexion
   */
  onDisconnect?: () => void;
  /**
   * Callback appelé en cas d'erreur
   */
  onError?: (error: Error) => void;
}

/**
 * Hook pour recevoir les mises à jour en temps réel des analytics de lecture via WebSocket
 * T0369: Create Playback Analytics Frontend Real-time Hook
 *
 * @param trackId - ID du track pour lequel recevoir les mises à jour
 * @param options - Options de configuration du hook
 * @returns État et fonctions pour gérer la connexion WebSocket
 */
export function usePlaybackRealtime(
  trackId: string | null,
  options: UsePlaybackRealtimeOptions = {},
) {
  const {
    autoConnect = true,
    pingInterval = 30000,
    maxReconnectAttempts = 5,
    reconnectDelay = 3000,
    onAnalyticsUpdate,
    onStatsUpdate,
    onConnect,
    onDisconnect,
    onError,
  } = options;

  const [isConnected, setIsConnected] = useState<boolean>(false);
  const [isConnecting, setIsConnecting] = useState<boolean>(false);
  const [error, setError] = useState<Error | null>(null);
  const [latestAnalytics, setLatestAnalytics] =
    useState<PlaybackAnalytics | null>(null);
  const [latestStats, setLatestStats] = useState<PlaybackStats | null>(null);

  const wsRef = useRef<WebSocket | null>(null);
  const reconnectAttemptsRef = useRef<number>(0);
  const pingIntervalRef = useRef<NodeJS.Timeout | null>(null);
  const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
  const isMountedRef = useRef<boolean>(true);

  /**
   * Construit l'URL WebSocket pour le track
   */
  const getWebSocketUrl = useCallback((trackId: string): string => {
    const apiBaseUrl = (() => {
      const url = import.meta.env.VITE_API_URL;
      if (!url) {
        if (import.meta.env.PROD) {
          throw new Error('VITE_API_URL must be defined in production');
        }
        // Fallback uniquement en développement
        return 'http://127.0.0.1:8080';
      }
      // Convertir URL relative en URL absolue
      if (url.startsWith('/')) {
        const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
        return `${protocol}//${window.location.host}${url}`;
      }
      return url;
    })();
    // Convertir http:// en ws:// ou https:// en wss://
    const wsBaseUrl = apiBaseUrl.replace(/^https?/, (match) => match === 'https' ? 'wss' : 'ws');
    // Récupérer le token d'authentification depuis le client API
    const token =
      apiClient.defaults.headers.common['Authorization']
        ?.toString()
        .replace('Bearer ', '') || '';
    // Construire l'URL avec le token si disponible
    const url = `${wsBaseUrl}/api/v1/tracks/${trackId}/playback/ws${token ? `?token=${token}` : ''}`;
    return url;
  }, []);

  /**
   * Envoie un message au serveur WebSocket
   */
  const sendMessage = useCallback(
    (message: WebSocketMessage) => {
      if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
        try {
          wsRef.current.send(JSON.stringify(message));
        } catch (err) {
          logger.error('Failed to send WebSocket message:', { error: err });
          const error =
            err instanceof Error
              ? err
              : new Error('Failed to send WebSocket message');
          setError(error);
          onError?.(error);
        }
      }
    },
    [onError],
  );

  /**
   * S'abonne au track
   */
  const subscribe = useCallback(() => {
    if (
      trackId &&
      wsRef.current &&
      wsRef.current.readyState === WebSocket.OPEN
    ) {
      sendMessage({
        type: 'subscribe',
        track_id: trackId,
      });
    }
  }, [trackId, sendMessage]);

  /**
   * Se désabonne du track
   */
  const unsubscribe = useCallback(() => {
    if (
      trackId &&
      wsRef.current &&
      wsRef.current.readyState === WebSocket.OPEN
    ) {
      sendMessage({
        type: 'unsubscribe',
        track_id: trackId,
      });
    }
  }, [trackId, sendMessage]);

  /**
   * Envoie un ping pour maintenir la connexion
   */
  const sendPing = useCallback(() => {
    if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
      sendMessage({ type: 'ping' });
    }
  }, [sendMessage]);

  /**
   * Gère la reconnexion
   */
  const reconnect = useCallback(() => {
    if (!isMountedRef.current || !trackId) {
      return;
    }

    if (reconnectAttemptsRef.current >= maxReconnectAttempts) {
      const error = new Error('Max reconnection attempts reached');
      setError(error);
      onError?.(error);
      setIsConnecting(false);
      return;
    }

    reconnectAttemptsRef.current += 1;
    setIsConnecting(true);

    reconnectTimeoutRef.current = setTimeout(() => {
      if (isMountedRef.current && trackId) {
        connect();
      }
    }, reconnectDelay);
  }, [trackId, maxReconnectAttempts, reconnectDelay, onError]);

  /**
   * Établit la connexion WebSocket
   */
  const connect = useCallback(() => {
    if (!trackId) {
      return;
    }

    // Nettoyer la connexion existante
    if (wsRef.current) {
      wsRef.current.close();
      wsRef.current = null;
    }

    // Nettoyer les timeouts
    if (reconnectTimeoutRef.current) {
      clearTimeout(reconnectTimeoutRef.current);
      reconnectTimeoutRef.current = null;
    }

    setIsConnecting(true);
    setError(null);

    try {
      const url = getWebSocketUrl(trackId);
      const ws = new WebSocket(url);

      ws.onopen = () => {
        if (!isMountedRef.current) {
          ws.close();
          return;
        }

        setIsConnected(true);
        setIsConnecting(false);
        reconnectAttemptsRef.current = 0;
        wsRef.current = ws;

        // S'abonner au track
        subscribe();

        // Démarrer le ping périodique
        if (pingIntervalRef.current) {
          clearInterval(pingIntervalRef.current);
        }
        pingIntervalRef.current = setInterval(() => {
          if (isMountedRef.current) {
            sendPing();
          }
        }, pingInterval);

        onConnect?.();
      };

      ws.onmessage = (event) => {
        if (!isMountedRef.current) {
          return;
        }

        try {
          const message: BroadcastMessage = JSON.parse(event.data);

          switch (message.type) {
            case 'analytics_update': {
              const analytics = message.data as PlaybackAnalytics;
              setLatestAnalytics(analytics);
              onAnalyticsUpdate?.(analytics);
              break;
            }

            case 'stats_update': {
              const stats = message.data as PlaybackStats;
              setLatestStats(stats);
              onStatsUpdate?.(stats);
              break;
            }

            case 'subscribed':
              // Confirmation d'abonnement
              break;

            case 'unsubscribed':
              // Confirmation de désabonnement
              break;

            case 'pong':
              // Réponse au ping
              break;

            default:
              logger.warn('Unknown WebSocket message type:', message.type);
          }
        } catch (err) {
          logger.error('Failed to parse WebSocket message:', { error: err });
          const error =
            err instanceof Error
              ? err
              : new Error('Failed to parse WebSocket message');
          setError(error);
          onError?.(error);
        }
      };

      ws.onerror = (event) => {
        if (!isMountedRef.current) {
          return;
        }

        logger.error('WebSocket error:', { error: event });
        const error = new Error('WebSocket connection error');
        setError(error);
        setIsConnecting(false);
        onError?.(error);
      };

      ws.onclose = (event) => {
        if (!isMountedRef.current) {
          return;
        }

        setIsConnected(false);
        setIsConnecting(false);
        wsRef.current = null;

        // Nettoyer le ping interval
        if (pingIntervalRef.current) {
          clearInterval(pingIntervalRef.current);
          pingIntervalRef.current = null;
        }

        onDisconnect?.();

        // Tenter de se reconnecter si la fermeture n'était pas intentionnelle
        if (event.code !== 1000 && trackId && autoConnect) {
          reconnect();
        }
      };
    } catch (err) {
      if (!isMountedRef.current) {
        return;
      }

      logger.error('Failed to create WebSocket connection:', { error: err });
      const error =
        err instanceof Error
          ? err
          : new Error('Failed to create WebSocket connection');
      setError(error);
      setIsConnecting(false);
      onError?.(error);

      // Tenter de se reconnecter
      if (autoConnect) {
        reconnect();
      }
    }
  }, [
    trackId,
    getWebSocketUrl,
    subscribe,
    sendPing,
    pingInterval,
    autoConnect,
    onConnect,
    onDisconnect,
    onError,
    reconnect,
  ]);

  /**
   * Déconnecte le WebSocket
   */
  const disconnect = useCallback(() => {
    // Nettoyer les timeouts
    if (reconnectTimeoutRef.current) {
      clearTimeout(reconnectTimeoutRef.current);
      reconnectTimeoutRef.current = null;
    }

    if (pingIntervalRef.current) {
      clearInterval(pingIntervalRef.current);
      pingIntervalRef.current = null;
    }

    // Se désabonner avant de fermer
    unsubscribe();

    if (wsRef.current) {
      wsRef.current.close(1000, 'Client disconnect');
      wsRef.current = null;
    }

    setIsConnected(false);
    setIsConnecting(false);
    reconnectAttemptsRef.current = 0;
  }, [unsubscribe]);

  // Gérer la connexion/déconnexion automatique
  useEffect(() => {
    isMountedRef.current = true;

    if (autoConnect && trackId) {
      connect();
    }

    return () => {
      isMountedRef.current = false;
      disconnect();
    };
  }, [trackId, autoConnect, connect, disconnect]);

  // Nettoyer lors du démontage
  useEffect(() => {
    return () => {
      isMountedRef.current = false;
      disconnect();
    };
  }, [disconnect]);

  return {
    isConnected,
    isConnecting,
    error,
    latestAnalytics,
    latestStats,
    connect,
    disconnect,
    subscribe,
    unsubscribe,
  };
}
