import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it } from 'vitest';

import {
  AnimationState,
  type Character,
  type Encounter,
  type SceneHostileNpc,
} from '../../types';
import { GameCanvasEntityLayer } from './GameCanvasEntityLayer';
import {
  CHARACTER_COMBAT_HP_TOP_PX,
  ENTITY_CONTAINER_REM,
  getEncounterCharacterBottomOffsetPx,
  getEncounterCharacterOpponentBottom,
  getHostileNpcSceneBottomOffsetPx,
  getMirroredStageEntityLeft,
  getNpcCombatHpTop,
  getSceneNpcVisualBottomOffsetPx,
  MONSTER_COMBAT_HP_TOP_PX,
} from './GameCanvasShared';

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 createEncounter(overrides: Partial<Encounter> = {}): Encounter {
  return {
    id: 'npc-liu',
    kind: 'npc',
    npcName: '柳无声',
    npcDescription: '桥口旧识',
    npcAvatar: '/npc-liu.png',
    context: '断桥',
    ...overrides,
  };
}

function createHostileNpc(overrides: Partial<SceneHostileNpc> = {}): SceneHostileNpc {
  return {
    id: 'npc-liu',
    name: '柳无声',
    action: '对峙',
    description: '桥口旧识',
    animation: 'idle',
    xMeters: 3,
    yOffset: 0,
    facing: 'left',
    attackRange: 1,
    speed: 1,
    hp: 10,
    maxHp: 10,
    encounter: createEncounter(),
    ...overrides,
  };
}

function renderEntityLayer(effectNpcId: string | null) {
  return renderToStaticMarkup(
    <GameCanvasEntityLayer
      companions={[]}
      sceneActAmbientEncounters={[]}
      currentScenePreset={null}
      sceneTransitionToken={0}
      isSceneTransitionEntering={false}
      isSceneTransitionExiting={false}
      transitionSweepPx={320}
      sceneTransitionExitDurationS={0.2}
      sceneTransitionEntryDurationS={0.2}
      companionAnchorLeft="10%"
      companionAnchorBottom="20%"
      playerBottomOffsetPx={0}
      sceneTransitionPhase="idle"
      inBattle={false}
      onEntitySelect={null}
      playerLeft="20%"
      playerCharacter={createCharacter()}
      playerHp={100}
      playerMaxHp={100}
      effectivePlayerFacing="right"
      effectivePlayerAnimationState={AnimationState.IDLE}
      shouldShowPlayerDialogueIcon={false}
      dialogueIndicator={null}
      npcAffinityEffect={
        effectNpcId
          ? {
              eventId: 'effect-1',
              npcId: effectNpcId,
              delta: 3,
            }
          : null
      }
      sceneCombatants={[createHostileNpc()]}
      monsters={[]}
      getHostileNpcOuterLeft={() => '70%'}
      groundBottom="18%"
      stageLiftPx={68}
      encounter={null}
      sideAnchor="15%"
      cameraAnchorX={0}
      monsterAnchorMeters={3.2}
      playerX={0}
    />,
  );
}

describe('GameCanvasEntityLayer', () => {
  it('uses mirrored stage anchors for player and opponent containers', () => {
    expect(getMirroredStageEntityLeft('15%', 'player')).toBe('15%');
    expect(getMirroredStageEntityLeft('15%', 'opponent')).toBe(`calc(100% - 15% - ${ENTITY_CONTAINER_REM}rem)`);
  });

  it('lowers large monster sprites to the shared scene ground line', () => {
    expect(getHostileNpcSceneBottomOffsetPx({frameHeight: 62})).toBe(-78);
    expect(getHostileNpcSceneBottomOffsetPx({frameHeight: 46})).toBe(-68);
    expect(getHostileNpcSceneBottomOffsetPx({frameHeight: 37})).toBe(-52);
    expect(getHostileNpcSceneBottomOffsetPx({frameHeight: 23})).toBe(-28);
  });

  it('uses scene npc visual anchors instead of template character foot offsets', () => {
    const sceneNpcEncounter = createEncounter({
      characterId: 'hero',
      monsterPresetId: 'monster-20',
      imageSrc: '/generated-custom-world-npc/shark.png',
    });
    const character = createCharacter();

    expect(getEncounterCharacterOpponentBottom('18%', 68, sceneNpcEncounter, character))
      .toBe('calc(18% + 68px - 78px)');
    expect(getEncounterCharacterBottomOffsetPx(68, sceneNpcEncounter, character))
      .toBe(-10);
  });

  it('lowers scene npc custom visuals even without character ids', () => {
    const sceneNpcEncounter = createEncounter({
      visual: {
        race: 'elf',
        bodyColor: 'blue',
        headIndex: 0,
        hairColorIndex: 1,
        hairStyleFrame: 2,
        facialHairEnabled: false,
        facialHairColorIndex: 0,
        facialHairStyleFrame: 0,
      },
    });

    expect(getSceneNpcVisualBottomOffsetPx(sceneNpcEncounter)).toBe(-78);
  });

  it('keeps combat hp bars above character and monster silhouettes', () => {
    expect(getNpcCombatHpTop('hero', null)).toBe(CHARACTER_COMBAT_HP_TOP_PX);
    expect(getNpcCombatHpTop(null, 'monster-20')).toBe(MONSTER_COMBAT_HP_TOP_PX);
    expect(getNpcCombatHpTop(null, null)).toBe(CHARACTER_COMBAT_HP_TOP_PX);
  });

  it('renders affinity effect on the matching hostile npc', () => {
    const html = renderEntityLayer('npc-liu');

    expect(html).toContain('data-testid="npc-affinity-effect-npc-liu"');
    expect(html).toContain('aria-label="好感度变化 +3"');
  });

  it('does not render affinity effect on a different npc', () => {
    const html = renderEntityLayer('npc-other');

    expect(html).not.toContain('npc-affinity-effect-npc-liu');
    expect(html).not.toContain('好感度变化 +3');
  });

  it('renders scene act back-row encounters alongside the primary encounter', () => {
    const html = renderToStaticMarkup(
      <GameCanvasEntityLayer
        companions={[]}
        sceneActAmbientEncounters={[
          createEncounter({ id: 'npc-back-1', npcName: '后排甲' }),
          createEncounter({ id: 'npc-back-2', npcName: '后排乙' }),
        ]}
        currentScenePreset={null}
        sceneTransitionToken={0}
        isSceneTransitionEntering={false}
        isSceneTransitionExiting={false}
        transitionSweepPx={320}
        sceneTransitionExitDurationS={0.2}
        sceneTransitionEntryDurationS={0.2}
        companionAnchorLeft="10%"
        companionAnchorBottom="20%"
        playerBottomOffsetPx={0}
        sceneTransitionPhase="idle"
        inBattle={false}
        onEntitySelect={null}
        playerLeft="20%"
        playerCharacter={createCharacter()}
        playerHp={100}
        playerMaxHp={100}
        effectivePlayerFacing="right"
        effectivePlayerAnimationState={AnimationState.IDLE}
        shouldShowPlayerDialogueIcon={false}
        dialogueIndicator={null}
        npcAffinityEffect={null}
        sceneCombatants={[]}
        monsters={[]}
        getHostileNpcOuterLeft={() => '70%'}
        groundBottom="18%"
        stageLiftPx={68}
        encounter={createEncounter({ id: 'npc-primary', npcName: '主角色' })}
        sideAnchor="15%"
        cameraAnchorX={0}
        monsterAnchorMeters={3.2}
        playerX={0}
      />,
    );

    expect(html).toContain('查看主角色详情');
    expect(html).toContain('查看后排甲详情');
    expect(html).toContain('查看后排乙详情');
  });
});
