// @vitest-environment jsdom

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

import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
import * as puzzleWorksService from '../../services/puzzle-works';
import { puzzleAssetClient } from '../../services/puzzle-works/puzzleAssetClient';
import { PuzzleResultView } from './PuzzleResultView';

vi.mock('../ResolvedAssetImage', () => ({
  ResolvedAssetImage: ({
    src,
    alt,
    className,
  }: {
    src?: string | null;
    alt?: string;
    className?: string;
  }) => (src ? <img src={src} alt={alt} className={className} /> : null),
}));

vi.mock('../../services/puzzle-works/puzzleAssetClient', () => ({
  puzzleAssetClient: {
    listHistoryAssets: vi.fn(),
  },
}));

vi.mock('../../services/puzzle-works', () => ({
  updatePuzzleWork: vi.fn(),
}));

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

function createSession(
  overrides: Partial<PuzzleAgentSessionSnapshot> = {},
): PuzzleAgentSessionSnapshot {
  const baseSession: PuzzleAgentSessionSnapshot = {
    sessionId: 'puzzle-session-1',
    currentTurn: 2,
    progressPercent: 88,
    stage: 'ready_to_publish',
    anchorPack: {
      themePromise: {
        key: 'themePromise',
        label: '题材承诺',
        value: '雨夜猫咪',
        status: 'confirmed',
      },
      visualSubject: {
        key: 'visualSubject',
        label: '画面主体',
        value: '屋檐下的猫',
        status: 'confirmed',
      },
      visualMood: {
        key: 'visualMood',
        label: '视觉气质',
        value: '温暖',
        status: 'confirmed',
      },
      compositionHooks: {
        key: 'compositionHooks',
        label: '拼图记忆点',
        value: '雨滴与灯牌',
        status: 'confirmed',
      },
      tagsAndForbidden: {
        key: 'tagsAndForbidden',
        label: '标签与禁忌',
        value: '猫咪、雨夜',
        status: 'confirmed',
      },
    },
    draft: {
      levelName: '雨夜猫街',
      summary: '屋檐下的猫与暖灯街角。',
      themeTags: ['猫咪', '雨夜'],
      forbiddenDirectives: [],
      creatorIntent: null,
      anchorPack: {
        themePromise: {
          key: 'themePromise',
          label: '题材承诺',
          value: '雨夜猫咪',
          status: 'confirmed',
        },
        visualSubject: {
          key: 'visualSubject',
          label: '画面主体',
          value: '屋檐下的猫',
          status: 'confirmed',
        },
        visualMood: {
          key: 'visualMood',
          label: '视觉气质',
          value: '温暖',
          status: 'confirmed',
        },
        compositionHooks: {
          key: 'compositionHooks',
          label: '拼图记忆点',
          value: '雨滴与灯牌',
          status: 'confirmed',
        },
        tagsAndForbidden: {
          key: 'tagsAndForbidden',
          label: '标签与禁忌',
          value: '猫咪、雨夜',
          status: 'confirmed',
        },
      },
      candidates: [
        {
          candidateId: 'candidate-1',
          imageSrc: '/puzzle/candidate-1.png',
          assetId: 'asset-1',
          prompt: '雨夜猫咪',
          actualPrompt: null,
          sourceType: 'generated',
          selected: true,
        },
      ],
      selectedCandidateId: 'candidate-1',
      coverImageSrc: '/puzzle/candidate-1.png',
      coverAssetId: 'asset-1',
      generationStatus: 'ready',
      metadata: null,
    },
    messages: [],
    lastAssistantReply: null,
    publishedProfileId: null,
    suggestedActions: [],
    resultPreview: null,
    updatedAt: '2026-04-26T10:00:00.000Z',
  };

  const session = {
    ...baseSession,
    resultPreview: {
      draft: baseSession.draft!,
      publishReady: true,
      blockers: [],
      qualityFindings: [],
    },
    ...overrides,
  } satisfies PuzzleAgentSessionSnapshot;

  return session;
}

describe('PuzzleResultView', () => {
  test('auto saves renamed title to the puzzle work profile', async () => {
    vi.useFakeTimers();
    vi.mocked(puzzleWorksService.updatePuzzleWork).mockResolvedValue({
      item: {} as never,
    });

    render(
      <PuzzleResultView
        session={createSession()}
        profileId="puzzle-profile-session-1"
        onBack={() => {}}
        onExecuteAction={() => {}}
      />,
    );

    fireEvent.change(screen.getByDisplayValue('雨夜猫街'), {
      target: { value: '暖灯猫街' },
    });

    await act(async () => {
      await vi.runAllTimersAsync();
    });

    expect(puzzleWorksService.updatePuzzleWork).toHaveBeenCalledWith(
      'puzzle-profile-session-1',
      expect.objectContaining({
        levelName: '暖灯猫街',
        summary: '屋檐下的猫与暖灯街角。',
        themeTags: ['猫咪', '雨夜'],
      }),
    );
  });

  test('uses one ordered list without tabs or persistent publish validation', () => {
    render(
      <PuzzleResultView
        session={createSession()}
        onBack={() => {}}
        onExecuteAction={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    expect(screen.queryByRole('button', { name: '基本信息' })).toBeNull();
    expect(screen.queryByRole('button', { name: '拼图图片' })).toBeNull();
    const html = document.body.textContent ?? '';
    expect(html.indexOf('关卡名称')).toBeLessThan(html.indexOf('画面预览'));
    expect(html.indexOf('画面预览')).toBeLessThan(html.indexOf('画面描述'));
    expect(html.indexOf('画面描述')).toBeLessThan(html.indexOf('重新生成画面'));
    expect(html.indexOf('重新生成画面')).toBeLessThan(html.indexOf('题材标签'));
    expect(screen.queryByText('作者预览')).toBeNull();
    expect(screen.queryByText('发布校验')).toBeNull();
    expect(screen.getByRole('button', { name: /作品测试/u })).toBeTruthy();
    expect(screen.getByRole('button', { name: /发布/u })).toBeTruthy();
  });

  test('edits theme tags with chips instead of a persistent tag input', () => {
    vi.mocked(puzzleWorksService.updatePuzzleWork).mockResolvedValue({
      item: {} as never,
    });
    render(
      <PuzzleResultView
        session={createSession()}
        profileId="puzzle-profile-session-1"
        onBack={() => {}}
        onExecuteAction={() => {}}
      />,
    );

    expect(screen.queryByLabelText('新题材标签')).toBeNull();

    fireEvent.click(screen.getByLabelText('删除标签 猫咪'));
    expect(screen.queryByText('猫咪')).toBeNull();
    expect(screen.getByText('雨夜')).toBeTruthy();

    fireEvent.click(screen.getByLabelText('新增题材标签'));
    fireEvent.change(screen.getByLabelText('新题材标签'), {
      target: { value: '暖灯' },
    });
    fireEvent.click(screen.getByRole('button', { name: '添加' }));

    expect(screen.getByText('暖灯')).toBeTruthy();
    expect(screen.queryByLabelText('新题材标签')).toBeNull();
  });

  test('shows blockers only after clicking publish and blocks publish action', () => {
    const onExecuteAction = vi.fn();

    render(
      <PuzzleResultView
        session={createSession({
          resultPreview: {
            draft: createSession().draft!,
            publishReady: false,
            blockers: [
              {
                id: 'missing-cover',
                code: 'missing-cover',
                message: '请先选择正式图',
              },
            ],
            qualityFindings: [],
          },
        })}
        onBack={() => {}}
        onExecuteAction={onExecuteAction}
      />,
    );

    expect(screen.queryByText('请先选择正式图')).toBeNull();

    fireEvent.click(screen.getByRole('button', { name: /发布/u }));
    const dialog = screen.getByRole('dialog', { name: '发布拼图作品' });
    expect(within(dialog).getByText('请先选择正式图')).toBeTruthy();

    fireEvent.click(within(dialog).getByRole('button', { name: '发布到广场' }));
    expect(onExecuteAction).not.toHaveBeenCalled();
  });

  test('starts work test from the current editable draft', () => {
    const onStartTestRun = vi.fn();

    render(
      <PuzzleResultView
        session={createSession()}
        onBack={() => {}}
        onExecuteAction={() => {}}
        onStartTestRun={onStartTestRun}
      />,
    );

    fireEvent.change(screen.getByDisplayValue('雨夜猫街'), {
      target: { value: '暖灯猫街' },
    });
    fireEvent.change(screen.getByLabelText('画面描述'), {
      target: { value: '一只猫在雨夜灯牌下回头。' },
    });
    fireEvent.click(screen.getByLabelText('新增题材标签'));
    fireEvent.change(screen.getByLabelText('新题材标签'), {
      target: { value: '暖灯' },
    });
    fireEvent.click(screen.getByRole('button', { name: '添加' }));
    fireEvent.click(screen.getByRole('button', { name: /作品测试/u }));

    expect(onStartTestRun).toHaveBeenCalledWith(
      expect.objectContaining({
        levelName: '暖灯猫街',
        summary: '一只猫在雨夜灯牌下回头。',
        themeTags: ['猫咪', '雨夜', '暖灯'],
      }),
    );
  });

  test('auto saves edited picture description to the puzzle work profile', async () => {
    vi.useFakeTimers();
    vi.mocked(puzzleWorksService.updatePuzzleWork).mockResolvedValue({
      item: {} as never,
    });

    render(
      <PuzzleResultView
        session={createSession()}
        profileId="puzzle-profile-session-1"
        onBack={() => {}}
        onExecuteAction={() => {}}
      />,
    );

    fireEvent.change(screen.getByLabelText('画面描述'), {
      target: { value: '一只猫在雨夜灯牌下回头。' },
    });

    await act(async () => {
      await vi.runAllTimersAsync();
    });

    expect(puzzleWorksService.updatePuzzleWork).toHaveBeenCalledWith(
      'puzzle-profile-session-1',
      expect.objectContaining({
        summary: '一只猫在雨夜灯牌下回头。',
      }),
    );
  });

  test('requires at least three theme tags before publish can pass', () => {
    const onExecuteAction = vi.fn();

    render(
      <PuzzleResultView
        session={createSession()}
        onBack={() => {}}
        onExecuteAction={onExecuteAction}
      />,
    );

    fireEvent.click(screen.getByLabelText('删除标签 猫咪'));
    fireEvent.click(screen.getByRole('button', { name: /发布/u }));

    const dialog = screen.getByRole('dialog', { name: '发布拼图作品' });
    expect(
      within(dialog).getByText('正式标签数量必须在 3 到 6 之间。'),
    ).toBeTruthy();
    expect(
      (
        within(dialog).getByRole('button', {
          name: '发布到广场',
        }) as HTMLButtonElement
      ).disabled,
    ).toBe(true);
  });

  test('publishes with the edited picture description', () => {
    const onExecuteAction = vi.fn();

    render(
      <PuzzleResultView
        session={createSession({
          draft: {
            ...createSession().draft!,
            themeTags: ['猫咪', '雨夜', '暖灯'],
          },
          resultPreview: {
            draft: {
              ...createSession().draft!,
              themeTags: ['猫咪', '雨夜', '暖灯'],
            },
            publishReady: true,
            blockers: [],
            qualityFindings: [],
          },
        })}
        onBack={() => {}}
        onExecuteAction={onExecuteAction}
      />,
    );

    fireEvent.change(screen.getByLabelText('画面描述'), {
      target: { value: '一只猫在雨夜灯牌下回头。' },
    });
    fireEvent.click(screen.getByRole('button', { name: /发布/u }));
    fireEvent.click(
      within(screen.getByRole('dialog', { name: '发布拼图作品' })).getByRole(
        'button',
        { name: '发布到广场' },
      ),
    );

    expect(onExecuteAction).toHaveBeenCalledWith({
      action: 'publish_puzzle_work',
      levelName: '雨夜猫街',
      summary: '一只猫在雨夜灯牌下回头。',
      themeTags: ['猫咪', '雨夜', '暖灯'],
    });
  });

  test('auto saves added and removed theme tags', async () => {
    vi.useFakeTimers();
    vi.mocked(puzzleWorksService.updatePuzzleWork).mockResolvedValue({
      item: {} as never,
    });

    render(
      <PuzzleResultView
        session={createSession()}
        profileId="puzzle-profile-session-1"
        onBack={() => {}}
        onExecuteAction={() => {}}
      />,
    );

    fireEvent.click(screen.getByLabelText('新增题材标签'));
    fireEvent.change(screen.getByLabelText('新题材标签'), {
      target: { value: '暖灯' },
    });
    fireEvent.click(screen.getByRole('button', { name: '添加' }));

    await act(async () => {
      await vi.runAllTimersAsync();
    });

    expect(puzzleWorksService.updatePuzzleWork).toHaveBeenLastCalledWith(
      'puzzle-profile-session-1',
      expect.objectContaining({
        themeTags: ['猫咪', '雨夜', '暖灯'],
      }),
    );

    fireEvent.click(screen.getByLabelText('删除标签 猫咪'));
    await act(async () => {
      await vi.runAllTimersAsync();
    });

    expect(puzzleWorksService.updatePuzzleWork).toHaveBeenLastCalledWith(
      'puzzle-profile-session-1',
      expect.objectContaining({
        themeTags: ['雨夜', '暖灯'],
      }),
    );
  });

  test('generates one image from the picture description and replaces current image', () => {
    const onExecuteAction = vi.fn();

    render(
      <PuzzleResultView
        session={createSession()}
        onBack={() => {}}
        onExecuteAction={onExecuteAction}
      />,
    );

    expect(screen.getByText('画面描述')).toBeTruthy();
    expect(screen.queryByText(/候选图/u)).toBeNull();
    expect(screen.queryByText(/请生成一张适合正方形拼图关卡/u)).toBeNull();

    fireEvent.change(screen.getByLabelText('画面描述'), {
      target: { value: '一只猫在雨夜灯牌下回头。' },
    });
    fireEvent.click(screen.getByRole('button', { name: /重新生成画面/u }));

    expect(onExecuteAction).toHaveBeenCalledWith({
      action: 'generate_puzzle_images',
      promptText: '一只猫在雨夜灯牌下回头。',
      referenceImageSrc: undefined,
      candidateCount: 1,
    });
  });

  test('selects a history puzzle asset as reference image for the next generation', async () => {
    const onExecuteAction = vi.fn();
    vi.mocked(puzzleAssetClient.listHistoryAssets).mockResolvedValue([
      {
        assetObjectId: 'asset-history-1',
        assetKind: 'puzzle_cover_image',
        imageSrc: '/generated-puzzle-assets/history/image.png',
        ownerUserId: 'user-1',
        ownerLabel: '账号 user-1',
        profileId: null,
        entityId: 'puzzle-session-1',
        createdAt: '2026-04-27T10:00:00.000Z',
        updatedAt: '2026-04-27T10:00:00.000Z',
      },
    ]);

    render(
      <PuzzleResultView
        session={createSession()}
        onBack={() => {}}
        onExecuteAction={onExecuteAction}
      />,
    );

    fireEvent.click(screen.getByLabelText('从历史拼图素材库选择'));

    const dialog = await screen.findByRole('dialog', {
      name: '选择历史拼图素材',
    });
    fireEvent.click(
      await within(dialog).findByRole('button', { name: /账号 user-1/u }),
    );

    await waitFor(() => {
      expect(
        screen.queryByRole('dialog', { name: '选择历史拼图素材' }),
      ).toBeNull();
    });

    fireEvent.click(screen.getByRole('button', { name: /重新生成画面/u }));

    expect(onExecuteAction).toHaveBeenLastCalledWith({
      action: 'generate_puzzle_images',
      promptText: '屋檐下的猫与暖灯街角。',
      referenceImageSrc: '/generated-puzzle-assets/history/image.png',
      candidateCount: 1,
    });
  });

  test('refreshes the current formal image when session cover image changes', async () => {
    const { rerender } = render(
      <PuzzleResultView
        session={createSession()}
        onBack={() => {}}
        onExecuteAction={() => {}}
      />,
    );

    expect(
      screen.getByRole('img', { name: '雨夜猫街' }).getAttribute('src'),
    ).toBe('/puzzle/candidate-1.png');

    rerender(
      <PuzzleResultView
        session={createSession({
          draft: {
            ...createSession().draft!,
            candidates: [
              {
                candidateId: 'candidate-2',
                imageSrc: '/puzzle/candidate-2.png',
                assetId: 'asset-2',
                prompt: '新图',
                actualPrompt: '新图',
                sourceType: 'generated',
                selected: true,
              },
            ],
            selectedCandidateId: 'candidate-2',
            coverImageSrc: '/puzzle/candidate-2.png',
            coverAssetId: 'asset-2',
          },
          updatedAt: '2026-04-27T11:11:11.000Z',
        })}
        onBack={() => {}}
        onExecuteAction={() => {}}
      />,
    );

    await waitFor(() => {
      expect(
        screen.getByRole('img', { name: '雨夜猫街' }).getAttribute('src'),
      ).toBe('/puzzle/candidate-2.png');
    });
  });

  test('prefers the selected latest candidate image when coverImageSrc lags behind', async () => {
    render(
      <PuzzleResultView
        session={createSession({
          draft: {
            ...createSession().draft!,
            candidates: [
              {
                candidateId: 'candidate-1',
                imageSrc: '/puzzle/candidate-1.png',
                assetId: 'asset-1',
                prompt: '旧图',
                actualPrompt: '旧图',
                sourceType: 'generated',
                selected: false,
              },
              {
                candidateId: 'candidate-2',
                imageSrc: '/puzzle/candidate-2.png',
                assetId: 'asset-2',
                prompt: '新图',
                actualPrompt: '新图',
                sourceType: 'generated',
                selected: true,
              },
            ],
            selectedCandidateId: 'candidate-2',
            coverImageSrc: '/puzzle/candidate-1.png',
            coverAssetId: 'asset-1',
          },
        })}
        onBack={() => {}}
        onExecuteAction={() => {}}
      />,
    );

    await waitFor(() => {
      expect(
        screen.getByRole('img', { name: '雨夜猫街' }).getAttribute('src'),
      ).toBe('/puzzle/candidate-2.png');
    });
  });
});
