/* @vitest-environment jsdom */

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

import {
  AnimationState,
  type Character,
  type GoalPulseEvent,
  type GoalStackState,
  type InventoryItem,
  type QuestLogEntry,
  type StoryMoment,
  type StoryOption,
  WorldType,
} from '../../types';
import { RpgAdventurePanel } from './RpgAdventurePanel';

function createCharacter(): Character {
  return {
    id: 'hero',
    name: '沈行',
    title: '试剑客',
    description: '测试主角',
    backstory: '测试背景',
    avatar: '/hero.png',
    portrait: '/hero.png',
    assetFolder: 'hero',
    assetVariant: 'default',
    attributes: {
      strength: 10,
      agility: 10,
      intelligence: 8,
      spirit: 9,
    },
    personality: 'calm',
    skills: [],
    adventureOpenings: {},
  } as Character;
}

function createOption(functionId: string, actionText: string): StoryOption {
  return {
    functionId,
    actionText,
    text: actionText,
    visuals: {
      playerAnimation: AnimationState.IDLE,
      playerMoveMeters: 0,
      playerOffsetY: 0,
      playerFacing: 'right',
      scrollWorld: false,
      monsterChanges: [],
    },
  };
}

function createPendingQuest(): QuestLogEntry {
  return {
    id: 'quest-liu-1',
    issuerNpcId: 'npc-liu',
    issuerNpcName: '柳无声',
    sceneId: 'scene-bamboo',
    title: '竹林密信',
    description: '替柳无声查清竹林中的密信来源。',
    summary: '去竹林查清密信来源。',
    objective: {
      kind: 'inspect_treasure',
      requiredCount: 1,
    },
    progress: 0,
    status: 'active',
    reward: {
      affinityBonus: 5,
      currency: 10,
      items: [],
    },
    rewardText: '完成后可获得报酬。',
  };
}

function createCompletedQuest(): QuestLogEntry {
  return {
    ...createPendingQuest(),
    id: 'quest-liu-completed',
    progress: 1,
    status: 'completed',
    completionNotified: false,
  };
}

function createRewardItem(): InventoryItem {
  return {
    id: 'battle-herb',
    name: '青玉药丸',
    category: '消耗品',
    quantity: 2,
    rarity: 'rare',
    tags: ['治疗'],
    iconSrc: '/Icons/28_bag.png',
    description: '战斗后取得的测试奖励。',
  };
}

function createQuestWithRewardItem(): QuestLogEntry {
  const quest = createPendingQuest();

  return {
    ...quest,
    id: 'quest-liu-reward-item',
    reward: {
      ...quest.reward,
      items: [createRewardItem()],
    },
  };
}

function createPendingQuestStory(quest: QuestLogEntry): StoryMoment {
  const viewOption = createOption('npc_chat_quest_offer_view', '查看任务');
  viewOption.runtimePayload = {
    npcChatQuestOfferAction: 'view',
  };
  const replaceOption = createOption(
    'npc_chat_quest_offer_replace',
    '更换任务',
  );
  replaceOption.runtimePayload = {
    npcChatQuestOfferAction: 'replace',
  };
  const abandonOption = createOption(
    'npc_chat_quest_offer_abandon',
    '放弃任务',
  );
  abandonOption.runtimePayload = {
    npcChatQuestOfferAction: 'abandon',
  };

  return {
    text: '柳无声把真正的委托说了出来。',
    displayMode: 'dialogue',
    dialogue: [
      {
        speaker: 'npc',
        speakerName: '柳无声',
        text: '这件事我只想正式托付给你。',
      },
    ],
    options: [viewOption, replaceOption, abandonOption],
    npcChatState: {
      npcId: 'npc-liu',
      npcName: '柳无声',
      turnCount: 2,
      customInputPlaceholder: '输入你想对 TA 说的话',
      pendingQuestOffer: {
        quest,
      },
    },
  };
}

function createAcceptedQuestStory(): StoryMoment {
  return {
    text: '柳无声把接下来的线索正式交给了你。',
    displayMode: 'dialogue',
    dialogue: [
      {
        speaker: 'npc',
        speakerName: '柳无声',
        text: '这件事我只想正式托付给你。',
      },
      { speaker: 'player', text: '这件事我愿意接下，你把关键要点交给我。' },
      { speaker: 'npc', speakerName: '柳无声', text: '先去竹林查清密信来源。' },
    ],
    options: [
      createOption('npc_chat', '这件事里你最担心哪一步'),
      createOption('npc_chat', '我回来时你最想先知道什么'),
    ],
    npcChatState: {
      npcId: 'npc-liu',
      npcName: '柳无声',
      turnCount: 2,
      customInputPlaceholder: '输入你想对 TA 说的话',
      pendingQuestOffer: null,
    },
  };
}

function createQuestGoalStack(quest: QuestLogEntry): GoalStackState {
  return {
    northStarGoal: null,
    activeGoal: {
      id: `goal-${quest.id}`,
      sourceKind: 'quest',
      sourceId: quest.id,
      layer: 'active_contract',
      track: 'side',
      title: quest.title,
      promiseText: quest.summary,
      whyNow: quest.description,
      nextStepText: '去竹林查清密信来源。',
      sceneHint: '竹林古道',
      npcHint: quest.issuerNpcName,
      progressLabel: '0/1',
      status: 'active',
      urgency: 'medium',
      relatedThreadIds: [],
    },
    immediateStepGoal: null,
    supportGoals: [],
  };
}

const QUEST_GOAL_PULSE: GoalPulseEvent = {
  id: 'pulse-quest-liu-1',
  goalId: 'goal-quest-liu-1',
  pulseType: 'progress',
  title: '任务更新',
  detail: '任务情报刚刚更新。',
  track: 'side',
};

type QuestOfferHarnessProps = {
  battleRewardUi?: ComponentProps<typeof RpgAdventurePanel>['battleRewardUi'];
  initialQuests?: QuestLogEntry[];
  isLoading?: boolean;
};

function QuestOfferHarness({
  battleRewardUi,
  initialQuests,
  isLoading = false,
}: QuestOfferHarnessProps = {}) {
  const pendingQuest = createPendingQuest();
  const [currentStory, setCurrentStory] = useState<StoryMoment>(
    createPendingQuestStory(pendingQuest),
  );
  const [quests, setQuests] = useState<QuestLogEntry[]>(initialQuests ?? []);
  const acceptPendingOffer = vi.fn(() => {
    queueMicrotask(() => {
      setQuests([pendingQuest]);
      setCurrentStory(createAcceptedQuestStory());
    });
    return pendingQuest.id;
  });

  return (
    <RpgAdventurePanel
      aiError={null}
      currentStory={currentStory}
      isLoading={isLoading}
      displayedOptions={currentStory.options}
      hideOptions={false}
      canRefreshOptions={false}
      onRefreshOptions={() => undefined}
      onChoice={() => undefined}
      onSubmitNpcChatInput={() => true}
      onExitNpcChat={() => true}
      onOpenCharacter={() => undefined}
      onOpenInventory={() => undefined}
      playerCharacter={createCharacter()}
      worldType={WorldType.WUXIA}
      quests={quests}
      questUi={{
        acknowledgeQuestCompletion: () => undefined,
        claimQuestReward: () => null,
      }}
      npcChatQuestOfferUi={{
        replacePendingOffer: () => false,
        abandonPendingOffer: () => false,
        acceptPendingOffer,
      }}
      goalStack={{
        ...createQuestGoalStack(pendingQuest),
      }}
      goalPulse={QUEST_GOAL_PULSE}
      onDismissGoalPulse={() => undefined}
      battleRewardUi={
        battleRewardUi ?? {
          reward: null,
          dismiss: () => undefined,
        }
      }
      playerHp={100}
      playerMaxHp={100}
      playerMana={20}
      playerMaxMana={20}
      playerSkillCooldowns={{}}
      inBattle={false}
      currentNpcBattleMode={null}
      statistics={{
        playTimeMs: 0,
        hostileNpcsDefeated: 0,
        questsAccepted: 0,
        questsCompleted: 0,
        questsTurnedIn: 0,
        itemsUsed: 0,
        scenesTraveled: 0,
        currentSceneName: '竹林古道',
        playerCurrency: 0,
        inventoryItemCount: 0,
        inventoryStackCount: 0,
        activeCompanionCount: 0,
        rosterCompanionCount: 0,
      }}
      musicVolume={0.6}
      onMusicVolumeChange={() => undefined}
      onSaveAndExit={() => undefined}
    />
  );
}

beforeAll(() => {
  if (!HTMLElement.prototype.scrollTo) {
    HTMLElement.prototype.scrollTo = () => undefined;
  }
});

function findNearestClassName(element: HTMLElement, className: string) {
  let current: HTMLElement | null = element;

  while (current) {
    if (current.className.includes(className)) {
      return current.className;
    }
    current = current.parentElement;
  }

  return '';
}

test('quest offer accept button reuses the shared accepted-quest follow-up chain', async () => {
  const user = userEvent.setup();

  render(<QuestOfferHarness />);

  await user.click(screen.getByRole('button', { name: /查看任务/u }));
  const affinityRewardClassName = findNearestClassName(
    await screen.findByText('好感度'),
    'bg-rose-500/8',
  );
  const currencyRewardClassName = findNearestClassName(
    screen.getByText('货币'),
    'bg-amber-500/8',
  );
  const experienceRewardClassName = findNearestClassName(
    screen.getByText('经验'),
    'bg-sky-500/8',
  );
  expect(affinityRewardClassName).toContain('border-rose-300/18');
  expect(currencyRewardClassName).toContain('border-amber-300/18');
  expect(experienceRewardClassName).toContain('border-sky-400/18');
  const acceptButton = await screen.findByRole('button', { name: '领取任务' });
  expect(acceptButton.className).toContain(
    'platform-action-button--editor-dark',
  );
  await user.click(acceptButton);

  expect((await screen.findAllByText('任务进度：0/1')).length).toBeGreaterThan(
    0,
  );
  const pulsePanel = screen.getByText('任务情报刚刚更新。').closest('div');
  expect(pulsePanel?.className).toContain('bg-black/25');
  const hintPanel = screen
    .getByText('地点：竹林古道 · 相关人物：柳无声')
    .closest('div');
  expect(hintPanel?.className).toContain('bg-black/25');
  const rewardEmptyState = screen.getByText('该任务没有物品奖励。');
  const rewardCacheClassName = findNearestClassName(
    screen.getByText('奖励缓存'),
    'bg-black/25',
  );
  expect(rewardCacheClassName).toContain('border-amber-300/15');
  expect(rewardEmptyState.className).toContain('platform-empty-state');
  expect(rewardEmptyState.className).toContain('bg-black/20');
  await user.click(screen.getByRole('button', { name: '查看任务' }));
  const questLogPanel = await screen.findByText('任务日志');
  const questLogDialog = questLogPanel.closest('.pixel-modal-shell');
  expect(questLogDialog).toBeTruthy();
  if (!(questLogDialog instanceof HTMLElement)) {
    throw new Error('未找到任务日志弹窗');
  }
  const questArticle = within(questLogDialog)
    .getByText('任务描述：')
    .closest('article');
  const questStatusBadge = within(questLogDialog).getByText('进行中');
  expect(questLogPanel).toBeTruthy();
  expect(questArticle?.className).toContain('bg-black/25');
  expect(questStatusBadge.className).toContain('rounded-full');
  expect(questStatusBadge.className).toContain('font-black');
  expect(questStatusBadge.className).toContain('bg-sky-500/10');
  expect(screen.getAllByText('竹林密信').length).toBeGreaterThan(0);
  expect(screen.queryByText('待领取')).toBeNull();
  expect(screen.getByText('这件事里你最担心哪一步')).toBeTruthy();
});

test('quest log empty state reuses dark PlatformEmptyState chrome', async () => {
  const user = userEvent.setup();

  render(<QuestOfferHarness />);

  const taskDockButton = screen.getByText('竹林密信').closest('button');
  expect(taskDockButton).toBeTruthy();
  if (!(taskDockButton instanceof HTMLElement)) {
    throw new Error('未找到任务入口');
  }

  await user.click(taskDockButton);

  const emptyState = await screen.findByText('暂无活跃任务。');
  expect(emptyState.className).toContain('platform-empty-state');
  expect(emptyState.className).toContain('bg-black/20');
});

test('quest reward strip reuses dark PlatformSubpanel and quantity badge chrome', async () => {
  const user = userEvent.setup();

  render(<QuestOfferHarness initialQuests={[createQuestWithRewardItem()]} />);

  const taskDockButton = screen.getByText('竹林密信').closest('button');
  expect(taskDockButton).toBeTruthy();
  if (!(taskDockButton instanceof HTMLElement)) {
    throw new Error('未找到任务入口');
  }

  await user.click(taskDockButton);

  const questLogPanel = await screen.findByText('任务日志');
  const questLogDialog = questLogPanel.closest('.pixel-modal-shell');
  expect(questLogDialog).toBeTruthy();
  if (!(questLogDialog instanceof HTMLElement)) {
    throw new Error('未找到任务日志弹窗');
  }

  const rewardStripClassName = findNearestClassName(
    within(questLogDialog).getByText('任务奖励'),
    'bg-black/25',
  );
  const rewardItemButton = within(questLogDialog).getByRole('button', {
    name: /查看任务奖励 青玉药丸/u,
  });
  const quantityBadge = within(rewardItemButton).getByText('2');

  expect(rewardStripClassName).toContain('border-amber-300/10');
  expect(quantityBadge.className).toContain('bg-black/70');

  await user.click(rewardItemButton);

  expect(await screen.findByLabelText('关闭奖励物品')).toBeTruthy();
});

test('adventure statistics panel reuses dark PlatformSubpanel chrome', async () => {
  const user = userEvent.setup();

  render(<QuestOfferHarness />);

  await user.click(screen.getByRole('button', { name: '打开设置' }));
  const statsEntryButton = await screen.findByRole('button', {
    name: /运行统计/u,
  });
  expect(statsEntryButton.className).toContain('bg-black/25');
  expect(statsEntryButton.className).toContain('border-white/10');
  await user.click(statsEntryButton);

  const statsTitle = await screen.findByText('冒险统计');
  const statsDialog = statsTitle.closest('.pixel-modal-shell');
  expect(statsDialog).toBeTruthy();
  if (!(statsDialog instanceof HTMLElement)) {
    throw new Error('未找到冒险统计弹窗');
  }

  const overviewClassName = findNearestClassName(
    within(statsDialog).getByText('冒险总览'),
    'bg-black/25',
  );
  const playTimeClassName = findNearestClassName(
    within(statsDialog).getByText('游戏时长'),
    'bg-black/25',
  );

  expect(overviewClassName).toContain('border-amber-300/15');
  expect(playTimeClassName).toContain('border-white/10');
});

test('settings save-disabled hint reuses dark PlatformEmptyState chrome', async () => {
  const user = userEvent.setup();

  render(<QuestOfferHarness isLoading />);

  await user.click(screen.getByRole('button', { name: '打开设置' }));

  const hint = await screen.findByText(
    '故事内容仍在加载或流式传输时，保存功能暂时禁用。',
  );

  expect(hint.className).toContain('platform-empty-state');
  expect(hint.className).toContain('border-dashed');
  expect(hint.className).toContain('bg-black/20');
  expect(hint.className).toContain('rounded-xl');
});

test('quest completion notice reuses dark PlatformSubpanel chrome', async () => {
  render(<QuestOfferHarness initialQuests={[createCompletedQuest()]} />);

  const noticeText = await screen.findByText('可前往任务日志领取奖励。');
  const noticeClassName = findNearestClassName(noticeText, 'bg-black/25');
  const openQuestLogButton = screen.getByRole('button', { name: '打开任务日志' });

  expect(screen.getByText('奖励已准备')).toBeTruthy();
  expect(noticeClassName).toContain('border-emerald-400/15');
  expect(openQuestLogButton.className).toContain(
    'platform-action-button--editor-dark',
  );
});

test('battle reward modal reuses dark PlatformSubpanel chrome', async () => {
  const user = userEvent.setup();

  render(
    <QuestOfferHarness
      battleRewardUi={{
        reward: {
          id: 'battle-reward-1',
          defeatedHostileNpcs: [{ id: 'shadow-wolf', name: '影狼' }],
          items: [createRewardItem()],
        },
        dismiss: () => undefined,
      }}
    />,
  );

  const rewardTitle = await screen.findByText('战斗奖励');
  const rewardDialog = rewardTitle.closest('.pixel-modal-shell');
  expect(rewardDialog).toBeTruthy();
  if (!(rewardDialog instanceof HTMLElement)) {
    throw new Error('未找到战斗奖励弹窗');
  }

  const battleEndClassName = findNearestClassName(
    within(rewardDialog).getByText('战斗结束'),
    'bg-black/25',
  );
  const lootClassName = findNearestClassName(
    within(rewardDialog).getByText('战利品'),
    'bg-black/25',
  );
  const defeatedBadge = within(rewardDialog).getByText('影狼');

  expect(battleEndClassName).toContain('border-emerald-300/15');
  expect(lootClassName).toContain('border-amber-300/15');
  expect(defeatedBadge.className).toContain('rounded-full');
  expect(defeatedBadge.className).toContain('font-black');
  expect(defeatedBadge.className).toContain('bg-emerald-500/10');

  await user.click(
    within(rewardDialog).getByRole('button', {
      name: /查看奖励物品 青玉药丸/u,
    }),
  );

  const itemCloseButton = await screen.findByLabelText('关闭奖励物品');
  const itemDialog = itemCloseButton.closest('.pixel-modal-shell');
  expect(itemDialog).toBeTruthy();
  if (!(itemDialog instanceof HTMLElement)) {
    throw new Error('未找到奖励物品详情弹窗');
  }

  const itemDescriptionClassName = findNearestClassName(
    within(itemDialog).getByText('战斗后取得的测试奖励。'),
    'bg-black/25',
  );
  const itemTagsClassName = findNearestClassName(
    within(itemDialog).getByText(/标签:/u),
    'bg-black/25',
  );

  expect(itemDescriptionClassName).toContain('border-white/10');
  expect(itemTagsClassName).toContain('border-white/10');
});
