/**
 * WebSocket Composable
 *
 * Manages Socket.io connection with type safety, authentication, and auto-reconnection.
 * This composable is league-agnostic and will be shared between SBA and PD frontends.
 *
 * Features:
 * - Type-safe Socket.io client (TypedSocket)
 * - JWT authentication integration
 * - Auto-reconnection with exponential backoff
 * - Connection status tracking
 * - Event listener lifecycle management
 * - Integration with game store
 */

import { ref, computed, watch, onMounted, onUnmounted, readonly } from 'vue'
import type { Socket } from 'socket.io-client';
import { io } from 'socket.io-client'
import type {
  TypedSocket,
  ClientToServerEvents,
  ServerToClientEvents,
  PlayResult,
} from '~/types'
import { useAuthStore } from '~/store/auth'
import { useGameStore } from '~/store/game'
import { useUiStore } from '~/store/ui'

// Reconnection configuration
const RECONNECTION_DELAY_BASE = 1000 // Start with 1 second
const RECONNECTION_DELAY_MAX = 30000 // Max 30 seconds
const MAX_RECONNECTION_ATTEMPTS = 10

// ============================================================================
// CLIENT-ONLY Singleton State
// These MUST only be accessed/modified on the client side to prevent SSR issues.
// SSR can corrupt module-level state that persists across hydration.
//
// CRITICAL: All module-level state is lazily initialized on first client-side
// access to prevent SSR from creating zombie state that persists into hydration.
// ============================================================================

// Client-only state container - initialized only on client
interface ClientState {
  socketInstance: Socket<ServerToClientEvents, ClientToServerEvents> | null
  reconnectionAttempts: number
  reconnectionTimeout: ReturnType<typeof setTimeout> | null
  connectionTimeoutId: ReturnType<typeof setTimeout> | null
  heartbeatInterval: ReturnType<typeof setInterval> | null
  stuckStateCheckInterval: ReturnType<typeof setInterval> | null
  initialized: boolean
}

// Lazily initialized on client only
let clientState: ClientState | null = null

function getClientState(): ClientState {
  // Only initialize on client
  if (import.meta.client && !clientState) {
    clientState = {
      socketInstance: null,
      reconnectionAttempts: 0,
      reconnectionTimeout: null,
      connectionTimeoutId: null,
      heartbeatInterval: null,
      stuckStateCheckInterval: null,
      initialized: true,
    }
    console.log('[WebSocket] Client state initialized')
  }
  // Return empty state for SSR (won't be used, but prevents errors)
  return clientState || {
    socketInstance: null,
    reconnectionAttempts: 0,
    reconnectionTimeout: null,
    connectionTimeoutId: null,
    heartbeatInterval: null,
    stuckStateCheckInterval: null,
    initialized: false,
  }
}

// Reactive state - using refs that work with SSR but should only be mutated client-side
const isConnected = ref(false)
const isConnecting = ref(false)
const connectionError = ref<string | null>(null)
const lastConnectionAttempt = ref<number | null>(null)
const permanentlyFailed = ref(false) // Set when max reconnection attempts reached

// Reset reactive state on client hydration to ensure clean slate
if (import.meta.client) {
  // Force reset on hydration - this runs once when module loads on client
  isConnected.value = false
  isConnecting.value = false
  connectionError.value = null
  lastConnectionAttempt.value = null
  permanentlyFailed.value = false
  console.log('[WebSocket] Reactive state reset on client hydration')
}

export function useWebSocket() {
  // State is now module-level singleton (above)

  // ============================================================================
  // Configuration (MUST be called during setup, not inside callbacks)
  // ============================================================================

  const config = useRuntimeConfig()
  const wsUrl = config.public.wsUrl

  // ============================================================================
  // Stores
  // ============================================================================

  const authStore = useAuthStore()
  const gameStore = useGameStore()
  const uiStore = useUiStore()

  // ============================================================================
  // Computed
  // ============================================================================

  const socket = computed((): TypedSocket | null => {
    if (!import.meta.client) return null
    const state = getClientState()
    return state.socketInstance as TypedSocket | null
  })

  const canConnect = computed(() => {
    return authStore.isAuthenticated
  })

  const shouldReconnect = computed(() => {
    if (!import.meta.client) return false
    const state = getClientState()
    return (
      !isConnected.value &&
      !permanentlyFailed.value &&
      canConnect.value &&
      state.reconnectionAttempts < MAX_RECONNECTION_ATTEMPTS
    )
  })

  // ============================================================================
  // Connection Management
  // ============================================================================

  // Connection timeout constant
  const CONNECTION_TIMEOUT = 10000 // 10 seconds

  /**
   * Connect to WebSocket server with JWT authentication
   */
  function connect() {
    // SSR guard - never attempt connection on server
    if (!import.meta.client) {
      console.log('[WebSocket] Skipping connect() - running on server')
      return
    }

    const state = getClientState()
    if (!state.initialized) {
      console.warn('[WebSocket] Client state not initialized, skipping connect')
      return
    }

    // REMOVED: Auth guard - let backend authenticate via cookies
    // The auth store state may not sync properly from SSR to client,
    // but cookies ARE sent automatically. Backend will reject if invalid.

    if (isConnected.value || isConnecting.value) {
      console.warn('[WebSocket] Already connected or connecting')
      return
    }

    isConnecting.value = true
    connectionError.value = null
    lastConnectionAttempt.value = Date.now()

    console.log('[WebSocket] Connecting to', wsUrl)

    // Set a timeout to reset isConnecting if no event fires
    // This prevents the "stuck connecting" state that can occur on Safari
    if (state.connectionTimeoutId) {
      clearTimeout(state.connectionTimeoutId)
    }
    state.connectionTimeoutId = setTimeout(() => {
      if (isConnecting.value && !isConnected.value) {
        console.warn('[WebSocket] Connection timeout - resetting state')
        isConnecting.value = false
        connectionError.value = 'Connection timeout - no response from server'
        // Schedule a reconnection attempt
        scheduleReconnection()
      }
    }, CONNECTION_TIMEOUT)

    try {
      // Create or reuse socket instance
      if (!state.socketInstance) {
        console.log('[WebSocket] Creating socket instance with URL:', wsUrl)

        if (!io) {
          console.error('[WebSocket] ERROR: io function is undefined!')
          connectionError.value = 'Socket.io library not loaded'
          isConnecting.value = false
          return
        }

        state.socketInstance = io(wsUrl, {
          withCredentials: true, // Send cookies automatically
          autoConnect: false,
          reconnection: false, // We handle reconnection manually for better control
          transports: ['websocket', 'polling'],
        })

        console.log('[WebSocket] Socket instance created:', !!state.socketInstance)

        setupEventListeners()
      }

      console.log('[WebSocket] Calling socketInstance.connect()')
      state.socketInstance.connect()
    } catch (err) {
      console.error('[WebSocket] Connection error:', err)
      isConnecting.value = false
      connectionError.value = err instanceof Error ? err.message : String(err)
      if (state.connectionTimeoutId) {
        clearTimeout(state.connectionTimeoutId)
        state.connectionTimeoutId = null
      }
    }
  }

  /**
   * Disconnect from WebSocket server
   */
  function disconnect() {
    // SSR guard
    if (!import.meta.client) return

    const state = getClientState()
    console.log('[WebSocket] Disconnecting')

    // Clear reconnection timer
    if (state.reconnectionTimeout) {
      clearTimeout(state.reconnectionTimeout)
      state.reconnectionTimeout = null
    }

    // Disconnect socket
    if (state.socketInstance) {
      state.socketInstance.disconnect()
    }

    isConnected.value = false
    isConnecting.value = false
    connectionError.value = null
    permanentlyFailed.value = false
    state.reconnectionAttempts = 0
  }

  /**
   * Force reconnect - clears the singleton and creates a fresh connection.
   * Use this when the connection is in a bad state and normal reconnection isn't working.
   */
  function forceReconnect() {
    // SSR guard
    if (!import.meta.client) return

    const state = getClientState()
    console.log('[WebSocket] Force reconnecting - clearing singleton')

    // Clear reconnection timer
    if (state.reconnectionTimeout) {
      clearTimeout(state.reconnectionTimeout)
      state.reconnectionTimeout = null
    }

    // Fully disconnect and destroy the socket instance
    if (state.socketInstance) {
      state.socketInstance.removeAllListeners()
      state.socketInstance.disconnect()
      state.socketInstance = null
    }

    // Reset all state
    isConnected.value = false
    isConnecting.value = false
    connectionError.value = null
    permanentlyFailed.value = false
    state.reconnectionAttempts = 0

    // Reconnect after a short delay
    setTimeout(() => {
      connect()
    }, 100)
  }

  /**
   * Manual retry after permanent failure.
   * Resets all connection state and attempts fresh connection.
   * Use this from UI when user clicks "Retry" after max attempts reached.
   */
  function manualRetry() {
    // SSR guard
    if (!import.meta.client) return

    const state = getClientState()
    console.log('[WebSocket] Manual retry requested - resetting failed state')

    // Reset all state
    permanentlyFailed.value = false
    connectionError.value = null
    state.reconnectionAttempts = 0

    // Restart stuck state detection
    startStuckStateDetection()

    // Attempt connection
    forceReconnect()
  }

  /**
   * Reconnect with exponential backoff
   */
  function scheduleReconnection() {
    // SSR guard
    if (!import.meta.client) return

    const state = getClientState()

    // Check if we've hit the max attempts
    if (state.reconnectionAttempts >= MAX_RECONNECTION_ATTEMPTS) {
      console.error(`[WebSocket] Max reconnection attempts (${MAX_RECONNECTION_ATTEMPTS}) reached - giving up`)
      permanentlyFailed.value = true
      connectionError.value = `Connection failed after ${MAX_RECONNECTION_ATTEMPTS} attempts. Click retry to try again.`
      // Stop the stuck state detector since we've given up
      stopStuckStateDetection()
      return
    }

    if (!shouldReconnect.value) {
      console.log('[WebSocket] Reconnection not needed')
      return
    }

    // Calculate delay with exponential backoff
    const delay = Math.min(
      RECONNECTION_DELAY_BASE * Math.pow(2, state.reconnectionAttempts),
      RECONNECTION_DELAY_MAX
    )

    console.log(
      `[WebSocket] Scheduling reconnection attempt ${state.reconnectionAttempts + 1}/${MAX_RECONNECTION_ATTEMPTS} in ${delay}ms`
    )

    state.reconnectionTimeout = setTimeout(() => {
      state.reconnectionAttempts++
      connect()
    }, delay)
  }

  // ============================================================================
  // Event Listeners Setup
  // ============================================================================

  /**
   * Set up all WebSocket event listeners
   */
  function setupEventListeners() {
    const state = getClientState()
    if (!state.socketInstance) return

    // ========================================
    // Connection Events
    // ========================================

    state.socketInstance.on('connect', () => {
      const s = getClientState()
      console.log('[WebSocket] Connected successfully')
      isConnected.value = true
      isConnecting.value = false
      connectionError.value = null
      s.reconnectionAttempts = 0

      // Clear connection timeout
      if (s.connectionTimeoutId) {
        clearTimeout(s.connectionTimeoutId)
        s.connectionTimeoutId = null
      }

      // Update global flag for plugin failsafe
      if (typeof window !== 'undefined') {
        window.__ws_connected = true
      }

      // Update game store
      gameStore.setConnected(true)

      // Show success toast
      uiStore.showSuccess('Connected to game server')
    })

    state.socketInstance.on('disconnect', (reason) => {
      console.log('[WebSocket] Disconnected:', reason)
      isConnected.value = false
      isConnecting.value = false

      // Update global flag for plugin failsafe
      if (typeof window !== 'undefined') {
        window.__ws_connected = false
      }

      // Update game store
      gameStore.setConnected(false)

      // Show warning toast
      uiStore.showWarning('Disconnected from game server')

      // Attempt reconnection if not intentional
      if (reason !== 'io client disconnect') {
        scheduleReconnection()
      }
    })

    state.socketInstance.on('connect_error', (error) => {
      console.error('[WebSocket] Connection error:', error)
      isConnected.value = false
      isConnecting.value = false
      connectionError.value = error.message

      // Update game store
      gameStore.setError(error.message)

      // Show error toast
      uiStore.showError(`Connection failed: ${error.message}`)

      // Attempt reconnection
      scheduleReconnection()
    })

    state.socketInstance.on('connected', (data) => {
      console.log('[WebSocket] Server confirmed connection for user:', data.user_id)
    })

    state.socketInstance.on('game_joined', (data: { game_id: string; role: string }) => {
      console.log('[WebSocket] Successfully joined game:', data.game_id, 'as', data.role)
      uiStore.showSuccess(`Joined game as ${data.role}`)
    })

    state.socketInstance.on('heartbeat_ack', () => {
      // Heartbeat acknowledged - connection is healthy
      // No action needed, just prevents timeout
    })

    // ========================================
    // Game State Events
    // ========================================

    state.socketInstance.on('game_state', (gameState) => {
      console.log('[WebSocket] Full game state received (from request_game_state)')
      gameStore.setGameState(gameState)
    })

    state.socketInstance.on('game_state_update', (gameState) => {
      const batterInfo = gameState.current_batter
        ? `lineup_id=${gameState.current_batter.lineup_id}, batting_order=${gameState.current_batter.batting_order}`
        : 'None'
      console.log('[WebSocket] Game state update received, current_batter:', batterInfo)
      console.log('[WebSocket] Full gameState:', JSON.stringify(gameState, null, 2).slice(0, 500))
      gameStore.setGameState(gameState)
      console.log('[WebSocket] After setGameState, store current_batter:', gameStore.currentBatter)
    })

    state.socketInstance.on('game_state_sync', (data) => {
      console.log('[WebSocket] Full game state sync received')
      gameStore.setGameState(data.state)
      // Replace play history with synced plays (O(1) - no iteration/dedup needed)
      gameStore.setPlayHistory(data.recent_plays)
    })

    state.socketInstance.on('inning_change', (data) => {
      console.log(`[WebSocket] Inning change: ${data.half} ${data.inning}`)
      uiStore.showInfo(`${data.half === 'top' ? 'Top' : 'Bottom'} ${data.inning}`, 3000)
    })

    state.socketInstance.on('game_ended', (data) => {
      console.log('[WebSocket] Game ended:', data.winner_team_id)
      uiStore.showSuccess(
        `Game Over! Final: ${data.final_score.away} - ${data.final_score.home}`,
        10000
      )
    })

    // ========================================
    // Decision Events
    // ========================================

    state.socketInstance.on('decision_required', (prompt) => {
      console.log('[WebSocket] Decision required:', prompt.phase)
      gameStore.setDecisionPrompt(prompt)
    })

    state.socketInstance.on('defensive_decision_submitted', (data) => {
      const s = getClientState()
      console.log('[WebSocket] Defensive decision submitted')
      gameStore.clearDecisionPrompt()

      // Request updated game state to get new decision_phase
      if (s.socketInstance && gameStore.gameId) {
        s.socketInstance.emit('request_game_state', {
          game_id: gameStore.gameId
        })
      }

      if (data.pending_decision) {
        uiStore.showInfo('Defense set. Waiting for offense...', 3000)
      } else {
        uiStore.showSuccess('Defense set. Ready to play!', 3000)
      }
    })

    state.socketInstance.on('offensive_decision_submitted', (data) => {
      const s = getClientState()
      console.log('[WebSocket] Offensive decision submitted')
      gameStore.clearDecisionPrompt()

      // Request updated game state to get new decision_phase
      if (s.socketInstance && gameStore.gameId) {
        s.socketInstance.emit('request_game_state', {
          game_id: gameStore.gameId
        })
      }

      uiStore.showSuccess('Offense set. Ready to play!', 3000)
    })

    // ========================================
    // Manual Workflow Events
    // ========================================

    state.socketInstance.on('dice_rolled', (data) => {
      console.log('[WebSocket] Dice rolled:', data.roll_id)
      gameStore.setPendingRoll({
        roll_id: data.roll_id,
        d6_one: data.d6_one,
        d6_two_a: data.d6_two_a,
        d6_two_b: data.d6_two_b,
        d6_two_total: data.d6_two_total,
        chaos_d20: data.chaos_d20,
        resolution_d20: data.resolution_d20,
        check_wild_pitch: data.check_wild_pitch,
        check_passed_ball: data.check_passed_ball,
        timestamp: data.timestamp,
      })
      uiStore.showInfo(data.message, 5000)
    })

    state.socketInstance.on('outcome_accepted', (data) => {
      console.log('[WebSocket] Outcome accepted:', data.outcome)
      gameStore.clearPendingRoll()
      uiStore.showSuccess('Outcome submitted. Resolving play...', 3000)
    })

    state.socketInstance.on('play_resolved', (data) => {
      console.log('[WebSocket] Play resolved:', data.description)

      // Convert to PlayResult and add to game store
      const playResult: PlayResult = {
        play_number: data.play_number,
        outcome: data.outcome,
        hit_location: data.hit_location,
        description: data.description,
        outs_recorded: data.outs_recorded,
        runs_scored: data.runs_scored,
        batter_result: data.batter_result,
        runners_advanced: data.runners_advanced,
        is_hit: data.is_hit,
        is_out: data.is_out,
        is_walk: data.is_walk,
        roll_id: data.roll_id,
        x_check_details: data.x_check_details,
      }

      gameStore.setLastPlayResult(playResult)
      gameStore.addPlayToHistory(playResult)

      // Clear pending decisions since the play is complete and we'll need new ones for next batter
      gameStore.clearPendingDecisions()

      uiStore.showSuccess(data.description, 5000)
    })

    // ========================================
    // Substitution Events
    // ========================================

    state.socketInstance.on('player_substituted', (data) => {
      const s = getClientState()
      console.log('[WebSocket] Player substituted:', data.type)
      uiStore.showInfo(data.message, 5000)
      // Request updated lineup
      if (s.socketInstance) {
        s.socketInstance.emit('get_lineup', {
          game_id: gameStore.gameId!,
          team_id: data.team_id,
        })
      }
    })

    state.socketInstance.on('substitution_confirmed', (data) => {
      console.log('[WebSocket] Substitution confirmed')
      uiStore.showSuccess(data.message, 5000)
    })

    // ========================================
    // Data Response Events
    // ========================================

    state.socketInstance.on('lineup_data', (data) => {
      console.log('[WebSocket] Lineup data received for team:', data.team_id)
      gameStore.updateLineup(data.team_id, data.players)
    })

    state.socketInstance.on('bench_data', (data) => {
      console.log('[WebSocket] Bench data received for team:', data.team_id, '- players:', data.players.length)
      gameStore.setBench(data.team_id, data.players)
    })

    state.socketInstance.on('box_score_data', (data) => {
      console.log('[WebSocket] Box score data received')
      // Box score will be handled by dedicated component
      // Just log for now
    })

    // ========================================
    // Error Events
    // ========================================

    state.socketInstance.on('error', (data) => {
      console.error('[WebSocket] Server error:', data.message)
      gameStore.setError(data.message)
      uiStore.showError(data.message, 7000)
    })

    state.socketInstance.on('outcome_rejected', (data) => {
      console.error('[WebSocket] Outcome rejected:', data.message)
      uiStore.showError(`Invalid outcome: ${data.message}`, 7000)
    })

    state.socketInstance.on('substitution_error', (data) => {
      console.error('[WebSocket] Substitution error:', data.message)
      uiStore.showError(`Substitution failed: ${data.message}`, 7000)
    })

    state.socketInstance.on('invalid_action', (data) => {
      console.error('[WebSocket] Invalid action:', data.reason)
      uiStore.showError(`Invalid action: ${data.reason}`, 7000)
    })

    state.socketInstance.on('connection_error', (data) => {
      console.error('[WebSocket] Connection error:', data.error)
      connectionError.value = data.error
      uiStore.showError(`Connection error: ${data.error}`, 7000)
    })
  }

  // ============================================================================
  // Heartbeat
  // ============================================================================

  /**
   * Send periodic heartbeat to keep connection alive
   */
  function startHeartbeat() {
    // SSR guard
    if (!import.meta.client) return

    const state = getClientState()
    if (state.heartbeatInterval) return

    state.heartbeatInterval = setInterval(() => {
      const s = getClientState()
      if (s.socketInstance?.connected) {
        s.socketInstance.emit('heartbeat')
      }
    }, 30000) // Every 30 seconds
  }

  function stopHeartbeat() {
    // SSR guard
    if (!import.meta.client) return

    const state = getClientState()
    if (state.heartbeatInterval) {
      clearInterval(state.heartbeatInterval)
      state.heartbeatInterval = null
    }
  }

  // ============================================================================
  // Watchers
  // ============================================================================

  // Watch for auth changes (not immediate - onMounted handles initial state)
  // Only set up watcher on client
  if (import.meta.client) {
    watch(
      () => authStore.isAuthenticated,
      (authenticated, oldValue) => {
        console.log('[WebSocket] Auth changed:', oldValue, '->', authenticated)
        if (authenticated && !isConnected.value && !isConnecting.value) {
          console.log('[WebSocket] Auth became true, connecting...')
          connect()
          startHeartbeat()
        } else if (!authenticated && isConnected.value) {
          disconnect()
          stopHeartbeat()
        }
      }
    )
  }

  // ============================================================================
  // Stuck State Detection
  // ============================================================================

  // Detects when auth is true but connection hasn't happened (race condition fallback)
  function startStuckStateDetection() {
    // SSR guard
    if (!import.meta.client) return

    const state = getClientState()
    if (state.stuckStateCheckInterval) return

    state.stuckStateCheckInterval = setInterval(() => {
      // Don't attempt if we've permanently failed - user must manually retry
      if (permanentlyFailed.value) {
        console.log('[WebSocket] Stuck state check skipped - permanently failed')
        return
      }

      // Try to connect if not connected - don't check auth state
      // Backend will authenticate via cookies
      if (!isConnected.value && !isConnecting.value) {
        console.warn('[WebSocket] STUCK STATE DETECTED: Not connected. Attempting connect...')
        connect()
        startHeartbeat()
      }
    }, 5000) // Check every 5 seconds
  }

  function stopStuckStateDetection() {
    // SSR guard
    if (!import.meta.client) return

    const state = getClientState()
    if (state.stuckStateCheckInterval) {
      clearInterval(state.stuckStateCheckInterval)
      state.stuckStateCheckInterval = null
    }
  }

  // ============================================================================
  // Lifecycle
  // ============================================================================

  // Initial connection on client-side mount (handles SSR hydration case)
  onMounted(async () => {
    console.log('[WebSocket] onMounted - isAuthenticated:', authStore.isAuthenticated)

    // Start stuck state detection to catch race conditions
    startStuckStateDetection()

    // If not authenticated, try to check auth first (handles SSR hydration case)
    if (!authStore.isAuthenticated) {
      console.log('[WebSocket] Not authenticated on mount, checking auth...')
      await authStore.checkAuth()
      console.log('[WebSocket] After checkAuth - isAuthenticated:', authStore.isAuthenticated)
    }

    if (authStore.isAuthenticated && !isConnected.value && !isConnecting.value) {
      console.log('[WebSocket] Auto-connecting on mount (authenticated)')
      connect()
      startHeartbeat()
    }
  })

  onUnmounted(() => {
    console.log('[WebSocket] Component unmounted, cleaning up')
    stopHeartbeat()
    stopStuckStateDetection()
    // Don't disconnect on unmount - keep connection alive for app
    // Only disconnect when explicitly requested or user logs out
  })

  // ============================================================================
  // Public API
  // ============================================================================

  return {
    // State
    socket,
    isConnected: readonly(isConnected),
    isConnecting: readonly(isConnecting),
    connectionError: readonly(connectionError),
    permanentlyFailed: readonly(permanentlyFailed),
    canConnect,

    // Actions
    connect,
    disconnect,
    forceReconnect,
    manualRetry,
  }
}
