/**
 * Socket.IO client manager for real-time game communication.
 *
 * Provides a singleton connection manager with:
 * - Automatic authentication via token
 * - Reconnection with exponential backoff
 * - Typed event emitters for the game namespace
 */
import { io, Socket } from 'socket.io-client'

import { config } from '@/config'
import { useAuthStore } from '@/stores/auth'
import type { ClientToServerEvents, ServerToClientEvents } from './types'

type TypedSocket = Socket<ServerToClientEvents, ClientToServerEvents>

export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting'

export interface SocketClientOptions {
  /** Auto-connect on creation (default: false) */
  autoConnect?: boolean
  /** Namespace to connect to (default: '/game') */
  namespace?: string
}

/**
 * Socket.IO client manager.
 *
 * Use `socketClient.connect()` to establish connection.
 * Use `socketClient.socket` to access the raw socket for event handling.
 */
class SocketClient {
  private _socket: TypedSocket | null = null
  private _connectionState: ConnectionState = 'disconnected'
  private _namespace: string = '/game'

  // Event listeners for connection state changes
  private connectionListeners: Set<(state: ConnectionState) => void> = new Set()

  /**
   * Get the current connection state.
   */
  get connectionState(): ConnectionState {
    return this._connectionState
  }

  /**
   * Get the raw socket instance.
   *
   * Returns null if not connected.
   */
  get socket(): TypedSocket | null {
    return this._socket
  }

  /**
   * Check if currently connected.
   */
  get isConnected(): boolean {
    return this._connectionState === 'connected'
  }

  /**
   * Connect to the Socket.IO server.
   *
   * Authenticates using the current access token from auth store.
   */
  async connect(options: SocketClientOptions = {}): Promise<void> {
    const { autoConnect = true, namespace = '/game' } = options
    this._namespace = namespace

    // Don't reconnect if already connected
    if (this._socket?.connected) {
      return
    }

    // Disconnect existing socket if any
    if (this._socket) {
      this._socket.disconnect()
      this._socket = null
    }

    const auth = useAuthStore()
    const token = await auth.getValidToken()

    if (!token) {
      throw new Error('Cannot connect: not authenticated')
    }

    this.setConnectionState('connecting')

    const socketUrl = `${config.wsUrl}${namespace}`

    this._socket = io(socketUrl, {
      auth: { token },
      autoConnect,
      reconnection: true,
      reconnectionAttempts: 10,
      reconnectionDelay: 1000,
      reconnectionDelayMax: 30000,
      timeout: 20000,
      transports: ['websocket', 'polling'],
    }) as TypedSocket

    this.setupEventHandlers()

    // Wait for connection or error
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Connection timeout'))
      }, 20000)

      this._socket!.once('connect', () => {
        clearTimeout(timeout)
        resolve()
      })

      this._socket!.once('connect_error', (error) => {
        clearTimeout(timeout)
        reject(error)
      })
    })
  }

  /**
   * Disconnect from the server.
   */
  disconnect(): void {
    if (this._socket) {
      this._socket.disconnect()
      this._socket = null
    }
    this.setConnectionState('disconnected')
  }

  /**
   * Set up internal event handlers for connection management.
   */
  private setupEventHandlers(): void {
    if (!this._socket) return

    this._socket.on('connect', () => {
      this.setConnectionState('connected')
    })

    this._socket.on('disconnect', (reason) => {
      if (reason === 'io server disconnect') {
        // Server initiated disconnect - don't auto-reconnect
        this.setConnectionState('disconnected')
      } else {
        // Client-side disconnect or network issue - will auto-reconnect
        this.setConnectionState('reconnecting')
      }
    })

    this._socket.io.on('reconnect_attempt', () => {
      this.setConnectionState('reconnecting')
    })

    this._socket.io.on('reconnect', () => {
      this.setConnectionState('connected')
    })

    this._socket.io.on('reconnect_failed', () => {
      this.setConnectionState('disconnected')
    })
  }

  /**
   * Update connection state and notify listeners.
   */
  private setConnectionState(state: ConnectionState): void {
    this._connectionState = state
    this.connectionListeners.forEach(listener => listener(state))
  }

  /**
   * Subscribe to connection state changes.
   *
   * @returns Unsubscribe function
   */
  onConnectionStateChange(listener: (state: ConnectionState) => void): () => void {
    this.connectionListeners.add(listener)
    return () => this.connectionListeners.delete(listener)
  }

  // ============================================
  // Game Event Helpers
  // ============================================

  /**
   * Join a game room.
   */
  joinGame(gameId: string): void {
    this._socket?.emit('game:join', { game_id: gameId })
  }

  /**
   * Send a game action.
   */
  sendAction(action: ClientToServerEvents['game:action'] extends (data: infer T) => void ? T : never): void {
    this._socket?.emit('game:action', action)
  }

  /**
   * Resign from the current game.
   */
  resign(gameId: string): void {
    this._socket?.emit('game:resign', { game_id: gameId })
  }

  /**
   * Join as spectator.
   */
  spectate(gameId: string): void {
    this._socket?.emit('game:spectate', { game_id: gameId })
  }

  /**
   * Leave spectator mode.
   */
  leaveSpectate(gameId: string): void {
    this._socket?.emit('game:leave_spectate', { game_id: gameId })
  }

  // ============================================
  // Event Subscription Helpers
  // ============================================

  /**
   * Subscribe to game state updates.
   */
  onGameState(handler: ServerToClientEvents['game:state']): () => void {
    this._socket?.on('game:state', handler)
    return () => this._socket?.off('game:state', handler)
  }

  /**
   * Subscribe to action results.
   */
  onActionResult(handler: ServerToClientEvents['game:action_result']): () => void {
    this._socket?.on('game:action_result', handler)
    return () => this._socket?.off('game:action_result', handler)
  }

  /**
   * Subscribe to game errors.
   */
  onGameError(handler: ServerToClientEvents['game:error']): () => void {
    this._socket?.on('game:error', handler)
    return () => this._socket?.off('game:error', handler)
  }

  /**
   * Subscribe to game over events.
   */
  onGameOver(handler: ServerToClientEvents['game:game_over']): () => void {
    this._socket?.on('game:game_over', handler)
    return () => this._socket?.off('game:game_over', handler)
  }

  /**
   * Subscribe to opponent connection status.
   */
  onOpponentConnected(handler: ServerToClientEvents['game:opponent_connected']): () => void {
    this._socket?.on('game:opponent_connected', handler)
    return () => this._socket?.off('game:opponent_connected', handler)
  }

  /**
   * Subscribe to turn timeout warnings.
   */
  onTurnTimeoutWarning(handler: ServerToClientEvents['game:turn_timeout_warning']): () => void {
    this._socket?.on('game:turn_timeout_warning', handler)
    return () => this._socket?.off('game:turn_timeout_warning', handler)
  }
}

/**
 * Singleton socket client instance.
 */
export const socketClient = new SocketClient()

export default socketClient
