/* @vitest-environment jsdom */

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useState } from 'react';
import { expect, test, vi } from 'vitest';

import type { CustomWorldPlayableNpc, CustomWorldProfile } from '../types';
import { CustomWorldResultView } from './CustomWorldResultView';

vi.mock('../services/aiService', () => ({
  generateCustomWorldPlayableNpc: vi.fn(),
  generateCustomWorldStoryNpc: vi.fn(),
  generateCustomWorldLandmark: vi.fn(),
}));

vi.mock('./CharacterAnimator', () => ({
  CharacterAnimator: () => <div>角色预览</div>,
}));

vi.mock('./CustomWorldNpcVisualEditor', () => ({
  CustomWorldNpcPortrait: ({ npc }: { npc: { name: string } }) => (
    <div>{npc.name}</div>
  ),
}));

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

async function loadAiService() {
  return import('../services/aiService');
}

function createBackstoryReveal() {
  return {
    publicSummary: '公开背景',
    chapters: [
      {
        id: 'surface',
        title: '表层来意',
        affinityRequired: 6,
        teaser: '表层来意',
        content: '表层来意内容',
        contextSnippet: '表层来意摘要',
      },
      {
        id: 'scar',
        title: '旧事裂痕',
        affinityRequired: 12,
        teaser: '旧事裂痕',
        content: '旧事裂痕内容',
        contextSnippet: '旧事裂痕摘要',
      },
      {
        id: 'hidden',
        title: '隐藏执念',
        affinityRequired: 18,
        teaser: '隐藏执念',
        content: '隐藏执念内容',
        contextSnippet: '隐藏执念摘要',
      },
      {
        id: 'final',
        title: '最终底牌',
        affinityRequired: 24,
        teaser: '最终底牌',
        content: '最终底牌内容',
        contextSnippet: '最终底牌摘要',
      },
    ],
  };
}

function createPlayableRole(id: string, name: string): CustomWorldPlayableNpc {
  return {
    id,
    name,
    title: '同行者',
    role: '协作战力',
    description: `${name}的定位描述`,
    backstory: `${name}的背景`,
    personality: `${name}的性格`,
    motivation: `${name}的动机`,
    combatStyle: `${name}的战斗风格`,
    initialAffinity: 18,
    relationshipHooks: ['关系钩子'],
    relations: [],
    tags: ['测试'],
    backstoryReveal: createBackstoryReveal(),
    skills: [
      {
        id: `${id}-skill-1`,
        name: '技能一',
        summary: '技能说明一',
        style: '起手压制',
      },
      {
        id: `${id}-skill-2`,
        name: '技能二',
        summary: '技能说明二',
        style: '机动周旋',
      },
      {
        id: `${id}-skill-3`,
        name: '技能三',
        summary: '技能说明三',
        style: '爆发终结',
      },
    ],
    initialItems: [
      {
        id: `${id}-item-1`,
        name: '物品一',
        category: '武器',
        quantity: 1,
        rarity: 'rare',
        description: '物品说明一',
        tags: ['测试'],
      },
      {
        id: `${id}-item-2`,
        name: '物品二',
        category: '专属物品',
        quantity: 1,
        rarity: 'rare',
        description: '物品说明二',
        tags: ['测试'],
      },
      {
        id: `${id}-item-3`,
        name: '物品三',
        category: '消耗品',
        quantity: 2,
        rarity: 'uncommon',
        description: '物品说明三',
        tags: ['测试'],
      },
    ],
  };
}

const baseProfile = {
  id: 'world-1',
  settingText: '潮雾群岛上的禁制与旧航道正在一起失衡。',
  name: '潮雾群岛',
  subtitle: '旧航道与沉钟回响',
  summary: '一座正在被旧誓与新利益共同撕扯的群岛世界。',
  tone: '压抑、潮湿、带着未解旧伤。',
  playerGoal: '找到能让群岛重新稳定的关键节点。',
  templateWorldType: 'WUXIA',
  majorFactions: ['守潮盟', '沉钟会'],
  coreConflicts: ['旧航道归属', '沉钟遗产争夺'],
  attributeSchema: {},
  playableNpcs: [createPlayableRole('playable-1', '沈砺')],
  storyNpcs: [
    {
      ...createPlayableRole('story-1', '顾潮音'),
      initialAffinity: 6,
    },
  ],
  items: [],
  camp: {
    name: '潮灯居',
    description: '玩家最初落脚的旧灯塔内院。',
    dangerLevel: 'medium',
  },
  anchorContent: {
    worldPromise: {
      hook: '被海雾反复改写航路的群岛世界。',
      differentiator: '旧灯塔与禁航令共同决定谁能活着穿过去。',
      desiredExperience: '压抑、悬疑、潮湿',
    },
    playerFantasy: {
      playerRole: '玩家是被迫返乡的守灯人继承者。',
      corePursuit: '查清沉钟异动与失控航路的真相。',
      fearOfLoss: '失去家族留下的最后航路坐标。',
    },
    themeBoundary: {
      toneKeywords: ['压抑', '悬疑'],
      aestheticDirectives: ['潮湿群岛', '冷雾港口'],
      forbiddenDirectives: ['热血少年漫'],
    },
    playerEntryPoint: {
      openingIdentity: '返乡守灯人继承者',
      openingProblem: '首夜就撞见禁航区假航灯重亮',
      entryMotivation: '阻止更多船只误入死潮',
    },
    coreConflict: {
      surfaceConflicts: ['守潮盟与沉钟会争夺航路解释权'],
      hiddenCrisis: '有人借假航灯持续清洗整片群岛的旧证据',
      firstTouchedConflict: '玩家回港当夜就被卷进禁航区封锁',
    },
    keyRelationships: [
      {
        pairs: '玩家 vs 沈砺',
        relationshipType: '旧友互疑',
        secretOrCost: '他掌握沉船夜的关键视角',
      },
    ],
    hiddenLines: {
      hiddenTruths: ['沉钟异动和旧案灭口是同一条线'],
      misdirectionHints: ['表面看像海雾自然失控'],
      revealPacing: '先见异常，再见旧案，再见操盘者',
    },
    iconicElements: {
      iconicMotifs: ['假航灯', '沉钟回响'],
      institutionsOrArtifacts: ['旧灯塔', '禁航碑'],
      hardRules: ['错误航灯会把船引进必死水域'],
    },
  },
  landmarks: [
    {
      id: 'landmark-1',
      name: '沉钟栈桥',
      description: '旧钟与潮声常年相撞的码头栈桥。',
      dangerLevel: 'medium',
      sceneNpcIds: ['story-1'],
      connections: [],
    },
  ],
  sceneChapterBlueprints: [
    {
      id: 'scene-chapter-1',
      sceneId: 'landmark-1',
      title: '沉钟栈桥章节',
      summary: '围绕沉钟栈桥推进的三幕结构。',
      linkedThreadIds: [],
      linkedLandmarkIds: ['landmark-1'],
      acts: [
        {
          id: 'scene-act-1',
          sceneId: 'landmark-1',
          title: '潮声逼近',
          summary: '第一幕先把潮声与旧钟压上来。',
          stageCoverage: ['opening'],
          backgroundImageSrc: '/generated-custom-world-scenes/scene-act-1.png',
          backgroundAssetId: 'scene-asset-1',
          encounterNpcIds: ['story-1'],
          primaryNpcId: 'story-1',
          linkedThreadIds: [],
          advanceRule: 'after_primary_contact',
          actGoal: '接住首幕压力',
          transitionHook: '继续逼近钟楼深处。',
        },
      ],
    },
  ],
  creatorIntent: null,
  anchorPack: null,
  lockState: null,
  ownedSettingLayers: null,
  generationMode: 'full',
  generationStatus: 'complete',
} as unknown as CustomWorldProfile;

function ResultViewHarness() {
  const [profile, setProfile] = useState(baseProfile);

  return (
    <CustomWorldResultView
      profile={profile}
      previewCharacters={[]}
      isGenerating={false}
      progress={0}
      progressLabel=""
      error={null}
      onBack={() => {}}
      onProfileChange={setProfile}
    />
  );
}

test('clicking新增可扮演角色 shows pending item, disables button, and marks result as new', async () => {
  const aiService = await loadAiService();
  const user = userEvent.setup();

  let resolveGeneration: ((value: CustomWorldPlayableNpc) => void) | null = null;
  vi.mocked(aiService.generateCustomWorldPlayableNpc).mockImplementation(
    () =>
      new Promise<CustomWorldPlayableNpc>((resolve) => {
        resolveGeneration = resolve;
      }),
  );

  render(<ResultViewHarness />);

  await user.click(screen.getByRole('button', { name: /可扮演角色/u }));
  await user.click(screen.getByRole('button', { name: '新增可扮演角色' }));

  expect(screen.getByText('新可扮演角色')).toBeTruthy();
  expect(screen.getByText('正在整理世界上下文')).toBeTruthy();

  const createButton = screen.getByRole('button', { name: '新增可扮演角色' });
  expect((createButton as HTMLButtonElement).disabled).toBe(true);

  const finishGeneration = resolveGeneration;
  if (!finishGeneration) {
    throw new Error('expected pending playable generation resolver');
  }

  (finishGeneration as (value: CustomWorldPlayableNpc) => void)(
    createPlayableRole('playable-2', '云止'),
  );

  await waitFor(() => {
    expect(screen.getByRole('button', { name: /云止/u })).toBeTruthy();
  });

  await waitFor(() => {
    expect(screen.queryByText('新可扮演角色')).toBeNull();
  });

  expect(screen.getAllByText('新').length).toBeGreaterThan(0);
});

test('world basic setting renders eight anchor fields and hides legacy parsed/source copy', () => {
  render(<ResultViewHarness />);

  expect(screen.getByText('世界承诺')).toBeTruthy();
  expect(screen.getByText('玩家幻想')).toBeTruthy();
  expect(screen.getByText('主题边界')).toBeTruthy();
  expect(screen.getByText('玩家切入口')).toBeTruthy();
  expect(screen.getByText('核心冲突')).toBeTruthy();
  expect(screen.getByText('关键关系')).toBeTruthy();
  expect(screen.getByText('暗线与揭示')).toBeTruthy();
  expect(screen.getByText('标志元素')).toBeTruthy();
  expect(screen.queryByText('解析字段')).toBeNull();
  expect(screen.queryByText('锚点原文')).toBeNull();
  expect(screen.getByText(/被海雾反复改写航路的群岛世界/u)).toBeTruthy();
  expect(screen.getByText(/沉钟异动和旧案灭口是同一条线/u)).toBeTruthy();
});

test('playable tab prefers generated portrait over runtime preview placeholder', async () => {
  const user = userEvent.setup();
  const profile = {
    ...baseProfile,
    playableNpcs: [
      {
        ...createPlayableRole('playable-portrait', '云止'),
        imageSrc: '/generated-characters/playable-portrait/master.png',
        generatedVisualAssetId: 'visual-playable-portrait',
      },
    ],
  } as CustomWorldProfile;

  render(
    <CustomWorldResultView
      profile={profile}
      previewCharacters={[
        {
          id: 'playable-portrait',
          name: '云止',
          title: '同行者',
          description: '预览角色',
          backstory: '预览背景',
          personality: '预览性格',
          portrait: '/template/portrait.png',
          avatar: '/template/avatar.png',
          assetFolder: 'test',
          assetVariant: 'Hero',
          combatTags: [],
          skills: [],
          adventureOpenings: {},
        } as never,
      ]}
      isGenerating={false}
      progress={0}
      progressLabel=""
      error={null}
      onBack={() => {}}
      onProfileChange={() => {}}
    />,
  );

  await user.click(screen.getByRole('button', { name: /可扮演角色/u }));

  const portrait = screen.getByRole('img', { name: '云止' });
  expect((portrait as HTMLImageElement).getAttribute('src')).toBe(
    '/generated-characters/playable-portrait/master.png',
  );
  expect(screen.getByText('已生成主图')).toBeTruthy();
});

test('landmark tab uses first act image as scene card preview and keeps chapter details out of list', async () => {
  const user = userEvent.setup();

  render(<ResultViewHarness />);

  await user.click(screen.getByRole('button', { name: /场景\s*2/u }));

  expect(screen.queryByText('沉钟栈桥章节')).toBeNull();
  expect(screen.queryByText('潮声逼近')).toBeNull();

  const sceneImage = screen.getByRole('img', { name: '沉钟栈桥' });
  expect((sceneImage as HTMLImageElement).getAttribute('src')).toBe(
    '/generated-custom-world-scenes/scene-act-1.png',
  );
});
