import { describe, it, expect, beforeEach, vi } from 'vitest';
import { processCombatTick, makeInitialSpells } from '../stores/combat-actions';
import { useCombatStore, makeInitialSpells as makeStoreInitialSpells } from '../stores/combatStore';
import { useManaStore, makeInitialElements } from '../stores/manaStore';
import { useGameStore } from '../stores/gameStore';
import { usePrestigeStore } from '../stores/prestigeStore';
import { useUIStore } from '../stores/uiStore';
import { useDisciplineStore } from '../stores/discipline-slice';
import { getFloorMaxHP } from '../utils';
import { calcMeleeDamage, getElementalBonus } from '../utils/combat-utils';
import type { CombatTickResult } from '../stores/combat-actions';
import type { EquipmentInstance } from '../types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

function resetStores() {
  useUIStore.setState({ paused: false, gameOver: false, victory: false, logs: [] });
  useGameStore.setState({ day: 1, hour: 0, incursionStrength: 0, containmentWards: 0, initialized: true });
  useManaStore.setState({ rawMana: 1000, meditateTicks: 0, totalManaGathered: 0, elements: makeInitialElements(500, {}) });
  useCombatStore.setState({
    currentFloor: 1,
    floorHP: getFloorMaxHP(1),
    floorMaxHP: getFloorMaxHP(1),
    maxFloorReached: 1,
    activeSpell: 'manaBolt',
    currentAction: 'climb',
    castProgress: 0,
    spireMode: false,
    currentRoom: { roomType: 'combat', enemies: [] },
    clearedFloors: {},
    climbDirection: 'up',
    isDescending: false,
    startFloor: 1,
    exitFloor: 1,
    currentRoomIndex: 0,
    roomsPerFloor: 5,
    descentPeak: null,
    roomResetState: {},
    clearedRooms: {},
    isDescentComplete: false,
    golemancy: { activeGolems: [], lastSummonFloor: 0, golemDesigns: {}, golemLoadout: [] },
    equipmentSpellStates: [],
    comboHitCount: 0,
    floorHitCount: 0,
    meleeSwordProgress: {},
    spells: makeStoreInitialSpells(),
    activityLog: [],
    achievements: { unlocked: [], progress: {} },
    totalSpellsCast: 0,
    totalDamageDealt: 0,
    totalCraftsCompleted: 0,
  });
  usePrestigeStore.setState({
    loopCount: 0, insight: 0, totalInsight: 0, loopInsight: 0,
    prestigeUpgrades: {}, pactSlots: 1, defeatedGuardians: [],
    signedPacts: [], signedPactDetails: {},
    pactRitualFloor: null, pactRitualProgress: 0,
  });
  useDisciplineStore.setState({ disciplines: {}, activeIds: [], concurrentLimit: 1, totalXP: 0, processedPerks: [] });
}

function golemApplyDamageToRoom(dmg: number): { floorHP: number; floorMaxHP: number; roomCleared: boolean } {
  const cs = useCombatStore.getState();
  const newFloorHP = Math.max(0, cs.floorHP - dmg);
  return { floorHP: newFloorHP, floorMaxHP: cs.floorMaxHP, roomCleared: newFloorHP <= 0 };
}

function makeSwordInstance(typeId: string, enchantments: EquipmentInstance['enchantments'] = []): EquipmentInstance {
  return {
    instanceId: `test-${typeId}`,
    typeId,
    name: typeId,
    enchantments,
    usedCapacity: 0,
    totalCapacity: 50,
    rarity: 'common',
    quality: 50,
    tags: [],
  };
}

function runCombatTickWithSwords(
  rawMana: number,
  elements: Record<string, { current: number; max: number; unlocked: boolean }>,
  equippedSwords: Record<string, EquipmentInstance>,
): CombatTickResult {
  return processCombatTick(
    () => useCombatStore.getState(),
    (partial) => useCombatStore.setState(partial),
    rawMana,
    elements,
    1000, // maxMana
    1,    // attackSpeedMult
    vi.fn(), // onFloorCleared
    (dmg) => ({ rawMana, elements, modifiedDamage: dmg }),
    [],   // signedPacts
    { activeGolems: [] },
    golemApplyDamageToRoom,
    (dmg: number) => dmg,
    equippedSwords,
  );
}

// ═══════════════════════════════════════════════════════════════════════════════
// MELEE AUTO-ATTACK SYSTEM (spec §3.1, §4.3)
// ═══════════════════════════════════════════════════════════════════════════════

describe('calcMeleeDamage', () => {
  it('should return base damage for a sword with no enchantments', () => {
    const swordType = { stats: { baseDamage: 10, attackSpeed: 1.2 } };
    const swordInstance = makeSwordInstance('ironBlade');
    const damage = calcMeleeDamage(swordInstance, swordType, 'fire');
    expect(damage).toBe(10);
  });

  it('should apply elemental bonus for fire-enchanted sword vs water enemy', () => {
    const swordType = { stats: { baseDamage: 10, attackSpeed: 1.2 } };
    const swordInstance = makeSwordInstance('ironBlade', [
      { effectId: 'sword_fire', stacks: 1, actualCost: 40 },
    ]);
    const damage = calcMeleeDamage(swordInstance, swordType, 'water');
    expect(damage).toBe(10 * 1.5);
  });

  it('should apply elemental bonus for fire-enchanted sword vs fire enemy (resonance)', () => {
    const swordType = { stats: { baseDamage: 10, attackSpeed: 1.2 } };
    const swordInstance = makeSwordInstance('ironBlade', [
      { effectId: 'sword_fire', stacks: 1, actualCost: 40 },
    ]);
    const damage = calcMeleeDamage(swordInstance, swordType, 'fire');
    expect(damage).toBe(10 * 1.25);
  });

  it('should apply elemental bonus for fire-enchanted sword vs air enemy (neutral)', () => {
    const swordType = { stats: { baseDamage: 10, attackSpeed: 1.2 } };
    const swordInstance = makeSwordInstance('ironBlade', [
      { effectId: 'sword_fire', stacks: 1, actualCost: 40 },
    ]);
    const damage = calcMeleeDamage(swordInstance, swordType, 'air');
    expect(damage).toBe(10);
  });

  it('should return fallback damage for sword with no stats', () => {
    const swordType = {};
    const swordInstance = makeSwordInstance('ironBlade');
    const damage = calcMeleeDamage(swordInstance, swordType, 'fire');
    expect(damage).toBe(5);
  });

  it('should handle frost-enchanted sword vs fire enemy (frost super effective vs fire = 1.5x, spec §4.2 bidirectional)', () => {
    const swordType = { stats: { baseDamage: 15, attackSpeed: 1.0 } };
    const swordInstance = makeSwordInstance('crystalBlade', [
      { effectId: 'sword_frost', stacks: 1, actualCost: 40 },
    ]);
    const damage = calcMeleeDamage(swordInstance, swordType, 'fire');
    // Frost ↔ fire is bidirectional: frost counters fire (super effective = 1.5x)
    expect(damage).toBe(15 * 1.5);
  });

  it('should handle lightning-enchanted sword vs earth enemy (lightning weak to earth = 0.75x)', () => {
    const swordType = { stats: { baseDamage: 20, attackSpeed: 1.4 } };
    const swordInstance = makeSwordInstance('arcanistBlade', [
      { effectId: 'sword_lightning', stacks: 1, actualCost: 50 },
    ]);
    const damage = calcMeleeDamage(swordInstance, swordType, 'earth');
    expect(damage).toBe(20 * 0.75);
  });

  it('should handle void-enchanted sword correctly (no specific matchup = neutral)', () => {
    const swordType = { stats: { baseDamage: 25, attackSpeed: 1.0 } };
    const swordInstance = makeSwordInstance('voidBlade', [
      { effectId: 'sword_void', stacks: 1, actualCost: 60 },
    ]);
    const damage = calcMeleeDamage(swordInstance, swordType, 'light');
    expect(damage).toBe(25);
  });
});

describe('melee auto-attack in processCombatTick', () => {
  beforeEach(resetStores);

  it('should not error when no swords are equipped', () => {
    const elements = makeInitialElements(500, {});
    const result = runCombatTickWithSwords(1000, elements, {});
    expect(result).toBeDefined();
    expect(result.meleeSwordProgress).toBeDefined();
  });

  it('should accumulate melee progress for equipped swords', () => {
    const elements = makeInitialElements(500, {});
    const sword = makeSwordInstance('ironBlade');
    const equippedSwords = { [sword.instanceId]: sword };
    let result: CombatTickResult = { rawMana: 1000, elements, logMessages: [], totalManaGathered: 0, currentFloor: 1, floorHP: 100, floorMaxHP: 100, maxFloorReached: 1, castProgress: 0, weaponCastProgress: {}, equipmentSpellStates: [], activeGolems: [], meleeSwordProgress: {}, currentRoom: { roomType: 'combat', enemies: [] } };
    for (let i = 0; i < 10; i++) {
      result = runCombatTickWithSwords(1000, elements, equippedSwords);
    }
    expect(Object.keys(result.meleeSwordProgress).length).toBeGreaterThanOrEqual(0);
  });

  it('should track meleeSwordProgress per sword instance', () => {
    const sword1 = makeSwordInstance('ironBlade');
    const sword2 = makeSwordInstance('steelBlade');
    sword1.instanceId = 'sword-1';
    sword2.instanceId = 'sword-2';
    const elements = makeInitialElements(500, {});
    const equippedSwords = { [sword1.instanceId]: sword1, [sword2.instanceId]: sword2 };
    const result = runCombatTickWithSwords(1000, elements, equippedSwords);
    expect(result.meleeSwordProgress['sword-1']).toBeDefined();
    expect(result.meleeSwordProgress['sword-2']).toBeDefined();
  });

  it('should return meleeSwordProgress in the result', () => {
    const elements = makeInitialElements(500, {});
    const result = runCombatTickWithSwords(1000, elements, {});
    expect(result.meleeSwordProgress).toBeDefined();
    expect(typeof result.meleeSwordProgress).toBe('object');
  });

  it('should deal melee damage alongside spell damage', () => {
    // Run many ticks to accumulate enough melee progress for a hit
    const elements = makeInitialElements(500, {});
    const sword = makeSwordInstance('ironBlade');
    sword.instanceId = 'test-sword';
    const equippedSwords = { [sword.instanceId]: sword };
    let result: CombatTickResult = { rawMana: 1000, elements, logMessages: [], totalManaGathered: 0, currentFloor: 1, floorHP: 100000, floorMaxHP: 100000, maxFloorReached: 1, castProgress: 0, weaponCastProgress: {}, equipmentSpellStates: [], activeGolems: [], meleeSwordProgress: {}, currentRoom: { roomType: 'combat', enemies: [] } };
    // Run 25 ticks: each tick adds 0.048 progress, so 25 * 0.048 = 1.2, triggering a hit
    for (let i = 0; i < 25; i++) {
      result = runCombatTickWithSwords(1000, elements, equippedSwords);
    }
    // Floor HP should have decreased from both spell and melee damage
    expect(result.floorHP).toBeLessThan(100000);
  });

  it('should apply elemental bonus for enchanted swords', () => {
    // Verify that calcMeleeDamage applies elemental bonus correctly
    const swordType = { stats: { baseDamage: 8, attackSpeed: 1.2 } };

    // Plain sword vs fire enemy (neutral)
    const plainSword = makeSwordInstance('ironBlade');
    const plainDamage = calcMeleeDamage(plainSword, swordType, 'fire');
    expect(plainDamage).toBe(8);

    // Fire-enchanted sword vs fire enemy (resonance = 1.25x)
    const fireSword = makeSwordInstance('ironBlade', [
      { effectId: 'sword_fire', stacks: 1, actualCost: 40 },
    ]);
    const fireDamage = calcMeleeDamage(fireSword, swordType, 'fire');
    expect(fireDamage).toBe(8 * 1.25);

    // Fire-enchanted sword vs water enemy (super effective = 1.5x)
    const fireVsWater = calcMeleeDamage(fireSword, swordType, 'water');
    expect(fireVsWater).toBe(8 * 1.5);
  });

  it('should not deduct mana for melee attacks', () => {
    const sword = makeSwordInstance('ironBlade');
    sword.instanceId = 'test-sword';
    const equippedSwords = { [sword.instanceId]: sword };
    const elements = makeInitialElements(500, {});
    const startMana = 1000;
    const result = runCombatTickWithSwords(startMana, elements, equippedSwords);
    // Melee attacks cost no mana — rawMana should be <= startMana (spell may have deducted)
    expect(result.rawMana).toBeLessThanOrEqual(startMana);
  });

  it('should handle multiple swords with different attack speeds', () => {
    const fastSword = makeSwordInstance('arcanistBlade');
    fastSword.instanceId = 'fast-sword';
    const slowSword = makeSwordInstance('voidBlade');
    slowSword.instanceId = 'slow-sword';
    const elements = makeInitialElements(500, {});
    const equippedSwords = { [fastSword.instanceId]: fastSword, [slowSword.instanceId]: slowSword };
    const result = runCombatTickWithSwords(1000, elements, equippedSwords);
    const fastProgress = result.meleeSwordProgress[fastSword.instanceId] || 0;
    const slowProgress = result.meleeSwordProgress[slowSword.instanceId] || 0;
    expect(fastProgress).toBeGreaterThanOrEqual(0);
    expect(slowProgress).toBeGreaterThanOrEqual(0);
  });
});
