import { useEffect, useRef, useCallback, useState } from 'react';
import { io, Socket } from 'socket.io-client';
import { api } from '@/shared/lib/api';
import type { BattleSession, BattleToken } from '@/shared/types';

// Derive WebSocket URL from API URL (remove /api suffix)
const SOCKET_URL = import.meta.env.VITE_API_URL?.replace('/api', '') || 'http://localhost:5000';

// Singleton socket manager to prevent multiple connections
let globalSocket: Socket | null = null;
let globalSocketRefCount = 0;
let currentSessionId: string | null = null;
let connectionAttempted = false;

export type BattleUpdateType =
  | 'token_added'
  | 'token_removed'
  | 'token_moved'
  | 'token_updated'
  | 'token_hp_changed'
  | 'initiative_set'
  | 'round_advanced'
  | 'session_updated'
  | 'session_deleted';

export interface BattleUpdate {
  sessionId: string;
  type: BattleUpdateType;
  data: any;
}

interface UseBattleSocketOptions {
  sessionId: string;
  onTokenAdded?: (data: { token: BattleToken }) => void;
  onTokenRemoved?: (data: { tokenId: string }) => void;
  onTokenMoved?: (data: { tokenId: string; positionX: number; positionY: number }) => void;
  onTokenUpdated?: (data: { token: BattleToken }) => void;
  onTokenHpChanged?: (data: { tokenId: string; hpCurrent: number; hpMax: number }) => void;
  onInitiativeSet?: (data: { tokenId: string; initiative: number }) => void;
  onRoundAdvanced?: (data: { roundNumber: number }) => void;
  onSessionUpdated?: (data: { session: BattleSession }) => void;
  onSessionDeleted?: () => void;
}

export function useBattleSocket({
  sessionId,
  onTokenAdded,
  onTokenRemoved,
  onTokenMoved,
  onTokenUpdated,
  onTokenHpChanged,
  onInitiativeSet,
  onRoundAdvanced,
  onSessionUpdated,
  onSessionDeleted,
}: UseBattleSocketOptions) {
  const [isConnected, setIsConnected] = useState(false);
  const [isGM, setIsGM] = useState(false);
  const mountedRef = useRef(true);

  // Use refs for callbacks to avoid reconnection on callback changes
  const callbacksRef = useRef({
    onTokenAdded,
    onTokenRemoved,
    onTokenMoved,
    onTokenUpdated,
    onTokenHpChanged,
    onInitiativeSet,
    onRoundAdvanced,
    onSessionUpdated,
    onSessionDeleted,
  });

  // Update refs when callbacks change (without causing reconnection)
  useEffect(() => {
    callbacksRef.current = {
      onTokenAdded,
      onTokenRemoved,
      onTokenMoved,
      onTokenUpdated,
      onTokenHpChanged,
      onInitiativeSet,
      onRoundAdvanced,
      onSessionUpdated,
      onSessionDeleted,
    };
  });

  useEffect(() => {
    mountedRef.current = true;
    const token = api.getToken();
    if (!token || !sessionId) return;

    // Increment ref count
    globalSocketRefCount++;

    // If we already have a socket for a different session, leave that room first
    if (globalSocket?.connected && currentSessionId && currentSessionId !== sessionId) {
      globalSocket.emit('leave_battle', { sessionId: currentSessionId });
    }

    // Update current session
    currentSessionId = sessionId;

    // Create socket if it doesn't exist and we haven't already tried
    if (!globalSocket && !connectionAttempted) {
      connectionAttempted = true;

      globalSocket = io(`${SOCKET_URL}/battles`, {
        auth: { token },
        transports: ['polling', 'websocket'],
        upgrade: true,
        reconnection: true,
        reconnectionAttempts: 3,
        reconnectionDelay: 3000,
        reconnectionDelayMax: 10000,
        timeout: 10000,
        autoConnect: true,
      });

      globalSocket.on('connect', () => {
        if (!mountedRef.current) return;
        console.log('[Battle WebSocket] Connected');
        setIsConnected(true);

        // Join battle room on connect/reconnect
        if (currentSessionId && globalSocket) {
          globalSocket.emit('join_battle', { sessionId: currentSessionId }, (response: { success: boolean; isGM?: boolean; error?: string }) => {
            if (response.success) {
              console.log(`[Battle WebSocket] Joined room: ${currentSessionId}, isGM: ${response.isGM}`);
              if (mountedRef.current) {
                setIsGM(response.isGM ?? false);
              }
            }
          });
        }
      });

      globalSocket.on('disconnect', (reason) => {
        console.log(`[Battle WebSocket] Disconnected: ${reason}`);
        if (mountedRef.current) {
          setIsConnected(false);
        }
      });

      globalSocket.on('connect_error', () => {
        if (mountedRef.current) {
          setIsConnected(false);
        }
      });

      // Handle battle updates
      globalSocket.on('battle_update', (update: BattleUpdate) => {
        if (!mountedRef.current) return;
        const callbacks = callbacksRef.current;

        switch (update.type) {
          case 'token_added':
            callbacks.onTokenAdded?.(update.data);
            break;
          case 'token_removed':
            callbacks.onTokenRemoved?.(update.data);
            break;
          case 'token_moved':
            callbacks.onTokenMoved?.(update.data);
            break;
          case 'token_updated':
            callbacks.onTokenUpdated?.(update.data);
            break;
          case 'token_hp_changed':
            callbacks.onTokenHpChanged?.(update.data);
            break;
          case 'initiative_set':
            callbacks.onInitiativeSet?.(update.data);
            break;
          case 'round_advanced':
            callbacks.onRoundAdvanced?.(update.data);
            break;
          case 'session_updated':
            callbacks.onSessionUpdated?.(update.data);
            break;
          case 'session_deleted':
            callbacks.onSessionDeleted?.();
            break;
        }
      });
    } else if (globalSocket?.connected) {
      // Socket already exists and connected, just join the room
      globalSocket.emit('join_battle', { sessionId }, (response: { success: boolean; isGM?: boolean; error?: string }) => {
        if (response.success) {
          console.log(`[Battle WebSocket] Joined room: ${sessionId}, isGM: ${response.isGM}`);
          if (mountedRef.current) {
            setIsGM(response.isGM ?? false);
          }
        }
      });
    }

    // Cleanup on unmount
    return () => {
      mountedRef.current = false;
      globalSocketRefCount--;

      if (globalSocket && currentSessionId === sessionId) {
        globalSocket.emit('leave_battle', { sessionId });
      }

      // Only disconnect socket if no more refs
      if (globalSocketRefCount <= 0 && globalSocket) {
        globalSocket.disconnect();
        globalSocket = null;
        currentSessionId = null;
        connectionAttempted = false;
      }
    };
  }, [sessionId]);

  // Socket actions
  const moveToken = useCallback((tokenId: string, positionX: number, positionY: number) => {
    if (!globalSocket?.connected || !currentSessionId) return Promise.reject('Not connected');

    return new Promise((resolve, reject) => {
      globalSocket!.emit('move_token', {
        sessionId: currentSessionId,
        tokenId,
        positionX,
        positionY,
      }, (response: { success: boolean; error?: string }) => {
        if (response.success) {
          resolve(response);
        } else {
          reject(response.error);
        }
      });
    });
  }, []);

  const updateTokenHp = useCallback((tokenId: string, hpCurrent: number) => {
    if (!globalSocket?.connected || !currentSessionId) return Promise.reject('Not connected');

    return new Promise((resolve, reject) => {
      globalSocket!.emit('update_token_hp', {
        sessionId: currentSessionId,
        tokenId,
        hpCurrent,
      }, (response: { success: boolean; error?: string }) => {
        if (response.success) {
          resolve(response);
        } else {
          reject(response.error);
        }
      });
    });
  }, []);

  const setInitiative = useCallback((tokenId: string, initiative: number) => {
    if (!globalSocket?.connected || !currentSessionId) return Promise.reject('Not connected');

    return new Promise((resolve, reject) => {
      globalSocket!.emit('set_initiative', {
        sessionId: currentSessionId,
        tokenId,
        initiative,
      }, (response: { success: boolean; error?: string }) => {
        if (response.success) {
          resolve(response);
        } else {
          reject(response.error);
        }
      });
    });
  }, []);

  const advanceRound = useCallback(() => {
    if (!globalSocket?.connected || !currentSessionId) return Promise.reject('Not connected');

    return new Promise((resolve, reject) => {
      globalSocket!.emit('advance_round', {
        sessionId: currentSessionId,
      }, (response: { success: boolean; error?: string }) => {
        if (response.success) {
          resolve(response);
        } else {
          reject(response.error);
        }
      });
    });
  }, []);

  const reconnect = useCallback(() => {
    if (globalSocket) {
      globalSocket.connect();
    } else {
      connectionAttempted = false;
    }
  }, []);

  return {
    socket: globalSocket,
    isConnected,
    isGM,
    reconnect,
    moveToken,
    updateTokenHp,
    setInitiative,
    advanceRound,
  };
}
