import { describe, it, expect } from 'vitest';
import {
  fmt,
  fmtDec,
  getFloorMaxHP,
  getFloorElement,
  canAffordSpellCost,
  deductSpellCost,
  computeMaxMana,
  computeRegen,
  computeClickMana,
  getMeditationBonus,
  getIncursionStrength,
} from '../utils';
import { MAX_DAY, INCURSION_START_DAY } from '../constants';

describe('fmt', () => {
  it('should format numbers < 1000 as integers', () => {
    expect(fmt(500)).toBe('500');
    expect(fmt(0)).toBe('0');
    expect(fmt(999)).toBe('999');
  });

  it('should format thousands with K suffix', () => {
    expect(fmt(1500)).toBe('1.5K');
    expect(fmt(1000)).toBe('1.0K');
    expect(fmt(9999)).toBe('10.0K');
  });

  it('should format millions with M suffix', () => {
    expect(fmt(1500000)).toBe('1.50M');
    expect(fmt(1000000)).toBe('1.00M');
  });

  it('should format billions with B suffix', () => {
    expect(fmt(1500000000)).toBe('1.50B');
    expect(fmt(1000000000)).toBe('1.00B');
  });

  it('should handle edge cases', () => {
    expect(fmt(NaN)).toBe('0');
    expect(fmt(Infinity)).toBe('0');
    expect(fmt(-100)).toBe('-100');
  });
});

describe('fmtDec', () => {
  it('should format decimal numbers', () => {
    expect(fmtDec(1.5, 1)).toBe('1.5');
    expect(fmtDec(1.234, 2)).toBe('1.23');
    expect(fmtDec(1000.5, 1)).toBe('1000.5');
  });
});

describe('getFloorMaxHP', () => {
  it('should return base HP for floor 1', () => {
    const hp = getFloorMaxHP(1);
    expect(hp).toBeGreaterThan(0);
  });

  it('should scale HP with floor number', () => {
    const hp1 = getFloorMaxHP(1);
    const hp5 = getFloorMaxHP(5);
    const hp10 = getFloorMaxHP(10);
    expect(hp5).toBeGreaterThan(hp1);
    expect(hp10).toBeGreaterThan(hp5);
  });

  it('should return higher HP for guardian floors', () => {
    // Floor 10 is Ignis Prime guardian
    const hp10 = getFloorMaxHP(10);
    const hp9 = getFloorMaxHP(9);
    expect(hp10).toBeGreaterThan(hp9 * 2); // Guardians have much more HP
  });
});

describe('getFloorElement', () => {
  it('should return an element string for valid floors', () => {
    const elem = getFloorElement(1);
    expect(typeof elem).toBe('string');
    expect(['fire', 'water', 'earth', 'air', 'raw']).toContain(elem);
  });

  it('should cycle through elements based on floor number', () => {
    // Check that floors have different elements
    const _elem1 = getFloorElement(1);
    const _elem7 = getFloorElement(7);
    // Since it cycles every 5 floors, floor 1 and 7 might have same element
    const _elem2 = getFloorElement(2);
    // Floor 1 and 2 should have different elements (if cycle allows)
  });
});

describe('canAffordSpellCost', () => {
  it('should return true for raw mana cost when enough mana', () => {
    const result = canAffordSpellCost({ type: 'raw', amount: 10 }, 50, {});
    expect(result).toBe(true);
  });

  it('should return false for raw mana cost when not enough mana', () => {
    const result = canAffordSpellCost({ type: 'raw', amount: 100 }, 50, {});
    expect(result).toBe(false);
  });

  it('should handle zero cost', () => {
    const result = canAffordSpellCost({ type: 'raw', amount: 0 }, 0, {});
    expect(result).toBe(true);
  });

  it('should handle elemental costs', () => {
    const elements = {
      fire: { current: 10, max: 50, unlocked: true },
    };
    const result = canAffordSpellCost({ type: 'element', element: 'fire', amount: 5 }, 0, elements);
    expect(result).toBe(true);
  });

  it('should return false for elemental cost when not enough', () => {
    const elements = {
      fire: { current: 3, max: 50, unlocked: true },
    };
    const result = canAffordSpellCost({ type: 'element', element: 'fire', amount: 5 }, 0, elements);
    expect(result).toBe(false);
  });

  it('should return false for locked element', () => {
    const elements = {
      fire: { current: 10, max: 50, unlocked: false },
    };
    const result = canAffordSpellCost({ type: 'element', element: 'fire', amount: 5 }, 0, elements);
    expect(result).toBe(false);
  });
});

describe('deductSpellCost', () => {
  it('should deduct raw mana correctly', () => {
    const result = deductSpellCost({ type: 'raw', amount: 10 }, 50, {});
    expect(result.rawMana).toBe(40);
  });

  it('should not go below zero', () => {
    const result = deductSpellCost({ type: 'raw', amount: 100 }, 50, {});
    expect(result.rawMana).toBe(0);
  });

  it('should deduct elemental mana correctly', () => {
    const elements = {
      fire: { current: 10, max: 50, unlocked: true },
    };
    const result = deductSpellCost({ type: 'element', element: 'fire', amount: 5 }, 0, elements);
    expect(result.elements.fire.current).toBe(5);
  });

  it('should return same values when cost is zero', () => {
    const elements = {
      fire: { current: 10, max: 50, unlocked: true },
    };
    const result = deductSpellCost({ type: 'raw', amount: 0 }, 50, elements);
    expect(result.rawMana).toBe(50);
    expect(result.elements.fire.current).toBe(10);
  });
});

describe('computeMaxMana', () => {
  it('should return base 100 with no upgrades', () => {
    const state = {
      prestigeUpgrades: {},
    };
    const effects = { maxManaBonus: 0, maxManaMultiplier: 1 } as any;
    const result = computeMaxMana(state, effects);
    expect(result).toBe(100);
  });

  it('should include manaWell prestige upgrade', () => {
    const state = {
      prestigeUpgrades: { manaWell: 5 },
    };
    const effects = { maxManaBonus: 0, maxManaMultiplier: 1 } as any;
    const result = computeMaxMana(state, effects);
    expect(result).toBe(100 + 5 * 500); // Base + 500 per level
  });

  it('should apply multiplier from effects', () => {
    const state = {
      prestigeUpgrades: {},
    };
    const effects = { maxManaBonus: 0, maxManaMultiplier: 1.5 } as any;
    const result = computeMaxMana(state, effects);
    expect(result).toBe(150); // 100 * 1.5
  });

  it('should apply bonus from effects', () => {
    const state = {
      prestigeUpgrades: {},
    };
    const effects = { maxManaBonus: 50, maxManaMultiplier: 1 } as any;
    const result = computeMaxMana(state, effects);
    expect(result).toBe(150); // 100 + 50
  });
});

describe('computeRegen', () => {
  it('should return base regen with no upgrades', () => {
    const state = {
      prestigeUpgrades: {} as Record<string, number>,
      attunements: {} as Record<string, { active: boolean; level: number; experience: number }>,
    };
    const effects = { regenBonus: 0, regenMultiplier: 1, permanentRegenBonus: 0 } as any;
    const result = computeRegen(state as any, effects);
    expect(result).toBe(2);
  });
});

describe('computeClickMana', () => {
  it('should return base click mana with no discipline', () => {
    const result = computeClickMana();
    expect(result).toBe(1);
  });

  it('should return click mana with discipline bonus', () => {
    const discipline = { bonuses: { clickManaBonus: 3 }, multipliers: {} };
    const result = computeClickMana(discipline);
    expect(result).toBe(4);
  });
});

describe('getMeditationBonus', () => {
  it('should return 1.0 with zero ticks', () => {
    const result = getMeditationBonus(0);
    expect(result).toBe(1.0);
  });

  it('should increase with more ticks', () => {
    const result1 = getMeditationBonus(10);
    const result2 = getMeditationBonus(100);
    expect(result2).toBeGreaterThan(result1);
  });

  it('should follow continuous ramp formula', () => {
    // At 4 hours: 1 + (4/8)*4 = 3.0, but capped at 2.5
    const ticksFor4Hours = 4 / 0.04;
    const result = getMeditationBonus(ticksFor4Hours);
    expect(result).toBeCloseTo(2.5, 5);
  });

  it('should cap at 2.5', () => {
    const ticksFor8Hours = 8 / 0.04;
    const result = getMeditationBonus(ticksFor8Hours);
    expect(result).toBeCloseTo(2.5, 5);
  });
});

describe('getIncursionStrength', () => {
  it('should return 0 before incursion start day', () => {
    const result = getIncursionStrength(1, 12);
    expect(result).toBe(0);
  });

  it('should return positive value during incursion', () => {
    // After incursion start day
    const result = getIncursionStrength(INCURSION_START_DAY, 12);
    expect(result).toBeGreaterThan(0);
  });

  it('should increase with later days', () => {
    const result1 = getIncursionStrength(INCURSION_START_DAY, 12);
    const result2 = getIncursionStrength(MAX_DAY, 12);
    expect(result2).toBeGreaterThan(result1);
  });
});
