/* @vitest-environment jsdom */

import { act, fireEvent, render, screen, within } from '@testing-library/react';
import { expect, test, vi } from 'vitest';

import type { PuzzleRunSnapshot } from '../../../packages/shared/src/contracts/puzzleRuntimeSession';
import { AuthUiContext } from '../auth/AuthUiContext';
import { PuzzleRuntimeShell } from './PuzzleRuntimeShell';

vi.mock('../../hooks/useResolvedAssetReadUrl', () => ({
  useResolvedAssetReadUrl: () => ({
    resolvedUrl: '',
    isResolving: false,
    shouldResolve: false,
  }),
}));

vi.mock('../ResolvedAssetImage', () => ({
  ResolvedAssetImage: () => null,
}));

function createAuthValue() {
  return {
    user: null,
    canAccessProtectedData: false,
    openLoginModal: () => {},
    requireAuth: (action: () => void) => action(),
    openSettingsModal: () => {},
    openAccountModal: () => {},
    logout: async () => {},
    musicVolume: 0.42,
    setMusicVolume: vi.fn(),
    platformTheme: 'light' as const,
    setPlatformTheme: vi.fn(),
    isHydratingSettings: false,
    isPersistingSettings: false,
    settingsError: null,
  };
}

function renderPuzzleRuntime(
  ui: React.ReactElement,
  authValue = createAuthValue(),
) {
  return render(
    <AuthUiContext.Provider value={authValue}>{ui}</AuthUiContext.Provider>,
  );
}

const clearedRun: PuzzleRunSnapshot = {
  runId: 'run-1',
  entryProfileId: 'profile-1',
  clearedLevelCount: 1,
  currentLevelIndex: 1,
  currentGridSize: 3,
  playedProfileIds: ['profile-1'],
  previousLevelTags: ['奇幻'],
  recommendedNextProfileId: 'profile-2',
  leaderboardEntries: [
    {
      rank: 1,
      nickname: '测试作者',
      elapsedMs: 12_340,
      isCurrentPlayer: true,
    },
    {
      rank: 2,
      nickname: '星桥旅人',
      elapsedMs: 18_120,
    },
  ],
  currentLevel: {
    runId: 'run-1',
    levelIndex: 1,
    gridSize: 3,
    profileId: 'profile-1',
    levelName: '潮雾拼图',
    authorDisplayName: '测试作者',
    themeTags: ['奇幻'],
    coverImageSrc: null,
    status: 'cleared',
    startedAtMs: 1000,
    clearedAtMs: 13_340,
    elapsedMs: 12_340,
    leaderboardEntries: [
      {
        rank: 1,
        nickname: '测试作者',
        elapsedMs: 12_340,
        isCurrentPlayer: true,
      },
      {
        rank: 2,
        nickname: '星桥旅人',
        elapsedMs: 18_120,
      },
    ],
    board: {
      rows: 3,
      cols: 3,
      selectedPieceId: null,
      allTilesResolved: true,
      mergedGroups: [],
      pieces: Array.from({ length: 9 }, (_, index) => ({
        pieceId: `piece-${index}`,
        correctRow: Math.floor(index / 3),
        correctCol: index % 3,
        currentRow: Math.floor(index / 3),
        currentCol: index % 3,
        mergedGroupId: null,
      })),
    },
  },
};

test('通关后显示结算弹窗、排行榜和下一关按钮', () => {
  vi.useFakeTimers();
  const onAdvanceNextLevel = vi.fn();

  renderPuzzleRuntime(
    <PuzzleRuntimeShell
      run={clearedRun}
      onBack={vi.fn()}
      onSwapPieces={vi.fn()}
      onDragPiece={vi.fn()}
      onAdvanceNextLevel={onAdvanceNextLevel}
    />,
  );

  expect(screen.queryByRole('dialog', { name: '通关完成' })).toBeNull();
  expect(screen.getByTestId('puzzle-clear-flash')).toBeTruthy();

  act(() => {
    vi.advanceTimersByTime(1_400);
  });

  const dialog = screen.getByRole('dialog', { name: '通关完成' });
  expect(within(dialog).getAllByText('0:12.34').length).toBeGreaterThan(0);
  expect(within(dialog).getByText('排行榜')).toBeTruthy();
  expect(within(dialog).getByText('#1')).toBeTruthy();
  expect(within(dialog).getByText('测试作者')).toBeTruthy();

  fireEvent.click(within(dialog).getByRole('button', { name: '下一关' }));

  expect(onAdvanceNextLevel).toHaveBeenCalledTimes(1);
  vi.useRealTimers();
});

test('关闭通关弹窗后保留底部下一关入口', () => {
  vi.useFakeTimers();

  renderPuzzleRuntime(
    <PuzzleRuntimeShell
      run={clearedRun}
      onBack={vi.fn()}
      onSwapPieces={vi.fn()}
      onDragPiece={vi.fn()}
      onAdvanceNextLevel={vi.fn()}
    />,
  );

  act(() => {
    vi.advanceTimersByTime(1_400);
  });
  fireEvent.click(screen.getByRole('button', { name: '关闭通关弹窗' }));

  expect(screen.queryByRole('dialog', { name: '通关完成' })).toBeNull();
  expect(screen.getByRole('button', { name: /下一关/u })).toBeTruthy();
  vi.useRealTimers();
});

test('右上角设置按钮打开拼图设置并支持音量调节', () => {
  const authValue = createAuthValue();

  renderPuzzleRuntime(
    <PuzzleRuntimeShell
      run={clearedRun}
      onBack={vi.fn()}
      onSwapPieces={vi.fn()}
      onDragPiece={vi.fn()}
      onAdvanceNextLevel={vi.fn()}
    />,
    authValue,
  );

  fireEvent.click(screen.getByRole('button', { name: '打开拼图设置' }));

  const dialog = screen.getByRole('dialog', { name: '拼图设置' });
  const slider = within(dialog).getByRole('slider', { name: '拼图音乐音量' });
  fireEvent.change(slider, { target: { value: '77' } });

  expect(within(dialog).getByText('第 1 关')).toBeTruthy();
  expect(authValue.setMusicVolume).toHaveBeenCalledWith(0.77);
});

test('合并块按实际拼块外轮廓描边', () => {
  const mergedRun: PuzzleRunSnapshot = {
    ...clearedRun,
    currentLevel: {
      ...clearedRun.currentLevel!,
      status: 'playing',
      board: {
        ...clearedRun.currentLevel!.board,
        allTilesResolved: false,
        mergedGroups: [
          {
            groupId: 'group-l',
            pieceIds: ['piece-0', 'piece-1', 'piece-3'],
            occupiedCells: [
              { row: 0, col: 0 },
              { row: 0, col: 1 },
              { row: 1, col: 0 },
            ],
          },
        ],
        pieces: clearedRun.currentLevel!.board.pieces.map((piece) =>
          ['piece-0', 'piece-1', 'piece-3'].includes(piece.pieceId)
            ? { ...piece, mergedGroupId: 'group-l' }
            : piece,
        ),
      },
    },
  };

  const { container } = renderPuzzleRuntime(
    <PuzzleRuntimeShell
      run={mergedRun}
      onBack={vi.fn()}
      onSwapPieces={vi.fn()}
      onDragPiece={vi.fn()}
      onAdvanceNextLevel={vi.fn()}
    />,
  );
  const outlinedPieces = container.querySelectorAll(
    '[data-merged-piece-outline="true"]',
  );

  expect(outlinedPieces).toHaveLength(3);
  expect(container.querySelector('.ring-2.ring-emerald-100\\/58')).toBeNull();
  expect(outlinedPieces[0]?.className).toContain('border-r-0');
  expect(outlinedPieces[0]?.className).toContain('border-b-0');
  expect(outlinedPieces[0]?.className).toContain('rounded-tl-[0.85rem]');
  expect(outlinedPieces[0]?.className).toContain('rounded-tr-none');
  expect(outlinedPieces[0]?.className).toContain('rounded-bl-none');
  expect(outlinedPieces[1]?.className).toContain('border-l-0');
  expect(outlinedPieces[1]?.className).toContain('rounded-tr-[0.85rem]');
  expect(outlinedPieces[2]?.className).toContain('border-t-0');
  expect(outlinedPieces[2]?.className).toContain('rounded-bl-[0.85rem]');
});
