import { useEffect, useRef, useCallback, useState } from 'react';
import { io, Socket } from 'socket.io-client';
import { api } from '@/shared/lib/api';
import type { Character, CharacterItem, CharacterCondition, CharacterAlchemyState, CharacterFormula, CharacterPreparedItem, ConditionReduced } 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 currentCharacterId: string | null = null;
let connectionAttempted = false;

export type CharacterUpdateType = 'hp' | 'conditions' | 'item' | 'inventory' | 'money' | 'level' | 'equipment_status' | 'rest' | 'alchemy_vials' | 'alchemy_formulas' | 'alchemy_prepared' | 'alchemy_state';

export interface CharacterUpdate {
  characterId: string;
  type: CharacterUpdateType;
  data: any;
}

export interface RestUpdateData {
  hpCurrent: number;
  hpHealed: number;
  conditionsRemoved: string[];
  conditionsReduced: ConditionReduced[];
  resourcesReset: string[];
  alchemyReset: boolean;
}

interface UseCharacterSocketOptions {
  characterId: string;
  onHpUpdate?: (data: { hpCurrent: number; hpTemp: number; hpMax: number }) => void;
  onConditionsUpdate?: (data: { action: 'add' | 'update' | 'remove'; condition?: CharacterCondition; conditionId?: string }) => void;
  onInventoryUpdate?: (data: { action: 'add' | 'remove' | 'update'; item?: CharacterItem; itemId?: string }) => void;
  onEquipmentStatusUpdate?: (data: { action: 'update'; item: CharacterItem }) => void;
  onMoneyUpdate?: (data: { credits: number }) => void;
  onLevelUpdate?: (data: { level: number }) => void;
  onRestUpdate?: (data: RestUpdateData) => void;
  onAlchemyVialsUpdate?: (data: { versatileVialsCurrent: number }) => void;
  onAlchemyFormulasUpdate?: (data: { action: 'add' | 'remove'; formula?: CharacterFormula; formulaId?: string }) => void;
  onAlchemyPreparedUpdate?: (data: { action: 'add' | 'update' | 'remove' | 'prepare' | 'quick_alchemy'; item?: CharacterPreparedItem; items?: CharacterPreparedItem[]; itemId?: string; batchUsed?: number }) => void;
  onAlchemyStateUpdate?: (data: CharacterAlchemyState) => void;
  onFullUpdate?: (character: Character) => void;
}

export function useCharacterSocket({
  characterId,
  onHpUpdate,
  onConditionsUpdate,
  onInventoryUpdate,
  onEquipmentStatusUpdate,
  onMoneyUpdate,
  onLevelUpdate,
  onRestUpdate,
  onAlchemyVialsUpdate,
  onAlchemyFormulasUpdate,
  onAlchemyPreparedUpdate,
  onAlchemyStateUpdate,
  onFullUpdate,
}: UseCharacterSocketOptions) {
  const [isConnected, setIsConnected] = useState(false);
  const mountedRef = useRef(true);

  // Use refs for callbacks to avoid reconnection on callback changes
  const callbacksRef = useRef({
    onHpUpdate,
    onConditionsUpdate,
    onInventoryUpdate,
    onEquipmentStatusUpdate,
    onMoneyUpdate,
    onLevelUpdate,
    onRestUpdate,
    onAlchemyVialsUpdate,
    onAlchemyFormulasUpdate,
    onAlchemyPreparedUpdate,
    onAlchemyStateUpdate,
    onFullUpdate,
  });

  // Update refs when callbacks change (without causing reconnection)
  useEffect(() => {
    callbacksRef.current = {
      onHpUpdate,
      onConditionsUpdate,
      onInventoryUpdate,
      onEquipmentStatusUpdate,
      onMoneyUpdate,
      onLevelUpdate,
      onRestUpdate,
      onAlchemyVialsUpdate,
      onAlchemyFormulasUpdate,
      onAlchemyPreparedUpdate,
      onAlchemyStateUpdate,
      onFullUpdate,
    };
  });

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

    // Increment ref count
    globalSocketRefCount++;

    // If we already have a socket for a different character, leave that room first
    if (globalSocket?.connected && currentCharacterId && currentCharacterId !== characterId) {
      globalSocket.emit('leave_character', { characterId: currentCharacterId });
    }

    // Update current character
    currentCharacterId = characterId;

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

      globalSocket = io(`${SOCKET_URL}/characters`, {
        auth: { token },
        // Start with polling to avoid browser WebSocket errors when server is down
        // Socket.io will automatically upgrade to websocket when connected
        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('[WebSocket] Connected');
        setIsConnected(true);

        // Join character room on connect/reconnect
        if (currentCharacterId && globalSocket) {
          globalSocket.emit('join_character', { characterId: currentCharacterId }, (response: { success: boolean; error?: string }) => {
            if (response.success) {
              console.log(`[WebSocket] Joined room: ${currentCharacterId}`);
            }
          });
        }
      });

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

      globalSocket.on('connect_error', () => {
        // Silently handle - socket.io will retry automatically
        if (mountedRef.current) {
          setIsConnected(false);
        }
      });

      // Handle character updates - use refs to always call latest callbacks
      globalSocket.on('character_update', (update: CharacterUpdate) => {
        if (!mountedRef.current) return;
        const callbacks = callbacksRef.current;

        switch (update.type) {
          case 'hp':
            callbacks.onHpUpdate?.(update.data);
            break;
          case 'conditions':
            callbacks.onConditionsUpdate?.(update.data);
            break;
          case 'inventory':
            callbacks.onInventoryUpdate?.(update.data);
            break;
          case 'equipment_status':
            callbacks.onEquipmentStatusUpdate?.(update.data);
            break;
          case 'money':
            callbacks.onMoneyUpdate?.(update.data);
            break;
          case 'level':
            callbacks.onLevelUpdate?.(update.data);
            break;
          case 'item':
            callbacks.onInventoryUpdate?.(update.data);
            break;
          case 'rest':
            callbacks.onRestUpdate?.(update.data);
            break;
          case 'alchemy_vials':
            callbacks.onAlchemyVialsUpdate?.(update.data);
            break;
          case 'alchemy_formulas':
            callbacks.onAlchemyFormulasUpdate?.(update.data);
            break;
          case 'alchemy_prepared':
            callbacks.onAlchemyPreparedUpdate?.(update.data);
            break;
          case 'alchemy_state':
            callbacks.onAlchemyStateUpdate?.(update.data);
            break;
        }
      });

      globalSocket.on('character_refresh', (character: Character) => {
        if (!mountedRef.current) return;
        callbacksRef.current.onFullUpdate?.(character);
      });
    } else if (globalSocket?.connected) {
      // Socket already exists and connected, just join the room
      globalSocket.emit('join_character', { characterId }, (response: { success: boolean; error?: string }) => {
        if (response.success) {
          console.log(`[WebSocket] Joined room: ${characterId}`);
        }
      });
    }

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

      if (globalSocket && currentCharacterId === characterId) {
        globalSocket.emit('leave_character', { characterId });
      }

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

  const reconnect = useCallback(() => {
    if (globalSocket) {
      globalSocket.connect();
    } else {
      // Reset connection attempt flag to allow new connection
      connectionAttempted = false;
    }
  }, []);

  return {
    socket: globalSocket,
    isConnected,
    reconnect,
  };
}
