import { describe, expect, it } from "vitest";
import { assignRosterToCombatSlots } from "./party";
import { createRunGearItem } from "./rewards";
import type {
  CombatPartyAssignment,
  PartyArchetypeId,
  PartyRosterMember,
  RunEquipment,
} from "./types";
import {
  createRpgDamageProfiles,
  extractRpgPartyVitals,
  normalizedSpellRank,
  projectRpgCombat,
  runGearMultipliers,
  spellRankCooldownMultiplier,
  spellRankPowerMultiplier,
} from "./combatAdapter";

function member(
  instanceId: string,
  archetypeId: PartyArchetypeId,
  overrides: Partial<PartyRosterMember> = {},
): PartyRosterMember {
  return {
    candidateId: `candidate-${instanceId}`,
    instanceId,
    name: instanceId,
    archetypeId,
    classId: "warrior",
    className: "Warrior",
    role: "Damage",
    rarity: "green",
    stats: { maxHp: 100, singleTarget: 1, areaDamage: 1, defense: 1, moveSpeed: 1 },
    traitIds: [],
    color: "#fff",
    hp: 100,
    ...overrides,
  };
}

function draftedRoster(): PartyRosterMember[] {
  return [
    member("tank-instance", "warrior-tank", {
      role: "Tank",
      stats: { maxHp: 145, singleTarget: 0.8, areaDamage: 1.02, defense: 1.14, moveSpeed: 0.96 },
      hp: 72.5,
    }),
    member("warlock-instance", "warlock-damage", {
      classId: "warlock",
      className: "Warlock",
      stats: { maxHp: 84, singleTarget: 1.24, areaDamage: 0.8, defense: 0.84, moveSpeed: 0.94 },
      hp: 84,
    }),
    member("monk-instance", "monk-damage", {
      classId: "monk",
      className: "Monk",
      stats: { maxHp: 82, singleTarget: 1.28, areaDamage: 0.76, defense: 0.82, moveSpeed: 1.22 },
      hp: 82,
    }),
    member("warrior-instance", "warrior-damage", {
      stats: { maxHp: 112, singleTarget: 1.02, areaDamage: 1.28, defense: 1, moveSpeed: 1 },
      hp: 112,
    }),
  ];
}

function runEquipment(): RunEquipment {
  return {
    player: {
      weapon: createRunGearItem("player-weapon", "player", "weapon", 4),
      armor: createRunGearItem("player-armor", "player", "armor", 1),
      trinket: createRunGearItem("player-trinket", "player", "trinket", 1),
    },
    "tank-instance": {
      weapon: createRunGearItem("tank-weapon", "tank-instance", "weapon", 1),
      armor: createRunGearItem("tank-armor", "tank-instance", "armor", 2),
      trinket: createRunGearItem("tank-trinket", "tank-instance", "trinket", 2),
    },
  };
}

describe("RPG roguelike combat adapter", () => {
  it("projects drafted identities into stable combat slots with run-only gear", () => {
    const roster = draftedRoster();
    const projection = projectRpgCombat(
      { roster, equipment: runEquipment() },
      { healerClassId: "druid", playerName: "Willow" },
    );

    expect(projection.party.map((entry) => entry.id)).toEqual(["aelia", "brann", "nia", "orin", "vale"]);
    expect(projection.party[0]).toMatchObject({ name: "Willow", className: "Restoration Druid", maxHp: 108, hp: 108 });

    const tank = projection.party[1];
    expect(tank).toMatchObject({
      id: "brann",
      name: "tank-instance",
      role: "Tank",
      maxHp: 162,
      hp: 81,
      runProfile: {
        instanceId: "tank-instance",
        combatProfileId: "warrior-tank",
        combatKitId: "tank",
        tier: "green",
        visualArchetype: "knight",
      },
    });
    expect(projection.party[2].runProfile?.visualArchetype).toBe("mage");
    expect(projection.party[3].runProfile?.visualArchetype).toBe("rogue");
    expect(projection.gearModifiers.brann.damage).toBeCloseTo(1.08);
    expect(projection.gearModifiers.brann.cooldown).toBeCloseTo(1 / 1.06);
    expect(projection.gearModifiers.aelia.healingPower).toBeCloseTo(1.2);
    expect(projection.gearModifiers.aelia.cooldown).toBeCloseTo(1 / 1.04);
  });

  it("keeps class damage identity and archetype kits independent from stable slots", () => {
    const projection = projectRpgCombat(
      { roster: draftedRoster(), equipment: runEquipment() },
      { healerClassId: "priest", playerName: "Aelia" },
    );
    expect(projection.party.slice(1).map((member) => member.runProfile?.combatKitId)).toEqual([
      "tank", "ranged", "melee", "melee",
    ]);
    const profiles = createRpgDamageProfiles(assignRosterToCombatSlots(draftedRoster()));
    expect(profiles.brann).toEqual({ singleTarget: 0.8, areaDamage: 1.02 });
    expect(profiles.nia).toEqual({ singleTarget: 1.24, areaDamage: 0.8 });
    expect(profiles.orin.singleTarget).toBeGreaterThan(profiles.vale.singleTarget);
    expect(profiles.vale.areaDamage).toBeGreaterThan(profiles.vale.singleTarget);

    const empty: CombatPartyAssignment = { brann: null, nia: null, orin: null, vale: null };
    expect(createRpgDamageProfiles(empty).brann).toEqual({ singleTarget: 0, areaDamage: 0 });
  });

  it("extracts gear-independent persistent health by drafted instance id", () => {
    const roster = draftedRoster();
    const combatParty = assignRosterToCombatSlots(roster);
    const projection = projectRpgCombat(
      { roster, equipment: runEquipment() },
      { healerClassId: "priest", playerName: "Aelia" },
    );
    const damaged = projection.party.map((entry) => entry.id === "brann"
      ? { ...entry, hp: entry.maxHp * 0.25 }
      : entry);

    const vitals = extractRpgPartyVitals(damaged, combatParty);
    expect(vitals[0]).toEqual({ instanceId: "tank-instance", hp: 36.25 });
    expect(vitals.map((entry) => entry.instanceId)).toEqual([
      "tank-instance",
      "warlock-instance",
      "monk-instance",
      "warrior-instance",
    ]);
  });

  it("uses bounded reciprocal haste and bounded spell-rank scaling", () => {
    const gear = runGearMultipliers(runEquipment(), "tank-instance");
    expect(gear).toEqual({ maxHealth: 1.12, damage: 1.08, cooldown: 1 / 1.06 });

    const ranks = { "priest-mend": 3, "druid-regrowth": 99, "shaman-riptide": -4 } as const;
    expect(normalizedSpellRank(ranks, "priest-mend")).toBe(3);
    expect(normalizedSpellRank(ranks, "druid-regrowth")).toBe(5);
    expect(normalizedSpellRank(ranks, "shaman-riptide")).toBe(0);
    expect(spellRankPowerMultiplier(ranks, "priest-mend")).toBeCloseTo(1.36);
    expect(spellRankCooldownMultiplier(ranks, "priest-mend")).toBeCloseTo(0.85);
    expect(spellRankCooldownMultiplier(ranks, "druid-regrowth")).toBeCloseTo(0.75);
  });
});
