import { expect, test } from 'playwright/test'

test('enter room, ready, start game, ding que, and discard tile', async ({ page }) => {
  let createdRoom: Record<string, unknown> | null = null

  await page.addInitScript(() => {
    localStorage.setItem(
      'mahjong_auth',
      JSON.stringify({
        token: 'mock-access-token',
        tokenType: 'Bearer',
        refreshToken: 'mock-refresh-token',
        user: {
          id: 'u-e2e-1',
          username: '测试玩家',
        },
      }),
    )

    type Tile = { id: number; suit: 'W' | 'T' | 'B'; value: number }
    type Player = {
      index: number
      player_id: string
      player_name: string
      ready: boolean
      missing_suit?: string | null
    }

    const state: {
      roomId: string
      status: 'waiting' | 'playing'
      dealerIndex: number
      currentTurn: number
      selfId: string
      players: Player[]
      hand: Tile[]
      missingSuit: string | null
    } = {
      roomId: 'room-e2e-001',
      status: 'waiting',
      dealerIndex: 0,
      currentTurn: 0,
      selfId: 'u-e2e-1',
      players: [
        { index: 0, player_id: 'u-e2e-1', player_name: '测试玩家', ready: false, missing_suit: null },
        { index: 1, player_id: 'bot-2', player_name: '机器人二号', ready: true, missing_suit: null },
        { index: 2, player_id: 'bot-3', player_name: '机器人三号', ready: true, missing_suit: null },
        { index: 3, player_id: 'bot-4', player_name: '机器人四号', ready: true, missing_suit: null },
      ],
      hand: [
        { id: 11, suit: 'W', value: 1 },
        { id: 12, suit: 'W', value: 2 },
        { id: 13, suit: 'W', value: 3 },
        { id: 21, suit: 'T', value: 4 },
        { id: 22, suit: 'T', value: 5 },
        { id: 23, suit: 'T', value: 6 },
        { id: 31, suit: 'B', value: 1 },
        { id: 32, suit: 'B', value: 2 },
        { id: 33, suit: 'B', value: 3 },
        { id: 34, suit: 'B', value: 4 },
        { id: 35, suit: 'B', value: 5 },
        { id: 36, suit: 'B', value: 6 },
        { id: 37, suit: 'B', value: 7 },
      ],
      missingSuit: null,
    }

    const clone = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T

    const buildRoomPayload = () => ({
      room: {
        room_id: state.roomId,
        name: 'E2E 测试房间',
        game_type: 'chengdu',
        owner_id: state.selfId,
        max_players: 4,
        player_count: state.players.length,
        players: clone(state.players),
        status: state.status,
        created_at: '2026-01-01T00:00:00Z',
        updated_at: '2026-01-01T00:00:00Z',
      },
      game_state:
        state.status === 'playing'
          ? {
              room_id: state.roomId,
              phase: 'playing',
              status: 'playing',
              wall_count: 55,
              current_turn_player: state.players.find((player) => player.index === state.currentTurn)?.player_id ?? '',
              players: state.players.map((player) => ({
                player_id: player.player_id,
                ding_que: player.missing_suit ?? '',
                ding_que_done: Boolean(player.missing_suit),
                hand_count: player.player_id === state.selfId ? state.hand.length : 13,
                melds: [],
                out_tiles: [],
                has_hu: false,
              })),
              scores: {},
              winners: [],
            }
          : null,
      player_view:
        state.status === 'playing'
          ? {
              room_id: state.roomId,
              ding_que: state.missingSuit ?? '',
              hand: clone(state.hand),
            }
          : {
              room_id: state.roomId,
              hand: [],
            },
    })

    class MockWebSocket {
      url: string
      readyState = 0
      onopen: ((event: Event) => void) | null = null
      onmessage: ((event: MessageEvent<string>) => void) | null = null
      onerror: ((event: Event) => void) | null = null
      onclose: ((event: CloseEvent) => void) | null = null

      constructor(url: string) {
        this.url = url
        window.setTimeout(() => {
          this.readyState = 1
          this.onopen?.(new Event('open'))
        }, 0)
      }

      send(raw: string) {
        const message = JSON.parse(raw) as {
          type?: string
          payload?: Record<string, unknown>
        }

        switch (message.type) {
          case 'get_room_info':
            this.emit({
              type: 'room_info',
              roomId: state.roomId,
              payload: buildRoomPayload(),
            })
            break
          case 'set_ready': {
            const nextReady = Boolean(message.payload?.ready)
            state.players = state.players.map((player) =>
              player.player_id === state.selfId ? { ...player, ready: nextReady } : player,
            )
            this.emit({
              type: 'set_ready',
              roomId: state.roomId,
              payload: {
                room_id: state.roomId,
                user_id: state.selfId,
                ready: nextReady,
              },
            })
            this.emit({
              type: 'room_player_update',
              roomId: state.roomId,
              payload: {
                room_id: state.roomId,
                status: 'waiting',
                player_ids: state.players.map((player) => player.player_id),
                players: clone(state.players),
              },
            })
            break
          }
          case 'start_game':
            state.status = 'playing'
            this.emit({
              type: 'room_state',
              roomId: state.roomId,
              payload: buildRoomPayload().game_state,
            })
            this.emit({
              type: 'player_hand',
              roomId: state.roomId,
              payload: {
                room_id: state.roomId,
                hand: clone(state.hand),
              },
            })
            break
          case 'ding_que': {
            const suit = typeof message.payload?.suit === 'string' ? message.payload.suit : ''
            state.missingSuit = suit || null
            state.players = state.players.map((player) =>
              player.player_id === state.selfId ? { ...player, missing_suit: state.missingSuit } : player,
            )
            this.emit({
              type: 'player_ding_que',
              roomId: state.roomId,
              payload: {
                room_id: state.roomId,
                player_id: state.selfId,
                suit,
              },
            })
            this.emit({
              type: 'room_state',
              roomId: state.roomId,
              payload: buildRoomPayload().game_state,
            })
            this.emit({
              type: 'player_hand',
              roomId: state.roomId,
              payload: {
                room_id: state.roomId,
                ding_que: state.missingSuit ?? '',
                hand: clone(state.hand),
              },
            })
            break
          }
          case 'discard': {
            const tile = message.payload?.tile as Tile | undefined
            if (!tile) {
              break
            }
            state.hand = state.hand.filter((item) => item.id !== tile.id)
            state.currentTurn = 1
            this.emit({
              type: 'room_state',
              roomId: state.roomId,
              payload: buildRoomPayload().game_state,
            })
            this.emit({
              type: 'player_hand',
              roomId: state.roomId,
              payload: {
                room_id: state.roomId,
                hand: clone(state.hand),
              },
            })
            break
          }
          default:
            break
        }
      }

      close() {
        this.readyState = 3
        this.onclose?.(new CloseEvent('close'))
      }

      private emit(payload: unknown) {
        window.setTimeout(() => {
          this.onmessage?.(
            new MessageEvent('message', {
              data: JSON.stringify(payload),
            }) as MessageEvent<string>,
          )
        }, 0)
      }
    }

    Object.defineProperty(window, 'WebSocket', {
      configurable: true,
      writable: true,
      value: MockWebSocket,
    })
  })

  await page.route('**/api/v1/user/info', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        code: 0,
        msg: 'ok',
        data: {
          userID: 'u-e2e-1',
          username: '测试玩家',
          nickname: '测试玩家',
        },
      }),
    })
  })

  await page.route('**/api/v1/game/mahjong/room/list', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        code: 0,
        msg: 'ok',
        data: {
          items: createdRoom ? [createdRoom] : [],
          page: 1,
          size: 20,
          total: createdRoom ? 1 : 0,
        },
      }),
    })
  })

  await page.route('**/api/v1/game/mahjong/room/create', async (route) => {
    const body = (route.request().postDataJSON() as Record<string, unknown> | null) ?? {}
    createdRoom = {
      room_id: 'room-e2e-001',
      name: typeof body.name === 'string' ? body.name : 'E2E 测试房间',
      game_type: typeof body.game_type === 'string' ? body.game_type : 'chengdu',
      owner_id: 'u-e2e-1',
      max_players: typeof body.max_players === 'number' ? body.max_players : 4,
      player_count: 1,
      players: [
        {
          index: 0,
          player_id: 'u-e2e-1',
          player_name: '测试玩家',
          ready: false,
        },
      ],
      status: 'waiting',
      created_at: '2026-01-01T00:00:00Z',
      updated_at: '2026-01-01T00:00:00Z',
    }

    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        code: 0,
        msg: 'ok',
        data: createdRoom,
      }),
    })
  })

  await page.route('**/api/v1/game/mahjong/room/join', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        code: 0,
        msg: 'ok',
        data: createdRoom ?? {
          room_id: 'room-e2e-001',
          name: 'E2E 测试房间',
          game_type: 'chengdu',
          owner_id: 'u-e2e-1',
          max_players: 4,
          player_count: 1,
          status: 'waiting',
          created_at: '2026-01-01T00:00:00Z',
          updated_at: '2026-01-01T00:00:00Z',
        },
      }),
    })
  })

  await page.goto('/hall')

  await expect(page.getByTestId('open-create-room')).toBeVisible()
  await page.getByTestId('open-create-room').click()
  await page.getByTestId('create-room-name').fill('E2E 测试房间')
  await page.getByTestId('submit-create-room').click()
  await expect(page.getByTestId('enter-created-room')).toBeVisible()
  await page.getByTestId('enter-created-room').click()

  await expect(page).toHaveURL(/\/game\/chengdu\/room-e2e-001/)
  await expect(page.getByTestId('ready-toggle')).toBeVisible()
  await page.getByTestId('ready-toggle').click()

  await expect(page.getByTestId('start-game')).toBeVisible()
  await page.getByTestId('start-game').click()

  await expect(page.getByTestId('ding-que-w')).toBeVisible()
  await page.getByTestId('ding-que-w').click()

  const handBar = page.getByTestId('hand-action-bar')
  await expect(handBar).toBeVisible()
  const tiles = handBar.locator('[data-testid^="hand-tile-"]')
  await expect(tiles).toHaveCount(13)
  await tiles.first().click()
  await expect(tiles).toHaveCount(12)
})
