// @vitest-environment jsdom

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

import type { Match3DWorkProfile } from '../../../packages/shared/src/contracts/match3dWorks';
import * as assetReadUrlService from '../../services/assetReadUrlService';
import * as hyper3dService from '../../services/hyper3dModelGenerationService';
import * as match3dWorksService from '../../services/match3d-works';
import { Match3DResultView } from './Match3DResultView';

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('./Match3DModelPreview', () => ({
  Match3DModelPreview: ({
    modelSrc,
  }: {
    modelSrc?: string | null;
  }) => (
    <div data-model-src={modelSrc ?? ''} data-testid="match3d-model-preview" />
  ),
}));

vi.mock('../../services/assetReadUrlService', () => ({
  readAssetBytes: vi.fn(() =>
    Promise.resolve(
      new Response(new Uint8Array([104, 101, 108, 108, 111]), {
        status: 200,
        headers: {
          'Content-Type': 'image/png',
        },
      }),
    ),
  ),
}));

vi.mock('../../services/match3d-works', () => ({
  generateMatch3DWorkTags: vi.fn(),
  publishMatch3DWork: vi.fn(),
  updateMatch3DWork: vi.fn(),
}));

vi.mock('../../services/hyper3dModelGenerationService', () => ({
  getHyper3dDownloads: vi.fn(),
  getHyper3dTaskStatus: vi.fn(),
  submitHyper3dImageToModel: vi.fn(),
}));

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

function createProfile(
  overrides: Partial<Match3DWorkProfile> = {},
): Match3DWorkProfile {
  return {
    workId: 'match3d-work-1',
    profileId: 'match3d-profile-1',
    ownerUserId: 'user-1',
    sourceSessionId: 'match3d-session-1',
    gameName: '水果抓大鹅',
    themeText: '水果',
    summary: '',
    tags: ['水果', '抓大鹅', '经典消除'],
    coverImageSrc: null,
    referenceImageSrc: null,
    clearCount: 4,
    difficulty: 3,
    publicationStatus: 'draft',
    playCount: 0,
    updatedAt: '2026-05-01T00:00:00.000Z',
    publishedAt: null,
    publishReady: false,
    ...overrides,
  };
}

describe('Match3DResultView', () => {
  test('作品信息 Tab 字段命名对齐拼图草稿且描述可为空', async () => {
    const profile = createProfile();
    vi.mocked(match3dWorksService.updateMatch3DWork).mockResolvedValue({
      item: profile,
    });

    render(
      <Match3DResultView
        profile={profile}
        onBack={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    expect(screen.getByLabelText('作品名称')).toHaveProperty(
      'value',
      '水果抓大鹅',
    );
    expect(screen.getByLabelText('作品描述')).toHaveProperty('value', '');
    expect(screen.getByText('作品标签')).toBeTruthy();
    expect(screen.getByText('水果')).toBeTruthy();
    expect(screen.getByText('抓大鹅')).toBeTruthy();

    fireEvent.click(screen.getByRole('button', { name: '试玩' }));

    await waitFor(() => {
      expect(match3dWorksService.updateMatch3DWork).toHaveBeenCalledWith(
        'match3d-profile-1',
        expect.objectContaining({
          gameName: '水果抓大鹅',
          summary: '',
          tags: ['水果', '抓大鹅', '经典消除'],
        }),
      );
    });
  });

  test('作品标签支持 AI 生成并写回标签编辑区', async () => {
    vi.mocked(match3dWorksService.generateMatch3DWorkTags).mockResolvedValue({
      tags: ['果园', '抓大鹅', '经典消除', '轻量休闲'],
    });

    render(
      <Match3DResultView
        profile={createProfile({ tags: [] })}
        onBack={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    fireEvent.click(screen.getByRole('button', { name: 'AI生成作品标签' }));

    await waitFor(() => {
      expect(match3dWorksService.generateMatch3DWorkTags).toHaveBeenCalledWith({
        gameName: '水果抓大鹅',
        themeText: '水果',
      });
      expect(screen.getByText('果园')).toBeTruthy();
      expect(screen.getByText('轻量休闲')).toBeTruthy();
    });
  });

  test('试玩只要求基础配置可保存，不被发布封面门槛阻断', async () => {
    const profile = createProfile();
    const onStartTestRun = vi.fn();
    vi.mocked(match3dWorksService.updateMatch3DWork).mockResolvedValue({
      item: profile,
    });

    render(
      <Match3DResultView
        profile={profile}
        onBack={() => {}}
        onStartTestRun={onStartTestRun}
      />,
    );

    fireEvent.click(screen.getByRole('button', { name: '试玩' }));

    await waitFor(() => {
      expect(match3dWorksService.updateMatch3DWork).toHaveBeenCalledWith(
        'match3d-profile-1',
        expect.objectContaining({
          clearCount: 4,
          difficulty: 3,
          gameName: '水果抓大鹅',
        }),
      );
    });
    expect(onStartTestRun).toHaveBeenCalledWith(profile);
    expect(match3dWorksService.publishMatch3DWork).not.toHaveBeenCalled();
  });

  test('发布仍要求封面和标签数量满足门槛', () => {
    render(
      <Match3DResultView
        profile={createProfile({ tags: ['水果', '抓大鹅'] })}
        onBack={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    const publishButton = screen.getByRole('button', { name: '发布' });
    expect(publishButton).toHaveProperty('disabled', true);

    fireEvent.click(publishButton);
    expect(match3dWorksService.publishMatch3DWork).not.toHaveBeenCalled();
  });

  test('结果页提供多 Tab，并能进入 Rodin 3D 素材详情', () => {
    render(
      <Match3DResultView
        profile={createProfile({ themeText: '水果' })}
        onBack={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    expect(screen.getByRole('button', { name: '作品信息' })).toBeTruthy();
    expect(screen.getByRole('button', { name: '玩法配置' })).toBeTruthy();
    fireEvent.click(screen.getByRole('button', { name: '3D素材' }));

    fireEvent.click(screen.getByRole('button', { name: /水果核心物件/u }));

    expect(screen.getByText('素材名称')).toBeTruthy();
    expect(screen.getByTestId('match3d-model-preview')).toBeTruthy();
    expect(screen.getByRole('button', { name: '重新生成' })).toBeTruthy();
    expect(screen.queryByRole('button', { name: '文生模型' })).toBeNull();
    expect(screen.queryByRole('button', { name: '图生模型' })).toBeNull();
    expect(screen.queryByText('用途')).toBeNull();
    expect(screen.queryByText('提示词')).toBeNull();
  });

  test('重新生成缺少参考图时阻止提交', async () => {
    render(
      <Match3DResultView
        profile={createProfile({ themeText: '水果' })}
        onBack={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    fireEvent.click(screen.getByRole('button', { name: '3D素材' }));
    fireEvent.click(screen.getByRole('button', { name: /水果核心物件/u }));

    const generateButton = screen.getByRole('button', { name: '重新生成' });
    expect(generateButton).toHaveProperty('disabled', true);
    expect(hyper3dService.submitHyper3dImageToModel).not.toHaveBeenCalled();
  });

  test('结果页优先把生成出来的模型文件交给模型预览', () => {
    const modelSrc =
      '/generated-match3d-assets/session/profile/items/strawberry/model/model.glb';
    render(
      <Match3DResultView
        profile={createProfile({
          clearCount: 3,
          generatedItemAssets: [
            {
              itemId: 'match3d-item-1',
              itemName: '草莓',
              imageSrc: '/generated-match3d-assets/session/profile/items/strawberry/image.png',
              imageObjectKey:
                'generated-match3d-assets/session/profile/items/strawberry/image.png',
              modelSrc,
              modelObjectKey:
                'generated-match3d-assets/session/profile/items/strawberry/model/model.glb',
              modelFileName: 'strawberry.glb',
              taskUuid: 'task-strawberry',
              subscriptionKey: 'sub-strawberry',
              status: 'model_ready',
              error: null,
            },
          ],
        })}
        onBack={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    fireEvent.click(screen.getByRole('button', { name: '3D素材' }));
    fireEvent.click(screen.getByRole('button', { name: /草莓/u }));

    expect(screen.getByDisplayValue('草莓')).toBeTruthy();
    expect(screen.getAllByText('已完成').length).toBeGreaterThan(0);
    expect(screen.getByTestId('match3d-model-preview').getAttribute('data-model-src')).toBe(
      modelSrc,
    );
  });

  test('草稿阶段仅有切割图片时模型预览为空', () => {
    render(
      <Match3DResultView
        profile={createProfile({
          clearCount: 3,
          generatedItemAssets: [
            {
              itemId: 'match3d-item-1',
              itemName: '草莓',
              imageSrc: '/generated-match3d-assets/session/profile/items/strawberry/image.png',
              imageObjectKey:
                'generated-match3d-assets/session/profile/items/strawberry/image.png',
              modelSrc: null,
              modelObjectKey: null,
              modelFileName: null,
              taskUuid: null,
              subscriptionKey: null,
              status: 'image_ready',
              error: null,
            },
          ],
        })}
        onBack={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    fireEvent.click(screen.getByRole('button', { name: '3D素材' }));
    fireEvent.click(screen.getByRole('button', { name: /草莓/u }));

    expect(screen.getByDisplayValue('草莓')).toBeTruthy();
    expect(screen.getAllByText('图片已就绪').length).toBeGreaterThan(0);
    expect(screen.getByTestId('match3d-model-preview').getAttribute('data-model-src')).toBe('');
    expect(screen.queryByRole('link', { name: /\.glb/u })).toBeNull();
  });

  test('重进草稿页时从持久化 profile 素材恢复 3D 素材列表', () => {
    render(
      <Match3DResultView
        profile={createProfile({
          clearCount: 3,
          generatedItemAssets: [
            {
              itemId: 'match3d-item-1',
              itemName: '草莓',
              imageSrc:
                '/generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
              imageObjectKey:
                'generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
              modelSrc: null,
              modelObjectKey: null,
              modelFileName: null,
              taskUuid: null,
              subscriptionKey: null,
              status: 'image_ready',
              error: null,
            },
            {
              itemId: 'match3d-item-2',
              itemName: '苹果',
              imageSrc:
                '/generated-match3d-assets/session/profile/items/match3d-item-2-item/image.png',
              imageObjectKey:
                'generated-match3d-assets/session/profile/items/match3d-item-2-item/image.png',
              modelSrc: null,
              modelObjectKey: null,
              modelFileName: null,
              taskUuid: null,
              subscriptionKey: null,
              status: 'image_ready',
              error: null,
            },
          ],
        })}
        draft={null}
        onBack={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    fireEvent.click(screen.getByRole('button', { name: '3D素材' }));

    expect(screen.getByRole('button', { name: /草莓/u })).toBeTruthy();
    expect(screen.getByRole('button', { name: /苹果/u })).toBeTruthy();
    expect(screen.queryByRole('button', { name: /水果核心物件/u })).toBeNull();
  });

  test('Rodin 图生模型提交前会把 generated 参考图转成 data URL', async () => {
    vi.mocked(hyper3dService.submitHyper3dImageToModel).mockResolvedValue({
      ok: true,
      provider: 'hyper3d-rodin',
      mode: 'image-to-model',
      taskUuid: 'task-image',
      subscriptionKey: 'sub-image',
      jobUuids: ['job-image'],
      message: 'submitted',
      tier: 'Gen-2',
    });
    vi.mocked(hyper3dService.getHyper3dTaskStatus).mockResolvedValue({
      ok: true,
      provider: 'hyper3d-rodin',
      status: 'done',
      jobs: [],
      raw: {},
    });
    vi.mocked(hyper3dService.getHyper3dDownloads).mockResolvedValue({
      ok: true,
      provider: 'hyper3d-rodin',
      files: [
        {
          name: 'strawberry.glb',
          url: 'https://cdn.example.com/strawberry.glb',
        },
      ],
      raw: {},
    });
    vi.stubGlobal('fetch', vi.fn());

    render(
      <Match3DResultView
        profile={createProfile({
          clearCount: 3,
          generatedItemAssets: [
            {
              itemId: 'match3d-item-1',
              itemName: '草莓',
              imageSrc: '/generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
              imageObjectKey:
                'generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
              modelSrc: null,
              modelObjectKey: null,
              modelFileName: null,
              taskUuid: null,
              subscriptionKey: null,
              status: 'image_ready',
              error: null,
            },
          ],
        })}
        onBack={() => {}}
        onStartTestRun={() => {}}
      />,
    );

    fireEvent.click(screen.getByRole('button', { name: '3D素材' }));
    fireEvent.click(screen.getByRole('button', { name: /草莓/u }));
    fireEvent.click(screen.getByRole('button', { name: '重新生成' }));

    await waitFor(() => {
      expect(assetReadUrlService.readAssetBytes).toHaveBeenCalledWith(
        '/generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
        expect.objectContaining({ expireSeconds: 300 }),
      );
      expect(globalThis.fetch).not.toHaveBeenCalled();
      expect(hyper3dService.submitHyper3dImageToModel).toHaveBeenCalledWith(
        expect.objectContaining({
          imageDataUrls: ['data:image/png;base64,aGVsbG8='],
          prompt: expect.stringContaining('草莓'),
        }),
      );
      expect(hyper3dService.getHyper3dDownloads).toHaveBeenCalledWith({
        taskUuid: 'task-image',
      });
    });
  });
});
