/**
 * WebSocket Composable Tests
 *
 * Tests for Socket.io connection management, authentication, and event handling.
 */

// IMPORTANT: Mock process.env BEFORE any other imports to fix Pinia
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { ref } from 'vue'
import { setActivePinia, createPinia } from 'pinia'
import { useWebSocket } from '~/composables/useWebSocket'

;(globalThis as any).process = {
  ...((globalThis as any).process || {}),
  env: {
    ...((globalThis as any).process?.env || {}),
    NODE_ENV: 'test',
  },
  client: true,
}

// Mock Socket.io
const mockSocketInstance = {
  on: vi.fn(),
  emit: vi.fn(),
  connect: vi.fn(),
  disconnect: vi.fn(),
  connected: false,
  auth: {},
}

vi.mock('socket.io-client', () => ({
  io: vi.fn(() => mockSocketInstance),
}))

// Mock Nuxt runtime config
vi.mock('#app', () => ({
  useRuntimeConfig: vi.fn(() => ({
    public: {
      wsUrl: 'http://localhost:8000',
    },
  })),
}))

// Mock stores
const mockAuthStore = {
  isAuthenticated: ref(true),
  isTokenValid: ref(true),
  token: 'test-jwt-token',
}

const mockGameStore = {
  setConnected: vi.fn(),
  setGameState: vi.fn(),
  addPlayToHistory: vi.fn(),
  setDecisionPrompt: vi.fn(),
  clearDecisionPrompt: vi.fn(),
  setPendingRoll: vi.fn(),
  clearPendingRoll: vi.fn(),
  updateLineup: vi.fn(),
  setError: vi.fn(),
  gameId: 'game-123',
}

const mockUiStore = {
  showSuccess: vi.fn(),
  showError: vi.fn(),
  showWarning: vi.fn(),
  showInfo: vi.fn(),
}

vi.mock('~/store/auth', () => ({
  useAuthStore: vi.fn(() => mockAuthStore),
}))

vi.mock('~/store/game', () => ({
  useGameStore: vi.fn(() => mockGameStore),
}))

vi.mock('~/store/ui', () => ({
  useUiStore: vi.fn(() => mockUiStore),
}))

// TODO: Fix import.meta.client issues for WebSocket operations in tests
describe.skip('useWebSocket', () => {
  beforeEach(() => {
    // Ensure process.env exists before creating Pinia
    if (!process.env) {
      (process as any).env = {}
    }
    process.env.NODE_ENV = 'test'

    setActivePinia(createPinia())

    // Reset all mocks
    vi.clearAllMocks()

    // Reset mock socket state
    mockSocketInstance.connected = false
    mockSocketInstance.on.mockClear()
    mockSocketInstance.emit.mockClear()
    mockSocketInstance.connect.mockClear()
    mockSocketInstance.disconnect.mockClear()

    // Reset auth store state
    mockAuthStore.isAuthenticated.value = true
    mockAuthStore.isTokenValid.value = true

    // Use fake timers for testing timeouts
    vi.useFakeTimers()
  })

  afterEach(() => {
    vi.restoreAllMocks()
    vi.useRealTimers()
  })

  describe('initialization', () => {
    it('initializes with disconnected state', () => {
      const ws = useWebSocket()

      expect(ws.isConnected.value).toBe(false)
      expect(ws.isConnecting.value).toBe(false)
      expect(ws.connectionError.value).toBeNull()
      expect(ws.socket.value).toBeNull()
    })

    it('computes canConnect based on auth state', () => {
      const { useAuthStore } = require('~/store/auth')

      vi.mocked(useAuthStore).mockReturnValue({
        ...mockAuthStore,
        isAuthenticated: ref(true),
        isTokenValid: ref(true),
      })

      const ws = useWebSocket()

      // canConnect is a ComputedRef, so use .value
      expect(ws.canConnect.value).toBe(true)
    })

    it('cannot connect when not authenticated', () => {
      const { useAuthStore } = require('~/store/auth')

      vi.mocked(useAuthStore).mockReturnValue({
        ...mockAuthStore,
        isAuthenticated: ref(false),
        isTokenValid: ref(true),
      })

      const ws = useWebSocket()

      expect(ws.canConnect.value).toBe(false)
    })

    it('cannot connect when token is invalid', () => {
      const { useAuthStore } = require('~/store/auth')

      vi.mocked(useAuthStore).mockReturnValue({
        ...mockAuthStore,
        isAuthenticated: ref(true),
        isTokenValid: ref(false),
      })

      const ws = useWebSocket()

      expect(ws.canConnect.value).toBe(false)
    })
  })

  describe('connection lifecycle', () => {
    it('connects with JWT authentication', () => {
      const { io } = require('socket.io-client')
      const ws = useWebSocket()

      ws.connect()

      expect(io).toHaveBeenCalledWith('http://localhost:8000', {
        auth: {
          token: 'test-jwt-token',
        },
        autoConnect: false,
        reconnection: false,
        transports: ['websocket', 'polling'],
      })
      expect(mockSocketInstance.connect).toHaveBeenCalled()
      expect(ws.isConnecting.value).toBe(true)
    })

    it('does not connect when not authenticated', () => {
      mockAuthStore.isAuthenticated.value = false

      const ws = useWebSocket()
      ws.connect()

      expect(mockSocketInstance.connect).not.toHaveBeenCalled()
      expect(ws.isConnecting.value).toBe(false)
    })

    it('does not connect when token is invalid', () => {
      mockAuthStore.isTokenValid.value = false

      const ws = useWebSocket()
      ws.connect()

      expect(mockSocketInstance.connect).not.toHaveBeenCalled()
    })

    it('does not connect when already connected', () => {
      const ws = useWebSocket()

      ws.connect()
      mockSocketInstance.connect.mockClear()

      // Simulate connected state
      ws.isConnected.value = true

      ws.connect()

      expect(mockSocketInstance.connect).not.toHaveBeenCalled()
    })

    it('updates state on successful connection', () => {
      const ws = useWebSocket()
      ws.connect()

      // Simulate 'connect' event
      const connectHandler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'connect'
      )?.[1]
      expect(connectHandler).toBeDefined()

      connectHandler?.()

      expect(ws.isConnected.value).toBe(true)
      expect(ws.isConnecting.value).toBe(false)
      expect(ws.connectionError.value).toBeNull()
      expect(mockGameStore.setConnected).toHaveBeenCalledWith(true)
      expect(mockUiStore.showSuccess).toHaveBeenCalledWith('Connected to game server')
    })

    it('disconnects and clears state', () => {
      const ws = useWebSocket()

      ws.connect()

      // Simulate connected
      const connectHandler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'connect'
      )?.[1]
      connectHandler?.()

      expect(ws.isConnected.value).toBe(true)

      // Disconnect
      ws.disconnect()

      expect(mockSocketInstance.disconnect).toHaveBeenCalled()
      expect(ws.isConnected.value).toBe(false)
      expect(ws.isConnecting.value).toBe(false)
    })

    it('handles disconnection event', () => {
      const ws = useWebSocket()
      ws.connect()

      // Simulate 'disconnect' event
      const disconnectHandler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'disconnect'
      )?.[1]
      expect(disconnectHandler).toBeDefined()

      disconnectHandler?.('transport close')

      expect(ws.isConnected.value).toBe(false)
      expect(mockGameStore.setConnected).toHaveBeenCalledWith(false)
      expect(mockUiStore.showWarning).toHaveBeenCalledWith('Disconnected from game server')
    })

    it('handles connection error', () => {
      const ws = useWebSocket()
      ws.connect()

      // Simulate 'connect_error' event
      const errorHandler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'connect_error'
      )?.[1]
      expect(errorHandler).toBeDefined()

      const error = new Error('Connection failed')
      errorHandler?.(error)

      expect(ws.isConnected.value).toBe(false)
      expect(ws.isConnecting.value).toBe(false)
      expect(ws.connectionError.value).toBe('Connection failed')
      expect(mockGameStore.setError).toHaveBeenCalledWith('Connection failed')
      expect(mockUiStore.showError).toHaveBeenCalledWith('Connection failed: Connection failed')
    })
  })

  describe('exponential backoff reconnection', () => {
    it('calculates exponential backoff delay correctly', () => {
      const ws = useWebSocket()
      ws.connect()

      // Get disconnect handler
      const disconnectHandler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'disconnect'
      )?.[1]

      // Trigger disconnect (not intentional)
      disconnectHandler?.('transport close')

      // Should schedule reconnection
      expect(setTimeout).toHaveBeenCalled()

      // First attempt should be 1000ms (2^0 * 1000)
      const firstCall = vi.mocked(setTimeout).mock.calls[0]
      expect(firstCall[1]).toBe(1000)
    })

    it('increases delay with each failed attempt', () => {
      const ws = useWebSocket()

      // Simulate multiple connection failures
      for (let i = 0; i < 3; i++) {
        ws.connect()

        const errorHandler = mockSocketInstance.on.mock.calls.find(
          ([event]) => event === 'connect_error'
        )?.[1]

        errorHandler?.(new Error('Connection failed'))

        vi.advanceTimersByTime(100000) // Fast-forward timers
      }

      // Check that delays increase exponentially
      const timeoutCalls = vi.mocked(setTimeout).mock.calls
      const delays = timeoutCalls.map((call) => call[1]).filter((delay) => delay !== 30000) // Filter out heartbeat

      // First 3 attempts: 1000ms, 2000ms, 4000ms
      expect(delays).toContain(1000)
      expect(delays).toContain(2000)
      expect(delays).toContain(4000)
    })

    it('caps reconnection delay at maximum', () => {
      const ws = useWebSocket()

      // Simulate many connection failures to exceed max delay
      for (let i = 0; i < 10; i++) {
        ws.connect()

        const errorHandler = mockSocketInstance.on.mock.calls.find(
          ([event]) => event === 'connect_error'
        )?.[1]

        errorHandler?.(new Error('Connection failed'))

        vi.advanceTimersByTime(100000)
      }

      // Check that delay never exceeds 30000ms
      const timeoutCalls = vi.mocked(setTimeout).mock.calls
      const delays = timeoutCalls.map((call) => call[1])

      // All delays should be <= 30000ms
      delays.forEach((delay) => {
        expect(delay).toBeLessThanOrEqual(30000)
      })
    })

    it('does not reconnect on intentional disconnect', () => {
      const ws = useWebSocket()
      ws.connect()

      const disconnectHandler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'disconnect'
      )?.[1]

      vi.clearAllTimers()
      const timeoutsBefore = vi.getTimerCount()

      // Trigger intentional disconnect
      disconnectHandler?.('io client disconnect')

      const timeoutsAfter = vi.getTimerCount()

      // Should not schedule reconnection
      expect(timeoutsAfter).toBe(timeoutsBefore)
    })

    it('stops reconnecting after max attempts', () => {
      const ws = useWebSocket()

      // Simulate 10 failed attempts (max is 10)
      for (let i = 0; i < 11; i++) {
        ws.connect()

        const errorHandler = mockSocketInstance.on.mock.calls.find(
          ([event]) => event === 'connect_error'
        )?.[1]

        errorHandler?.(new Error('Connection failed'))

        vi.advanceTimersByTime(100000)
      }

      // After 10 attempts, should not schedule more reconnections
      // This is tested implicitly - the 11th attempt should not schedule a timeout
    })

    it('resets reconnection attempts on successful connection', () => {
      const ws = useWebSocket()

      // Fail once
      ws.connect()
      const errorHandler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'connect_error'
      )?.[1]
      errorHandler?.(new Error('Connection failed'))

      // Then succeed
      const connectHandler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'connect'
      )?.[1]
      connectHandler?.()

      expect(ws.isConnected.value).toBe(true)

      // Reconnection attempts should be reset to 0
      // (tested implicitly through state)
    })
  })

  describe('event handler registration', () => {
    it('registers connection event handlers', () => {
      const ws = useWebSocket()
      ws.connect()

      const registeredEvents = mockSocketInstance.on.mock.calls.map(([event]) => event)

      expect(registeredEvents).toContain('connect')
      expect(registeredEvents).toContain('disconnect')
      expect(registeredEvents).toContain('connect_error')
      expect(registeredEvents).toContain('connected')
      expect(registeredEvents).toContain('heartbeat_ack')
    })

    it('registers game state event handlers', () => {
      const ws = useWebSocket()
      ws.connect()

      const registeredEvents = mockSocketInstance.on.mock.calls.map(([event]) => event)

      expect(registeredEvents).toContain('game_state_update')
      expect(registeredEvents).toContain('game_state_sync')
      expect(registeredEvents).toContain('play_completed')
      expect(registeredEvents).toContain('inning_change')
      expect(registeredEvents).toContain('game_ended')
    })

    it('registers decision event handlers', () => {
      const ws = useWebSocket()
      ws.connect()

      const registeredEvents = mockSocketInstance.on.mock.calls.map(([event]) => event)

      expect(registeredEvents).toContain('decision_required')
      expect(registeredEvents).toContain('defensive_decision_submitted')
      expect(registeredEvents).toContain('offensive_decision_submitted')
    })

    it('registers manual workflow event handlers', () => {
      const ws = useWebSocket()
      ws.connect()

      const registeredEvents = mockSocketInstance.on.mock.calls.map(([event]) => event)

      expect(registeredEvents).toContain('dice_rolled')
      expect(registeredEvents).toContain('outcome_accepted')
    })

    it('registers error event handlers', () => {
      const ws = useWebSocket()
      ws.connect()

      const registeredEvents = mockSocketInstance.on.mock.calls.map(([event]) => event)

      expect(registeredEvents).toContain('error')
      expect(registeredEvents).toContain('outcome_rejected')
      expect(registeredEvents).toContain('substitution_error')
      expect(registeredEvents).toContain('invalid_action')
      expect(registeredEvents).toContain('connection_error')
    })
  })

  describe('game state event handling', () => {
    it('handles game_state_update event', () => {
      const ws = useWebSocket()
      ws.connect()

      const handler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'game_state_update'
      )?.[1]

      const mockState = { game_id: 'game-123', inning: 5 }
      handler?.(mockState)

      expect(mockGameStore.setGameState).toHaveBeenCalledWith(mockState)
    })

    it('handles game_state_sync event with recent plays', () => {
      const ws = useWebSocket()
      ws.connect()

      const handler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'game_state_sync'
      )?.[1]

      const mockData = {
        state: { game_id: 'game-123', inning: 3 },
        recent_plays: [
          { play_number: 1, outcome: 'SINGLE_1', description: 'Single' },
          { play_number: 2, outcome: 'OUT', description: 'Out' },
        ],
      }

      handler?.(mockData)

      expect(mockGameStore.setGameState).toHaveBeenCalledWith(mockData.state)
      expect(mockGameStore.addPlayToHistory).toHaveBeenCalledTimes(2)
    })

    it('handles play_completed event', () => {
      const ws = useWebSocket()
      ws.connect()

      const handler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'play_completed'
      )?.[1]

      const mockPlay = {
        play_number: 1,
        outcome: 'SINGLE_1',
        description: 'Single to center',
      }

      handler?.(mockPlay)

      expect(mockGameStore.addPlayToHistory).toHaveBeenCalledWith(mockPlay)
      expect(mockUiStore.showInfo).toHaveBeenCalledWith('Single to center', 3000)
    })

    it('handles decision_required event', () => {
      const ws = useWebSocket()
      ws.connect()

      const handler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'decision_required'
      )?.[1]

      const mockPrompt = {
        phase: 'defense',
        role: 'home',
        timeout_seconds: 30,
      }

      handler?.(mockPrompt)

      expect(mockGameStore.setDecisionPrompt).toHaveBeenCalledWith(mockPrompt)
    })

    it('handles dice_rolled event', () => {
      const ws = useWebSocket()
      ws.connect()

      const handler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'dice_rolled'
      )?.[1]

      const mockRollData = {
        roll_id: 'roll-123',
        d6_one: 4,
        d6_two_total: 7,
        chaos_d20: 15,
        resolution_d20: 12,
        check_wild_pitch: false,
        check_passed_ball: false,
        timestamp: '2025-01-10T12:00:00Z',
        message: 'Dice rolled!',
      }

      handler?.(mockRollData)

      expect(mockGameStore.setPendingRoll).toHaveBeenCalledWith(
        expect.objectContaining({
          roll_id: 'roll-123',
          d6_one: 4,
          d6_two_total: 7,
          chaos_d20: 15,
          resolution_d20: 12,
        })
      )
      expect(mockUiStore.showInfo).toHaveBeenCalledWith('Dice rolled!', 5000)
    })
  })

  describe('error event handling', () => {
    it('handles server error event', () => {
      const ws = useWebSocket()
      ws.connect()

      const handler = mockSocketInstance.on.mock.calls.find(([event]) => event === 'error')?.[1]

      handler?.({ message: 'Server error occurred' })

      expect(mockGameStore.setError).toHaveBeenCalledWith('Server error occurred')
      expect(mockUiStore.showError).toHaveBeenCalledWith('Server error occurred', 7000)
    })

    it('handles outcome_rejected event', () => {
      const ws = useWebSocket()
      ws.connect()

      const handler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'outcome_rejected'
      )?.[1]

      handler?.({ message: 'Invalid outcome' })

      expect(mockUiStore.showError).toHaveBeenCalledWith('Invalid outcome: Invalid outcome', 7000)
    })

    it('handles invalid_action event', () => {
      const ws = useWebSocket()
      ws.connect()

      const handler = mockSocketInstance.on.mock.calls.find(
        ([event]) => event === 'invalid_action'
      )?.[1]

      handler?.({ reason: 'Not your turn' })

      expect(mockUiStore.showError).toHaveBeenCalledWith('Invalid action: Not your turn', 7000)
    })
  })

  describe('JWT token update on reconnection', () => {
    it('updates auth token when reconnecting', () => {
      const { io } = require('socket.io-client')
      const ws = useWebSocket()

      // First connection
      ws.connect()

      // Change token
      mockAuthStore.token = 'new-jwt-token'

      // Reconnect (socket instance exists)
      ws.connect()

      // Should update auth token
      expect(mockSocketInstance.auth).toEqual({
        token: 'new-jwt-token',
      })
    })

    it('creates new socket with auth on first connection', () => {
      const { io } = require('socket.io-client')
      const ws = useWebSocket()

      ws.connect()

      expect(io).toHaveBeenCalledWith(
        'http://localhost:8000',
        expect.objectContaining({
          auth: {
            token: 'test-jwt-token',
          },
        })
      )
    })
  })
})
