/* @vitest-environment jsdom */

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

import {
  type BarkBattleImageGenerationBatchResult,
  generateAllBarkBattleImageAssets,
  updateBarkBattleDraftConfig,
} from '../../services/bark-battle-creation';
import { BarkBattleGeneratingView } from './BarkBattleGeneratingView';

vi.mock('../../services/bark-battle-creation', () => ({
  generateAllBarkBattleImageAssets: vi.fn(),
  updateBarkBattleDraftConfig: vi.fn(),
}));

const draft = {
  draftId: 'bark-battle-draft-1',
  workId: 'BB-12345678',
  title: '汪汪冠军杯',
  description: '',
  themeDescription: '霓虹公园擂台',
  playerImageDescription: '红围巾柴犬',
  opponentImageDescription: '蓝头带哈士奇',
  difficultyPreset: 'normal' as const,
  configVersion: 2,
  rulesetVersion: 'bark-battle-ruleset-v1',
  updatedAt: '2026-05-14T10:00:00.000Z',
};

describe('BarkBattleGeneratingView', () => {
  it('renders all generation slots while parallel generation is still running', async () => {
    const onComplete = vi.fn();
    let resolveGeneration: (
      result: BarkBattleImageGenerationBatchResult,
    ) => void = () => {};
    vi.mocked(generateAllBarkBattleImageAssets).mockReturnValue(
      new Promise<BarkBattleImageGenerationBatchResult>((resolve) => {
        resolveGeneration = resolve;
      }),
    );
    vi.mocked(updateBarkBattleDraftConfig).mockResolvedValue({
      ...draft,
      configVersion: 3,
      updatedAt: '2026-05-14T10:01:00.000Z',
    });

    const { container } = render(
      <BarkBattleGeneratingView
        draft={draft}
        onBack={() => {}}
        onComplete={onComplete}
        onError={() => {}}
      />,
    );

    expect(container.firstChild).toBeTruthy();
    expect((container.firstChild as HTMLElement).className).toContain('z-[1]');
    expect((container.firstChild as HTMLElement).className).toContain(
      'overflow-hidden',
    );
    expect((container.firstChild as HTMLElement).className).not.toContain(
      'overflow-y-auto',
    );
    expect(screen.getByText('总进度')).toBeTruthy();
    expect(screen.getByText('总进度').className).toContain('text-[9px]');
    const pageVideo = screen.getByTestId(
      'generation-page-background-video',
    ) as HTMLVideoElement;
    expect(pageVideo.parentElement?.className).toContain('z-0');
    expect(pageVideo.parentElement?.className).toContain('bg-transparent');
    expect(pageVideo.parentElement?.className).not.toContain('bg-[#fff4ea]');
    expect((container.firstChild as HTMLElement).contains(pageVideo)).toBe(
      true,
    );
    expect(pageVideo.autoplay).toBe(true);
    expect(pageVideo.loop).toBe(true);
    expect(pageVideo.muted).toBe(true);
    expect(pageVideo.playsInline).toBe(true);
    expect(pageVideo.getAttribute('preload')).toBe('auto');
    expect(
      document.querySelector(
        'video[data-testid="generation-page-background-video"] source[type="video/mp4"]',
      ),
    ).toBeTruthy();
    expect(screen.getByRole('button', { name: '返回编辑' }).className).toContain(
      'text-xs',
    );
    expect(screen.getByText('生成中').className).toContain('text-[11px]');
    expect(screen.getByText('当前步骤')).toBeTruthy();
    expect(screen.getByText('当前步骤').className).toContain('text-[10px]');
    expect(screen.getByTestId('generation-hero-wait-card').className).toContain(
      'text-center',
    );
    expect(screen.getByTestId('generation-hero-elapsed-card').className).toContain(
      'text-center',
    );
    expect(screen.getByTestId('generation-hero-wait-card').className).toContain(
      'bg-white/58',
    );
    expect(screen.getByTestId('generation-hero-elapsed-card').className).toContain(
      'bg-white/58',
    );
    expect(
      screen.getByTestId('generation-hero-wait-card').parentElement
        ?.className,
    ).toContain('mt-3');
    expect(
      screen.getByTestId('generation-hero-wait-card').parentElement
        ?.className,
    ).toContain('px-0');
    expect(screen.getByText('预计等待').className).toContain('text-[9px]');
    expect(screen.getByText('已耗时').className).toContain('text-[9px]');
    expect(screen.getByText('预计等待').parentElement?.className).toContain(
      'justify-center',
    );
    expect(screen.getByText('已耗时').parentElement?.className).toContain(
      'justify-center',
    );
    expect(screen.getByText('3 分钟')).toBeTruthy();
    expect(screen.getByText('1 秒')).toBeTruthy();
    expect(screen.queryByText('预计还需 3 分钟')).toBeNull();
    expect(screen.queryByText('已耗时 1 秒')).toBeNull();
    expect(screen.getByTestId('generation-hero-progress-content').className).toContain(
      'justify-start',
    );
    expect(screen.getByTestId('generation-hero-progress-content').className).toContain(
      'z-30',
    );
    expect(screen.getByTestId('generation-hero-progress-content').className).toContain(
      'pt-[2%]',
    );
    expect(screen.getByText('玩家形象')).toBeTruthy();
    expect(screen.getByText('进行中 36%')).toBeTruthy();
    expect(screen.getByText('进行中 36%').className).toContain('text-[11px]');
    expect(screen.getByText('总进度').className).toContain('text-[9px]');
    expect(screen.getByText('0%').className).toContain('text-[1.15rem]');
    expect(
      screen
        .getByRole('progressbar', { name: '汪汪声浪素材生成进度' })
        .className,
    ).toContain('w-[min(400px,calc(100%_-_0.75rem))]');
    expect(
      screen
        .getByRole('progressbar', { name: '汪汪声浪素材生成进度' })
        .className,
    ).toContain('max-w-full');
    expect(
      screen
        .getByRole('progressbar', { name: '汪汪声浪素材生成进度' })
        .className,
    ).toContain('aspect-square');
    expect(
      screen
        .getByRole('progressbar', { name: '汪汪声浪素材生成进度' })
        .getAttribute('aria-valuenow'),
    ).toBe('0');
    expect(
      screen
        .getByRole('progressbar', { name: '汪汪声浪素材生成进度' })
        .getAttribute('data-ring-start-degrees'),
    ).toBe('135');
    expect(
      screen
        .getByRole('progressbar', { name: '汪汪声浪素材生成进度' })
        .getAttribute('data-ring-fill-start-degrees'),
    ).toBe('135');
    expect(
      screen
        .getByRole('progressbar', { name: '汪汪声浪素材生成进度' })
        .getAttribute('data-ring-sweep-degrees'),
    ).toBe('270');
    expect(
      screen
        .getByRole('progressbar', { name: '汪汪声浪素材生成进度' })
        .getAttribute('data-ring-gap-degrees'),
    ).toBe('90');
    expect(
      screen
        .getByRole('progressbar', { name: '汪汪声浪素材生成进度' })
        .getAttribute('data-ring-fill-degrees'),
    ).toBe('0');
    expect(screen.getByTestId('generation-hero-progress-ring').tagName).toBe(
      'svg',
    );
    expect(screen.getByTestId('generation-hero-progress-ring').getAttribute('class')).toContain(
      'z-0',
    );
    expect(
      screen
        .getByTestId('generation-hero-progress-ring')
        .getAttribute('viewBox'),
    ).toBe('0 0 400 400');
    expect(
      screen
        .getByTestId('generation-hero-progress-ring-track')
        .getAttribute('r'),
    ).toBe('166');
    expect(
      screen
        .getByTestId('generation-hero-progress-ring-track')
        .getAttribute('stroke-width'),
    ).toBe('18');
    expect(
      screen
        .getByTestId('generation-hero-progress-ring-track')
        .getAttribute('transform'),
    ).toBe('rotate(135 200 200)');
    expect(
      screen
        .getByTestId('generation-hero-progress-ring-fill')
        .getAttribute('transform'),
    ).toBe('rotate(135 200 200)');
    expect(
      screen
        .getByTestId('generation-hero-progress-ring-fill')
        .getAttribute('stroke-dasharray'),
    ).toMatch(/^0\.00 1043\.\d{2}$/u);
    expect(
      screen.getByRole('progressbar', { name: '玩家形象 进度' }),
    ).toBeTruthy();
    expect(
      screen
        .getByRole('progressbar', { name: '玩家形象 进度' })
        .getAttribute('aria-valuenow'),
    ).toBe('36');
    expect(
      screen.getByTestId('generation-current-step-card').className,
    ).toContain('bg-white/58');
    expect(
      screen.getByTestId('generation-current-step-card').parentElement
        ?.className,
    ).toContain('mt-5');
    expect(screen.queryByText('预览信息')).toBeNull();
    expect(screen.queryByText('汪汪声浪预览')).toBeNull();
    expect(screen.queryByText('霓虹公园擂台')).toBeNull();
    expect(screen.queryByText('对手形象')).toBeNull();
    expect(screen.queryByText('竞技背景')).toBeNull();
    expect(onComplete).not.toHaveBeenCalled();

    resolveGeneration({
      assets: {
        'player-character': {
          imageSrc: '/generated-bark-battle/player.png',
          assetId: 'asset-player',
          model: 'gpt-image-2-all',
          size: '1024*1024',
          taskId: 'task-player',
          prompt: 'player',
        },
        'opponent-character': {
          imageSrc: '/generated-bark-battle/opponent.png',
          assetId: 'asset-opponent',
          model: 'gpt-image-2-all',
          size: '1024*1024',
          taskId: 'task-opponent',
          prompt: 'opponent',
        },
        'ui-background': {
          imageSrc: '/generated-bark-battle/background.png',
          assetId: 'asset-background',
          model: 'gpt-image-2-all',
          size: '1024*1792',
          taskId: 'task-background',
          prompt: 'background',
        },
      },
      failures: {},
    });
    await waitFor(() => expect(onComplete).toHaveBeenCalled());
  });

  it('persists generated image assets before entering result view', async () => {
    const onComplete = vi.fn();
    const onError = vi.fn();
    vi.mocked(generateAllBarkBattleImageAssets).mockResolvedValue({
      assets: {
        'player-character': {
          imageSrc: '/generated-bark-battle/player.png',
          assetId: 'asset-player',
          model: 'gpt-image-2-all',
          size: '1024*1024',
          taskId: 'task-player',
          prompt: 'player',
        },
        'opponent-character': {
          imageSrc: '/generated-bark-battle/opponent.png',
          assetId: 'asset-opponent',
          model: 'gpt-image-2-all',
          size: '1024*1024',
          taskId: 'task-opponent',
          prompt: 'opponent',
        },
        'ui-background': {
          imageSrc: '/generated-bark-battle/background.png',
          assetId: 'asset-background',
          model: 'gpt-image-2-all',
          size: '1024*1792',
          taskId: 'task-background',
          prompt: 'background',
        },
      },
      failures: {},
    });
    vi.mocked(updateBarkBattleDraftConfig).mockResolvedValue({
      ...draft,
      configVersion: 3,
      updatedAt: '2026-05-14T10:01:00.000Z',
    });

    render(
      <BarkBattleGeneratingView
        draft={draft}
        onBack={() => {}}
        onComplete={onComplete}
        onError={onError}
      />,
    );

    await waitFor(() => {
      expect(updateBarkBattleDraftConfig).toHaveBeenCalledWith(
        expect.objectContaining({
          draftId: 'bark-battle-draft-1',
          workId: 'BB-12345678',
          playerCharacterImageSrc: '/generated-bark-battle/player.png',
          opponentCharacterImageSrc: '/generated-bark-battle/opponent.png',
          uiBackgroundImageSrc: '/generated-bark-battle/background.png',
        }),
      );
    });
    expect(onComplete).toHaveBeenCalledWith(
      expect.objectContaining({
        configVersion: 3,
        playerCharacterImageSrc: '/generated-bark-battle/player.png',
        opponentCharacterImageSrc: '/generated-bark-battle/opponent.png',
        uiBackgroundImageSrc: '/generated-bark-battle/background.png',
      }),
      false,
    );
    expect(onError).toHaveBeenCalledWith(null);
  });

  it('enters result view with partial failure when only part of the images are generated', async () => {
    const onComplete = vi.fn();
    vi.mocked(generateAllBarkBattleImageAssets).mockResolvedValue({
      assets: {
        'player-character': {
          imageSrc: '/generated-bark-battle/player.png',
          assetId: 'asset-player',
          model: 'gpt-image-2-all',
          size: '1024*1024',
          taskId: 'task-player',
          prompt: 'player',
        },
      },
      failures: {
        'opponent-character': '泥点不足，本次需要 1 泥点，当前 0 泥点。',
        'ui-background': '场景图片生成失败：上游超时',
      },
    });
    vi.mocked(updateBarkBattleDraftConfig).mockResolvedValue({
      ...draft,
      playerCharacterImageSrc: '/generated-bark-battle/player.png',
      configVersion: 3,
    });

    render(
      <BarkBattleGeneratingView
        draft={draft}
        onBack={() => {}}
        onComplete={onComplete}
        onError={() => {}}
      />,
    );

    await waitFor(() => {
      expect(onComplete).toHaveBeenCalledWith(
        expect.objectContaining({
          playerCharacterImageSrc: '/generated-bark-battle/player.png',
        }),
        true,
      );
    });
  });

  it('still enters result view when generated assets cannot be persisted', async () => {
    const onComplete = vi.fn();
    const onError = vi.fn();
    vi.mocked(generateAllBarkBattleImageAssets).mockResolvedValue({
      assets: {
        'player-character': {
          imageSrc: '/generated-bark-battle/player.png',
          assetId: 'asset-player',
          model: 'gpt-image-2-all',
          size: '1024*1024',
          taskId: 'task-player',
          prompt: 'player',
        },
        'opponent-character': {
          imageSrc: '/generated-bark-battle/opponent.png',
          assetId: 'asset-opponent',
          model: 'gpt-image-2-all',
          size: '1024*1024',
          taskId: 'task-opponent',
          prompt: 'opponent',
        },
        'ui-background': {
          imageSrc: '/generated-bark-battle/background.png',
          assetId: 'asset-background',
          model: 'gpt-image-2-all',
          size: '1024*1792',
          taskId: 'task-background',
          prompt: 'background',
        },
      },
      failures: {},
    });
    vi.mocked(updateBarkBattleDraftConfig).mockRejectedValue(
      new Error('保存超时'),
    );

    render(
      <BarkBattleGeneratingView
        draft={draft}
        onBack={() => {}}
        onComplete={onComplete}
        onError={onError}
      />,
    );

    await waitFor(() => {
      expect(onComplete).toHaveBeenCalledWith(
        expect.objectContaining({
          playerCharacterImageSrc: '/generated-bark-battle/player.png',
          opponentCharacterImageSrc: '/generated-bark-battle/opponent.png',
          uiBackgroundImageSrc: '/generated-bark-battle/background.png',
        }),
        true,
      );
    });
    expect(onError).toHaveBeenCalledWith('保存超时');
  });

  it('shows generation failures and enters result view when no image asset is generated', async () => {
    const onComplete = vi.fn();
    const onError = vi.fn();
    vi.mocked(generateAllBarkBattleImageAssets).mockResolvedValue({
      assets: {},
      failures: {
        'player-character': '泥点不足，本次需要 1 泥点，当前 0 泥点。',
        'opponent-character': '泥点不足，本次需要 1 泥点，当前 0 泥点。',
        'ui-background': '场景图片生成失败：上游超时',
      },
    });
    vi.mocked(updateBarkBattleDraftConfig).mockResolvedValue(draft);

    render(
      <BarkBattleGeneratingView
        draft={draft}
        onBack={() => {}}
        onComplete={onComplete}
        onError={onError}
      />,
    );

    await waitFor(() => {
      expect(onError).toHaveBeenCalledWith(
        '泥点不足，本次需要 1 泥点，当前 0 泥点。',
      );
      expect(onComplete).toHaveBeenCalledWith(
        expect.objectContaining({
          draftId: draft.draftId,
          workId: draft.workId,
          title: draft.title,
        }),
        true,
      );
    });
  });
});
