import { beforeEach, describe, expect, it } from "vitest";
import { createClassInventory } from "./healers";
import { HOCKEY_PVP_GOAL_DAMAGE, HOCKEY_PVP_GOAL_Z, hockeyPvpBossAt } from "./hockeyHealingPvp";
import { upcomingEncounterMechanic, useGameStore } from "./store";
import { freshParty } from "./data";
import { createDefaultGearProgress, GEAR_OWNER_ORDER, GEAR_SLOT_ORDER, MAX_GEAR_LEVEL } from "./progression/gear";
import { EMPTY_MEMBER_GEAR_MODIFIERS } from "./progression/gearEffects";

const MATCH = {
  matchId: null,
  seed: 4242,
  opponentName: "CPU Aster",
  role: "cpu" as const,
};

function createMaxedGear() {
  const gear = createDefaultGearProgress();
  for (const ownerId of GEAR_OWNER_ORDER) {
    for (const slotId of GEAR_SLOT_ORDER) gear[ownerId].slots[slotId].level = MAX_GEAR_LEVEL;
  }
  gear.priest.passiveInfusionId = "mend-echo";
  return gear;
}

describe("Healing Hockey PVP encounter integration", () => {
  beforeEach(() => {
    useGameStore.getState().configureHealer(
      "priest",
      "Aelia",
      createClassInventory("priest"),
      [hockeyPvpBossAt(MATCH.seed, 0)],
      "hockey-healing-pvp",
      undefined,
      "initiate",
      MATCH,
    );
  });

  it("starts exactly two copies of same boss across mirrored parties", () => {
    const state = useGameStore.getState();
    expect(state.additionalBosses).toHaveLength(0);
    expect(state.boss.id).toBe(hockeyPvpBossAt(MATCH.seed, 0));
    expect(state.hockeyPvpOpponent.boss.id).toBe(state.boss.id);
    expect(state.hockeyPvp.opponentName).toBe("CPU Aster");
  });

  it("normalizes both parties to default base gear without changing saved upgrades", () => {
    const maxedGear = createMaxedGear();
    const baseHealth = freshParty("priest", "Aelia").map((member) => member.maxHp);
    useGameStore.getState().configureHealer(
      "priest",
      "Aelia",
      createClassInventory("priest"),
      [hockeyPvpBossAt(MATCH.seed, 0)],
      "hockey-healing-pvp",
      maxedGear,
      "initiate",
      MATCH,
    );

    const briefing = useGameStore.getState();
    expect(briefing.party.map((member) => member.maxHp)).toEqual(baseHealth);
    expect(briefing.hockeyPvpOpponent.party.map((member) => member.maxHp)).toEqual(baseHealth);
    expect(briefing.healingMultiplier).toBe(1);
    expect(briefing.passiveRunBuffId).toBeNull();
    expect(briefing.gearProgress).toBe(maxedGear);
    for (const modifiers of Object.values(briefing.gearModifiers)) {
      expect(modifiers).toEqual(EMPTY_MEMBER_GEAR_MODIFIERS);
    }

    briefing.startEncounter();
    const combat = useGameStore.getState();
    expect(combat.party.map((member) => member.maxHp)).toEqual(baseHealth);
    expect(combat.hockeyPvpOpponent.party.map((member) => member.maxHp)).toEqual(baseHealth);
    expect(combat.healingMultiplier).toBe(1);
    expect(combat.passiveRunBuffId).toBeNull();
    expect(combat.gearProgress).toBe(maxedGear);
  });

  it("continues applying saved gear outside PVP", () => {
    const maxedGear = createMaxedGear();
    const baseHealth = freshParty("priest", "Aelia").map((member) => member.maxHp);
    useGameStore.getState().configureHealer(
      "priest",
      "Aelia",
      createClassInventory("priest"),
      "bulldrome",
      "encounter",
      maxedGear,
    );

    const state = useGameStore.getState();
    expect(state.party.map((member) => member.maxHp)).not.toEqual(baseHealth);
    expect(state.healingMultiplier).toBeGreaterThan(1);
    expect(state.passiveRunBuffId).toBe("mend-echo");
  });

  it("deals exactly 45 partywide damage when local net is breached", () => {
    useGameStore.getState().startEncounter();
    const startingHp = useGameStore.getState().party.map((member) => member.hp);
    useGameStore.setState((state) => ({
      hockeyPvp: {
        ...state.hockeyPvp,
        puckPosition: [0, HOCKEY_PVP_GOAL_Z - 0.1],
        puckVelocity: [0, 8],
      },
      playerPosition: [8.8, 8.5],
      partyPositions: { ...state.partyPositions, aelia: [8.8, 8.5] },
      boss: { ...state.boss, nextMeleeAt: 999 },
      bossMotion: { ...state.bossMotion, nextMechanicAt: 999 },
      hockeyPvpOpponent: {
        ...state.hockeyPvpOpponent,
        boss: { ...state.hockeyPvpOpponent.boss, nextMeleeAt: 999 },
        bossMotion: { ...state.hockeyPvpOpponent.bossMotion, nextMechanicAt: 999 },
      },
    }));

    useGameStore.getState().tick(0.1);

    useGameStore.getState().party.forEach((member, index) => {
      expect(member.hp).toBe(startingHp[index] - HOCKEY_PVP_GOAL_DAMAGE);
    });
    expect(useGameStore.getState().phase).toBe("combat");
  });

  it("reduces both local and CPU healing by combined boss-kill dampening", () => {
    useGameStore.getState().startEncounter();
    useGameStore.setState((state) => ({
      endlessBossKills: 1,
      party: state.party.map((member) => ({ ...member, hp: Math.max(1, member.hp - 40) })),
      boss: { ...state.boss, nextMeleeAt: 999 },
      bossMotion: { ...state.bossMotion, nextMechanicAt: 999 },
      hockeyPvp: { ...state.hockeyPvp, opponentBossKills: 1, nextCpuHealAt: 0 },
      hockeyPvpOpponent: {
        ...state.hockeyPvpOpponent,
        party: state.hockeyPvpOpponent.party.map((member, index) => ({ ...member, hp: index === 0 ? 50 : member.hp })),
        boss: { ...state.hockeyPvpOpponent.boss, nextMeleeAt: 999 },
        bossMotion: { ...state.hockeyPvpOpponent.bossMotion, nextMechanicAt: 999 },
      },
    }));

    const localBefore = useGameStore.getState().party[0].hp;
    expect(useGameStore.getState().castAbility("ability5")).toBe(true);
    expect(useGameStore.getState().party[0].hp - localBefore).toBeCloseTo(22 * 0.9, 5);

    useGameStore.getState().tick(0.01);
    expect(useGameStore.getState().hockeyPvpOpponent.party[0].hp).toBeCloseTo(50 + 32 * 0.9, 5);
  });

  it("keeps faster and slower parties on same deterministic boss order", () => {
    useGameStore.getState().startEncounter();
    useGameStore.setState((state) => ({
      boss: { ...state.boss, hp: 0, defeatedAt: state.time },
      endlessBossKills: 1,
    }));
    useGameStore.getState().tick(0.01);
    expect(useGameStore.getState().boss.id).toBe(hockeyPvpBossAt(MATCH.seed, 1));
    expect(useGameStore.getState().hockeyPvpOpponent.boss.id).toBe(hockeyPvpBossAt(MATCH.seed, 0));

    useGameStore.setState((state) => ({
      hockeyPvp: { ...state.hockeyPvp, opponentBossKills: 1 },
      hockeyPvpOpponent: {
        ...state.hockeyPvpOpponent,
        boss: { ...state.hockeyPvpOpponent.boss, hp: 0, defeatedAt: state.time },
      },
    }));
    useGameStore.getState().tick(0.01);

    expect(useGameStore.getState().hockeyPvpOpponent.boss.id).toBe(hockeyPvpBossAt(MATCH.seed, 1));
    expect(useGameStore.getState().boss.id).toBe(useGameStore.getState().hockeyPvpOpponent.boss.id);
  });

  it("wins when opponent party falls", () => {
    useGameStore.getState().startEncounter();
    useGameStore.getState().setActiveTab("map");
    useGameStore.setState((state) => ({
      hockeyPvpOpponent: {
        ...state.hockeyPvpOpponent,
        party: state.hockeyPvpOpponent.party.map((member) => ({ ...member, hp: 0 })),
      },
    }));
    useGameStore.getState().tick(0.01);
    expect(useGameStore.getState().phase).toBe("victory");
    expect(useGameStore.getState().hockeyPvp.status).toBe("won");
    expect(useGameStore.getState().activeTab).toBe("combat");
  });

  it("keeps HUD mechanic data defined during instant boss replacement", () => {
    useGameStore.setState((state) => ({ boss: { ...state.boss, hp: 0 } }));
    expect(upcomingEncounterMechanic(useGameStore.getState())).toEqual({
      name: "Replacement incoming",
      remaining: 0,
      cycle: 1,
      urgent: false,
    });
  });
});
