/**
 * Game Actions Composable Tests
 *
 * Tests for type-safe game action emitters with validation and error handling.
 */

// IMPORTANT: Mock process.env BEFORE any other imports to fix Pinia
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { ref, computed } from 'vue'
import { useGameActions } from '~/composables/useGameActions'
import type { DefensiveDecision, OffensiveDecision } from '~/types'

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

// Mock composables
vi.mock('~/composables/useWebSocket', () => ({
  useWebSocket: vi.fn(),
}))

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

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

describe('useGameActions', () => {
  let mockSocket: any
  let mockGameStore: any
  let mockUiStore: any

  beforeEach(async () => {
    // Mock socket with emit function
    mockSocket = {
      value: {
        emit: vi.fn(),
      },
    }

    // Mock game store
    mockGameStore = {
      gameId: 'game-123',
      canRollDice: true,
      canSubmitOutcome: true,
      resetGame: vi.fn(),
    }

    // Mock UI store
    mockUiStore = {
      showError: vi.fn(),
      showInfo: vi.fn(),
      showWarning: vi.fn(),
    }

    // Set up mocks
    const { useWebSocket } = await import('~/composables/useWebSocket')
    const { useGameStore } = await import('~/store/game')
    const { useUiStore } = await import('~/store/ui')

    vi.mocked(useWebSocket).mockReturnValue({
      socket: mockSocket,
      isConnected: ref(true),
      isConnecting: ref(false),
      connectionError: ref(null),
      canConnect: computed(() => true),
      connect: vi.fn(),
      disconnect: vi.fn(),
    })

    vi.mocked(useGameStore).mockReturnValue(mockGameStore)
    vi.mocked(useUiStore).mockReturnValue(mockUiStore)
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  // TODO: Fix require() module import issues
  describe.skip('validation', () => {
    it('validates connection before emitting', () => {
      const { useWebSocket } = require('~/composables/useWebSocket')
      vi.mocked(useWebSocket).mockReturnValue({
        socket: mockSocket,
        isConnected: ref(false), // Not connected
        isConnecting: ref(false),
        connectionError: ref(null),
        canConnect: computed(() => false),
        connect: vi.fn(),
        disconnect: vi.fn(),
      })

      const actions = useGameActions()
      actions.joinGame()

      expect(mockSocket.value.emit).not.toHaveBeenCalled()
      expect(mockUiStore.showError).toHaveBeenCalledWith('Not connected to game server')
    })

    it('validates socket exists before emitting', () => {
      const { useWebSocket } = require('~/composables/useWebSocket')
      vi.mocked(useWebSocket).mockReturnValue({
        socket: ref(null), // No socket
        isConnected: ref(true),
        isConnecting: ref(false),
        connectionError: ref(null),
        canConnect: computed(() => true),
        connect: vi.fn(),
        disconnect: vi.fn(),
      })

      const actions = useGameActions()
      actions.joinGame()

      expect(mockUiStore.showError).toHaveBeenCalledWith('WebSocket not initialized')
    })

    it('validates gameId exists before emitting', () => {
      mockGameStore.gameId = null // No game ID

      const actions = useGameActions()
      actions.joinGame()

      expect(mockSocket.value.emit).not.toHaveBeenCalled()
      expect(mockUiStore.showError).toHaveBeenCalledWith('No active game')
    })

    it('validates canRollDice before rolling dice', () => {
      mockGameStore.canRollDice = false

      const actions = useGameActions()
      actions.rollDice()

      expect(mockSocket.value.emit).not.toHaveBeenCalled()
      expect(mockUiStore.showWarning).toHaveBeenCalledWith('Cannot roll dice at this time')
    })

    it('validates canSubmitOutcome before submitting outcome', () => {
      mockGameStore.canSubmitOutcome = false

      const actions = useGameActions()
      actions.submitManualOutcome('SINGLE_1')

      expect(mockSocket.value.emit).not.toHaveBeenCalled()
      expect(mockUiStore.showWarning).toHaveBeenCalledWith('Must roll dice first')
    })
  })

  // TODO: Fix graceful handling test
  describe.skip('connection actions', () => {
    it('emits join_game with correct parameters', () => {
      const actions = useGameActions()
      actions.joinGame('player')

      expect(mockSocket.value.emit).toHaveBeenCalledWith('join_game', {
        game_id: 'game-123',
        role: 'player',
      })
    })

    it('emits join_game as spectator', () => {
      const actions = useGameActions()
      actions.joinGame('spectator')

      expect(mockSocket.value.emit).toHaveBeenCalledWith('join_game', {
        game_id: 'game-123',
        role: 'spectator',
      })
    })

    it('emits leave_game and resets game store', () => {
      const actions = useGameActions()
      actions.leaveGame()

      expect(mockSocket.value.emit).toHaveBeenCalledWith('leave_game', {
        game_id: 'game-123',
      })
      expect(mockGameStore.resetGame).toHaveBeenCalled()
    })

    it('handles leave_game when not connected gracefully', () => {
      const { useWebSocket } = require('~/composables/useWebSocket')
      vi.mocked(useWebSocket).mockReturnValue({
        socket: ref(null),
        isConnected: ref(false),
        isConnecting: ref(false),
        connectionError: ref(null),
        canConnect: computed(() => false),
        connect: vi.fn(),
        disconnect: vi.fn(),
      })

      const actions = useGameActions()

      // Should not crash
      expect(() => actions.leaveGame()).not.toThrow()
    })
  })

  describe('strategic decision actions', () => {
    it('emits defensive decision with correct parameters', () => {
      const decision: DefensiveDecision = {
        infield_depth: 'normal',
        outfield_depth: 'normal',
        hold_runners: [],
      }

      const actions = useGameActions()
      actions.submitDefensiveDecision(decision)

      expect(mockSocket.value.emit).toHaveBeenCalledWith('submit_defensive_decision', {
        game_id: 'game-123',
        infield_depth: 'normal',
        outfield_depth: 'normal',
        hold_runners: [],
      })
    })

    it('emits offensive decision with correct parameters', () => {
      const decision: OffensiveDecision = {
        action: 'steal',
        steal_attempts: [2],
      }

      const actions = useGameActions()
      actions.submitOffensiveDecision(decision)

      expect(mockSocket.value.emit).toHaveBeenCalledWith('submit_offensive_decision', {
        game_id: 'game-123',
        action: 'steal',
        steal_attempts: [2],
      })
    })
  })

  describe('manual outcome workflow', () => {
    it('emits roll_dice and shows info toast', () => {
      const actions = useGameActions()
      actions.rollDice()

      expect(mockSocket.value.emit).toHaveBeenCalledWith('roll_dice', {
        game_id: 'game-123',
      })
      expect(mockUiStore.showInfo).toHaveBeenCalledWith('Rolling dice...', 2000)
    })

    it('emits submit_manual_outcome with outcome only', () => {
      const actions = useGameActions()
      actions.submitManualOutcome('STRIKEOUT')

      expect(mockSocket.value.emit).toHaveBeenCalledWith('submit_manual_outcome', {
        game_id: 'game-123',
        outcome: 'STRIKEOUT',
        hit_location: undefined,
      })
    })

    it('emits submit_manual_outcome with outcome and hit location', () => {
      const actions = useGameActions()
      actions.submitManualOutcome('SINGLE_1', '8')

      expect(mockSocket.value.emit).toHaveBeenCalledWith('submit_manual_outcome', {
        game_id: 'game-123',
        outcome: 'SINGLE_1',
        hit_location: '8',
      })
    })
  })

  describe('substitution actions', () => {
    it('submitSubstitution routes pinch_hitter to correct emit', () => {
      const actions = useGameActions()
      actions.submitSubstitution('pinch_hitter', 10, 20, 1)

      expect(mockSocket.value.emit).toHaveBeenCalledWith('request_pinch_hitter', {
        game_id: 'game-123',
        player_out_lineup_id: 10,
        player_in_card_id: 20,
        team_id: 1,
      })
      expect(mockUiStore.showInfo).toHaveBeenCalledWith('Requesting pinch hitter...', 3000)
    })

    it('submitSubstitution routes defensive_replacement to correct emit', () => {
      const actions = useGameActions()
      actions.submitSubstitution('defensive_replacement', 10, 20, 1, 'SS')

      expect(mockSocket.value.emit).toHaveBeenCalledWith('request_defensive_replacement', {
        game_id: 'game-123',
        player_out_lineup_id: 10,
        player_in_card_id: 20,
        new_position: 'SS',
        team_id: 1,
      })
      expect(mockUiStore.showInfo).toHaveBeenCalledWith('Requesting defensive replacement...', 3000)
    })

    it('submitSubstitution shows error when defensive_replacement missing position', () => {
      const actions = useGameActions()
      actions.submitSubstitution('defensive_replacement', 10, 20, 1)

      expect(mockSocket.value.emit).not.toHaveBeenCalled()
      expect(mockUiStore.showError).toHaveBeenCalledWith('Position required for defensive replacement')
    })

    it('submitSubstitution routes pitching_change to correct emit', () => {
      const actions = useGameActions()
      actions.submitSubstitution('pitching_change', 10, 20, 1)

      expect(mockSocket.value.emit).toHaveBeenCalledWith('request_pitching_change', {
        game_id: 'game-123',
        player_out_lineup_id: 10,
        player_in_card_id: 20,
        team_id: 1,
      })
      expect(mockUiStore.showInfo).toHaveBeenCalledWith('Requesting pitching change...', 3000)
    })
  })

  describe('data request actions', () => {
    it('emits get_lineup with correct team ID', () => {
      const actions = useGameActions()
      actions.getLineup(1)

      expect(mockSocket.value.emit).toHaveBeenCalledWith('get_lineup', {
        game_id: 'game-123',
        team_id: 1,
      })
    })

    it('emits get_box_score request', () => {
      const actions = useGameActions()
      actions.getBoxScore()

      expect(mockSocket.value.emit).toHaveBeenCalledWith('get_box_score', {
        game_id: 'game-123',
      })
    })

    it('emits request_game_state and shows info', () => {
      const actions = useGameActions()
      actions.requestGameState()

      expect(mockSocket.value.emit).toHaveBeenCalledWith('request_game_state', {
        game_id: 'game-123',
      })
      expect(mockUiStore.showInfo).toHaveBeenCalledWith('Syncing game state...', 3000)
    })
  })

  describe('gameId override', () => {
    it('uses provided gameId instead of store gameId', () => {
      const actions = useGameActions('override-game-456')
      actions.joinGame()

      expect(mockSocket.value.emit).toHaveBeenCalledWith('join_game', {
        game_id: 'override-game-456',
        role: 'player',
      })
    })

    it('falls back to store gameId when not provided', () => {
      const actions = useGameActions()
      actions.joinGame()

      expect(mockSocket.value.emit).toHaveBeenCalledWith('join_game', {
        game_id: 'game-123', // From store
        role: 'player',
      })
    })
  })

  // TODO: Fix require() module import in error message test
  describe.skip('error handling', () => {
    it('does not emit when validation fails', () => {
      mockGameStore.gameId = null

      const actions = useGameActions()
      actions.rollDice()
      actions.submitDefensiveDecision({
        infield_depth: 'normal',
        outfield_depth: 'normal',
        hold_runners: [],
      })
      actions.getLineup(1)

      // Should not have emitted any events
      expect(mockSocket.value.emit).not.toHaveBeenCalled()
      // Should have shown errors
      expect(mockUiStore.showError).toHaveBeenCalledTimes(3)
    })

    it('shows appropriate error messages for each validation failure', () => {
      const { useWebSocket } = require('~/composables/useWebSocket')

      // Test not connected
      vi.mocked(useWebSocket).mockReturnValue({
        socket: mockSocket,
        isConnected: ref(false),
        isConnecting: ref(false),
        connectionError: ref(null),
        canConnect: computed(() => false),
        connect: vi.fn(),
        disconnect: vi.fn(),
      })
      const actions1 = useGameActions()
      actions1.joinGame()
      expect(mockUiStore.showError).toHaveBeenCalledWith('Not connected to game server')

      vi.clearAllMocks()

      // Test no socket
      vi.mocked(useWebSocket).mockReturnValue({
        socket: ref(null),
        isConnected: ref(true),
        isConnecting: ref(false),
        connectionError: ref(null),
        canConnect: computed(() => true),
        connect: vi.fn(),
        disconnect: vi.fn(),
      })
      const actions2 = useGameActions()
      actions2.joinGame()
      expect(mockUiStore.showError).toHaveBeenCalledWith('WebSocket not initialized')

      vi.clearAllMocks()

      // Test no gameId
      vi.mocked(useWebSocket).mockReturnValue({
        socket: mockSocket,
        isConnected: ref(true),
        isConnecting: ref(false),
        connectionError: ref(null),
        canConnect: computed(() => true),
        connect: vi.fn(),
        disconnect: vi.fn(),
      })
      mockGameStore.gameId = null
      const actions3 = useGameActions()
      actions3.joinGame()
      expect(mockUiStore.showError).toHaveBeenCalledWith('No active game')
    })
  })
})
