import { describe, it, expect, beforeEach, vi } from 'vitest';
import { processCombatTick } 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 type { CombatTickResult } from '../stores/combat-actions';
import type { EquipmentInstance, EnemyState } 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): EquipmentInstance {
  return {
    instanceId: `test-${typeId}`,
    typeId,
    name: typeId,
    enchantments: [],
    usedCapacity: 0,
    totalCapacity: 50,
    rarity: 'common',
    quality: 50,
    tags: [],
  };
}

function makeEnemy(overrides: Partial<EnemyState> = {}): EnemyState {
  return {
    id: 'test_enemy',
    name: 'Test Enemy',
    hp: 5000,
    maxHP: 5000,
    armor: 0,
    dodgeChance: 0,
    barrier: 0,
    element: 'fire',
    activeEffects: [],
    effectiveArmor: 0,
    ...overrides,
  };
}

/**
 * Run a combat tick with equipped swords and a real applyEnemyDefenses.
 * Properly persists melee progress and floor state between ticks.
 */
function runCombatTickWithDefenses(
  equippedSwords: Record<string, EquipmentInstance>,
  applyEnemyDefensesFn: (
    dmg: number,
    enemy: EnemyState | null,
    roomType: string,
    addLog: (msg: string) => void,
    bypassArmor?: boolean,
    bypassBarrier?: boolean,
  ) => number,
): CombatTickResult {
  return processCombatTick(
    () => useCombatStore.getState(),
    (partial) => useCombatStore.setState(partial),
    1000, // rawMana
    makeInitialElements(500, {}),
    1000, // maxMana
    1,    // attackSpeedMult
    vi.fn(), // onFloorCleared
    (dmg) => ({
      rawMana: 1000,
      elements: makeInitialElements(500, {}),
      modifiedDamage: dmg,
    }),
    [],   // signedPacts
    { activeGolems: [] },
    golemApplyDamageToRoom,
    applyEnemyDefensesFn,
    equippedSwords,
  );
}

/**
 * Run multiple combat ticks, persisting state between each.
 * Returns the final floorHP and the total damage dealt.
 */
function runTicksAndMeasureDamage(
  ticks: number,
  equippedSwords: Record<string, EquipmentInstance>,
  enemy: EnemyState,
  applyEnemyDefensesFn: (
    dmg: number,
    enemy: EnemyState | null,
    roomType: string,
    addLog: (msg: string) => void,
    bypassArmor?: boolean,
    bypassBarrier?: boolean,
  ) => number,
): { finalFloorHP: number; totalDamage: number; finalRoom: { roomType: string; enemies: EnemyState[] } } {
  const initialFloorHP = useCombatStore.getState().floorHP;

  let result: CombatTickResult = {
    rawMana: 1000,
    elements: makeInitialElements(500, {}),
    logMessages: [],
    totalManaGathered: 0,
    currentFloor: 1,
    floorHP: initialFloorHP,
    floorMaxHP: initialFloorHP,
    maxFloorReached: 1,
    castProgress: 0,
    weaponCastProgress: {},
    equipmentSpellStates: [],
    activeGolems: [],
    meleeSwordProgress: useCombatStore.getState().meleeSwordProgress,
    currentRoom: useCombatStore.getState().currentRoom,
  };

  for (let i = 0; i < ticks; i++) {
    result = runCombatTickWithDefenses(equippedSwords, applyEnemyDefensesFn);
    // Persist melee progress and floor state back to store for next tick
    useCombatStore.setState({
      meleeSwordProgress: result.meleeSwordProgress,
      floorHP: result.floorHP,
      currentRoom: result.currentRoom,
    });
  }

  const finalFloorHP = useCombatStore.getState().floorHP;
  return {
    finalFloorHP,
    totalDamage: initialFloorHP - finalFloorHP,
    finalRoom: useCombatStore.getState().currentRoom,
  };
}

// ═══════════════════════════════════════════════════════════════════════════════
// MELEE DEFENSE BYPASS REGRESSION TEST (issue #285)
// These tests verify that melee attacks go through applyEnemyDefenses,
// not bypassing armor/barrier/dodge as they did before the fix.
// ═══════════════════════════════════════════════════════════════════════════════

describe('Melee attacks apply enemy defenses (issue #285 regression)', () => {
  beforeEach(resetStores);

  // ─── Shared defense function that applies armor/barrier/dodge ──────────
  function realDefenses(
    dmg: number,
    enemy: EnemyState | null,
    _roomType: string,
    _addLog: (msg: string) => void,
    bypassArmor?: boolean,
    bypassBarrier?: boolean,
  ): number {
    if (!enemy) return dmg;
    let result = dmg;
    // Dodge check (deterministic: 0 dodge = no dodge)
    if (enemy.dodgeChance > 0 && Math.random() < enemy.dodgeChance) {
      return 0;
    }
    // Barrier absorption
    if (!bypassBarrier && enemy.barrier && enemy.barrier > 0) {
      result *= (1 - enemy.barrier);
    }
    // Armor reduction
    if (!bypassArmor) {
      const armorValue = enemy.effectiveArmor ?? enemy.armor;
      if (armorValue && armorValue > 0) {
        result *= (1 - armorValue);
      }
    }
    return result;
  }

  it('should reduce melee damage when enemy has armor', () => {
    const armoredEnemy = makeEnemy({ armor: 0.5, effectiveArmor: 0.5 }); // 50% armor
    useCombatStore.setState({
      currentRoom: { roomType: 'combat', enemies: [armoredEnemy] },
      floorHP: armoredEnemy.hp,
      floorMaxHP: armoredEnemy.maxHP,
    });

    const sword = makeSwordInstance('ironBlade');
    const equippedSwords = { [sword.instanceId]: sword };

    // Run enough ticks to trigger melee hits
    // ironBlade attackSpeed=1.2, progress per tick = 0.04 * 1.2 = 0.048
    // ~21 ticks per hit. Run 50 ticks for multiple hits.
    const { totalDamage } = runTicksAndMeasureDamage(50, equippedSwords, armoredEnemy, realDefenses);

    // With 50% armor, melee damage should be reduced but still > 0
    expect(totalDamage).toBeGreaterThan(0);
  });

  it('should reduce melee damage when enemy has barrier', () => {
    const barrierEnemy = makeEnemy({ barrier: 0.6 }); // 60% barrier
    useCombatStore.setState({
      currentRoom: { roomType: 'combat', enemies: [barrierEnemy] },
      floorHP: barrierEnemy.hp,
      floorMaxHP: barrierEnemy.maxHP,
    });

    const sword = makeSwordInstance('ironBlade');
    const equippedSwords = { [sword.instanceId]: sword };

    const { totalDamage } = runTicksAndMeasureDamage(50, equippedSwords, barrierEnemy, realDefenses);

    // With 60% barrier, damage should be reduced but still > 0
    expect(totalDamage).toBeGreaterThan(0);
  });

  it('should deal more melee damage to unarmored enemies than armored ones', () => {
    // Set up two scenarios: one with armor, one without
    const noArmorEnemy = makeEnemy({ armor: 0, barrier: 0 });
    const armoredEnemy = makeEnemy({ id: 'armored', armor: 0.5, effectiveArmor: 0.5 });

    const sword = makeSwordInstance('ironBlade');
    const equippedSwords = { [sword.instanceId]: sword };

    // Test 1: No armor
    useCombatStore.setState({
      currentRoom: { roomType: 'combat', enemies: [{ ...noArmorEnemy }] },
      floorHP: 5000,
      floorMaxHP: 5000,
      meleeSwordProgress: {},
    });
    const { totalDamage: damageNoArmor } = runTicksAndMeasureDamage(30, equippedSwords, noArmorEnemy, realDefenses);

    // Test 2: With armor (fresh state)
    useCombatStore.setState({
      currentRoom: { roomType: 'combat', enemies: [{ ...armoredEnemy, hp: 5000, maxHP: 5000 }] },
      floorHP: 5000,
      floorMaxHP: 5000,
      meleeSwordProgress: {},
    });
    const { totalDamage: damageWithArmor } = runTicksAndMeasureDamage(30, equippedSwords, armoredEnemy, realDefenses);

    // Unarmored should take more damage than armored
    expect(damageNoArmor).toBeGreaterThan(damageWithArmor);
  });

  it('should deal full melee damage when enemy has no defenses', () => {
    const noDefenseEnemy = makeEnemy({ armor: 0, dodgeChance: 0, barrier: 0 });
    useCombatStore.setState({
      currentRoom: { roomType: 'combat', enemies: [noDefenseEnemy] },
      floorHP: noDefenseEnemy.hp,
      floorMaxHP: noDefenseEnemy.maxHP,
    });

    const sword = makeSwordInstance('ironBlade');
    const equippedSwords = { [sword.instanceId]: sword };

    const { totalDamage } = runTicksAndMeasureDamage(50, equippedSwords, noDefenseEnemy, realDefenses);

    // With no defenses, melee damage should be full (no reduction)
    expect(totalDamage).toBeGreaterThan(0);
  });

  it('should use the lowest HP enemy as target for melee (focus-fire)', () => {
    // Two enemies: one with low HP (should be targeted), one with high HP
    const lowHPEnemy = makeEnemy({ id: 'low_hp', name: 'Low HP Enemy', hp: 100, maxHP: 100, armor: 0.5, effectiveArmor: 0.5 });
    const highHPEnemy = makeEnemy({ id: 'high_hp', name: 'High HP Enemy', hp: 5000, maxHP: 5000, armor: 0 });

    useCombatStore.setState({
      currentRoom: { roomType: 'combat', enemies: [highHPEnemy, lowHPEnemy] },
      floorHP: 5100,
      floorMaxHP: 5100,
      meleeSwordProgress: {},
    });

    const sword = makeSwordInstance('ironBlade');
    const equippedSwords = { [sword.instanceId]: sword };

    // Run enough ticks for melee to hit
    const { finalRoom } = runTicksAndMeasureDamage(30, equippedSwords, lowHPEnemy, realDefenses);

    // The low HP enemy should have taken damage (focus-fire targeting)
    const lowHPAfter = finalRoom.enemies.find(e => e.id === 'low_hp');
    expect(lowHPAfter).toBeDefined();
    // The low HP enemy should have been hit (its HP reduced)
    expect(lowHPAfter!.hp).toBeLessThan(100);
  });

  it('should handle null/empty enemy list gracefully for melee', () => {
    // No enemies in room — melee should not crash
    useCombatStore.setState({
      currentRoom: { roomType: 'combat', enemies: [] },
      floorHP: 100,
      floorMaxHP: 100,
    });

    const sword = makeSwordInstance('ironBlade');
    const equippedSwords = { [sword.instanceId]: sword };

    // Should not throw
    expect(() => {
      runTicksAndMeasureDamage(10, equippedSwords, makeEnemy(), realDefenses);
    }).not.toThrow();
  });

  it('FAILS on pre-fix code: melee with null enemy bypasses armor', () => {
    // This test simulates the OLD buggy behavior where applyEnemyDefenses
    // was called with null enemy for melee attacks.
    // On pre-fix code, this would pass (damage bypasses armor).
    // On post-fix code, this should fail because the code now passes the enemy.

    const armoredEnemy = makeEnemy({ armor: 0.5, effectiveArmor: 0.5 });
    useCombatStore.setState({
      currentRoom: { roomType: 'combat', enemies: [armoredEnemy] },
      floorHP: 5000,
      floorMaxHP: 5000,
    });

    const sword = makeSwordInstance('ironBlade');
    const equippedSwords = { [sword.instanceId]: sword };

    // Simulate OLD behavior: always pass null enemy (bypass defenses)
    const bypassDefenses = (
      dmg: number,
      _enemy: EnemyState | null,
      _roomType: string,
      _addLog: (msg: string) => void,
    ): number => dmg; // No defense applied

    const { totalDamage: damageBypass } = runTicksAndMeasureDamage(30, equippedSwords, armoredEnemy, bypassDefenses);

    // Now test with proper defenses
    useCombatStore.setState({
      currentRoom: { roomType: 'combat', enemies: [{ ...armoredEnemy, hp: 5000, maxHP: 5000 }] },
      floorHP: 5000,
      floorMaxHP: 5000,
      meleeSwordProgress: {},
    });
    const { totalDamage: damageWithDefense } = runTicksAndMeasureDamage(30, equippedSwords, armoredEnemy, realDefenses);

    // Bypass should deal MORE damage than with defense
    // This proves the defense is being applied in the fixed code
    expect(damageBypass).toBeGreaterThan(damageWithDefense);
  });
});
