/* @vitest-environment jsdom */

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

import {
  AnimationState,
  type Encounter,
  type GameState,
  type EquipmentLoadout,
  WorldType,
} from '../types';
import { AdventureEntityModal } from './AdventureEntityModal';

vi.mock('./CharacterAnimator', () => ({
  CharacterAnimator: () => <div data-testid="character-portrait" />,
}));

vi.mock('./MedievalNpcAnimator', () => ({
  MedievalNpcAnimator: () => <div data-testid="medieval-npc-portrait" />,
}));

vi.mock('./HostileNpcAnimator', () => ({
  HostileNpcAnimator: () => <div data-testid="hostile-npc-portrait" />,
}));

function createGameState(overrides: Partial<GameState> = {}): GameState {
  return {
    worldType: WorldType.WUXIA,
    customWorldProfile: null,
    playerCharacter: null,
    runtimeStats: {
      playTimeMs: 0,
      lastPlayTickAt: null,
      hostileNpcsDefeated: 0,
      questsAccepted: 0,
      itemsUsed: 0,
      scenesTraveled: 0,
    },
    currentScene: 'test-scene',
    storyHistory: [],
    characterChats: {},
    animationState: AnimationState.IDLE,
    currentEncounter: null,
    npcInteractionActive: false,
    currentScenePreset: null,
    sceneHostileNpcs: [],
    playerX: 0,
    playerOffsetY: 0,
    playerFacing: 'right',
    playerActionMode: 'idle',
    scrollWorld: false,
    inBattle: false,
    playerHp: 100,
    playerMaxHp: 100,
    playerMana: 30,
    playerMaxMana: 30,
    playerSkillCooldowns: {},
    activeCombatEffects: [],
    playerCurrency: 0,
    playerInventory: [],
    playerEquipment: {} as EquipmentLoadout,
    npcStates: {},
    quests: [],
    roster: [],
    companions: [],
    currentBattleNpcId: null,
    currentNpcBattleMode: null,
    currentNpcBattleOutcome: null,
    sparReturnEncounter: null,
    sparPlayerHpBefore: null,
    sparPlayerMaxHpBefore: null,
    sparStoryHistoryBefore: null,
    ...overrides,
  } as GameState;
}

function createEncounter(overrides: Partial<Encounter> = {}): Encounter {
  return {
    id: 'runtime-npc',
    kind: 'npc',
    npcName: '雾中来客',
    npcDescription: '带着临时生成形象的相遇者',
    npcAvatar: '/avatar.png',
    context: '桥边试探',
    ...overrides,
  };
}

afterEach(() => {
  vi.restoreAllMocks();
});

test('NPC 详情立绘优先展示遭遇实例形象，而不是 characterId 对应预设', () => {
  const encounter = createEncounter({
    characterId: 'sword-princess',
    imageSrc: '/runtime-npc-preview.png',
  });

  render(
    <AdventureEntityModal
      selection={{ kind: 'npc', encounter }}
      gameState={createGameState()}
      onClose={() => undefined}
    />,
  );

  const portrait = screen.getByAltText('雾中来客');

  expect(portrait.getAttribute('src')).toBe('/runtime-npc-preview.png');
  expect(screen.queryByTestId('character-portrait')).toBeNull();
});

test('NPC 背包物品空 id 会被规范成稳定渲染 id', () => {
  const consoleErrorSpy = vi
    .spyOn(console, 'error')
    .mockImplementation(() => undefined);
  const encounter = createEncounter();

  render(
    <AdventureEntityModal
      selection={{ kind: 'npc', encounter }}
      gameState={createGameState({
        npcStates: {
          'runtime-npc': {
            affinity: 0,
            relationState: { affinity: 0, stance: 'neutral' },
            helpUsed: false,
            chattedCount: 0,
            giftsGiven: 0,
            inventory: [
              {
                id: '',
                category: '材料',
                name: '裂纹石片',
                quantity: 1,
                rarity: 'common',
                tags: [],
              },
              {
                id: '',
                category: '材料',
                name: '裂纹石片',
                quantity: 2,
                rarity: 'common',
                tags: [],
              },
            ],
            recruited: false,
            revealedFacts: [],
            knownAttributeRumors: [],
            firstMeaningfulContactResolved: false,
            seenBackstoryChapterIds: [],
          },
        },
      })}
      onClose={() => undefined}
    />,
  );

  expect(screen.getAllByTitle(/裂纹石片 x/)).toHaveLength(2);
  expect(
    consoleErrorSpy.mock.calls.some((call) =>
      call.some(
        (arg) =>
          typeof arg === 'string' &&
          arg.includes('Encountered two children with the same key'),
      ),
    ),
  ).toBe(false);
});
