import {
  describe,
  it,
  expect,
  vi,
  afterEach
} from 'vitest';
import { getEnemyName, generateSwarmEnemies } from '../utils/enemy-utils';
import { FLOOR_ELEM_CYCLE } from '../constants';

// ─── getEnemyName ─────────────────────────────────────────────────────────────

describe('getEnemyName', () => {
  // Restore Math.random after each test that mocks it
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should return a string for any valid element', () => {
    const elements = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'];
    for (const elem of elements) {
      const name = getEnemyName(elem, 1);
      expect(typeof name).toBe('string');
      expect(name.length).toBeGreaterThan(0);
    }
  });

  it('should return a string for special elements', () => {
    const specialElements = ['lightning', 'metal', 'sand', 'crystal', 'stellar', 'void'];
    for (const elem of specialElements) {
      const name = getEnemyName(elem, 1);
      expect(typeof name).toBe('string');
      expect(name.length).toBeGreaterThan(0);
    }
  });

  it('should return "Unknown Entity" for unknown elements', () => {
    const name = getEnemyName('nonexistent', 1);
    expect(name).toBe('Unknown Entity');
  });

  it('should return "Unknown Entity" for empty string element', () => {
    const name = getEnemyName('', 1);
    expect(name).toBe('Unknown Entity');
  });

  it('should return a name from the correct element pool for fire', () => {
    // Mock Math.random to always pick the first index
    vi.spyOn(Math, 'random').mockReturnValue(0);
    const name = getEnemyName('fire', 1);
    const fireNames = ['Fire Imp', 'Flame Sprite', 'Emberling', 'Scorchling', 'Inferno Whelp'];
    expect(fireNames).toContain(name);
  });

  it('should return a name from the correct element pool for water', () => {
    vi.spyOn(Math, 'random').mockReturnValue(0);
    const name = getEnemyName('water', 1);
    const waterNames = ['Water Elemental', 'Tidal Wraith', 'Aqua Sprite', 'Drowned One', 'Tsunami Spawn'];
    expect(waterNames).toContain(name);
  });

  it('should return a name from the correct element pool for void', () => {
    vi.spyOn(Math, 'random').mockReturnValue(0);
    const name = getEnemyName('void', 1);
    const voidNames = ['Void Lord', 'Abyssal Horror', 'Entropy Spawn', 'Chaos Elemental', 'Nether Beast'];
    expect(voidNames).toContain(name);
  });

  it('should pick from higher tier names on higher floors', () => {
    // On floor 100, tierIndex = min(4, floor(100/20)) = min(4, 5) = 4
    // So it should only pick from index 4 onwards (last element)
    vi.spyOn(Math, 'random').mockReturnValue(0);
    const name = getEnemyName('fire', 100);
    // With tierIndex=4 and random=0, randomIndex = (4 + floor(0 * 1)) % 5 = 4
    expect(name).toBe('Inferno Whelp');
  });

  it('should pick from tier 0 on floors 1-19', () => {
    vi.spyOn(Math, 'random').mockReturnValue(0);
    const name = getEnemyName('fire', 1);
    // tierIndex = min(4, floor(1/20)) = 0
    // randomIndex = (0 + floor(0 * 5)) % 5 = 0
    expect(name).toBe('Fire Imp');
  });

  it('should pick from tier 1 on floors 20-39', () => {
    vi.spyOn(Math, 'random').mockReturnValue(0);
    const name = getEnemyName('fire', 20);
    // tierIndex = min(4, floor(20/20)) = 1
    // randomIndex = (1 + floor(0 * 4)) % 5 = 1
    expect(name).toBe('Flame Sprite');
  });

  it('should pick from tier 2 on floors 40-59', () => {
    vi.spyOn(Math, 'random').mockReturnValue(0);
    const name = getEnemyName('fire', 40);
    // tierIndex = min(4, floor(40/20)) = 2
    // randomIndex = (2 + floor(0 * 3)) % 5 = 2
    expect(name).toBe('Emberling');
  });

  it('should pick from tier 3 on floors 60-79', () => {
    vi.spyOn(Math, 'random').mockReturnValue(0);
    const name = getEnemyName('fire', 60);
    // tierIndex = min(4, floor(60/20)) = 3
    // randomIndex = (3 + floor(0 * 2)) % 5 = 3
    expect(name).toBe('Scorchling');
  });

  it('should handle floor 0', () => {
    // floor 0: tierIndex = min(4, floor(0/20)) = 0
    const name = getEnemyName('fire', 0);
    expect(typeof name).toBe('string');
    expect(name.length).toBeGreaterThan(0);
  });

  it('should handle very high floors', () => {
    const name = getEnemyName('fire', 999);
    expect(typeof name).toBe('string');
    expect(name.length).toBeGreaterThan(0);
  });

  it('should return consistent element pool regardless of floor', () => {
    // All names returned should be from the fire pool
    vi.spyOn(Math, 'random').mockReturnValue(0.5);
    const fireNames = ['Fire Imp', 'Flame Sprite', 'Emberling', 'Scorchling', 'Inferno Whelp'];
    for (let floor = 1; floor <= 100; floor += 10) {
      const name = getEnemyName('fire', floor);
      expect(fireNames).toContain(name);
    }
  });
});

// ─── generateSwarmEnemies ─────────────────────────────────────────────────────

describe('generateSwarmEnemies', () => {
  it('should generate an array of enemies', () => {
    const enemies = generateSwarmEnemies(10);
    expect(Array.isArray(enemies)).toBe(true);
  });

  it('should generate at least SWARM_CONFIG.minEnemies', () => {
    for (let i = 0; i < 20; i++) {
      const enemies = generateSwarmEnemies(10);
      expect(enemies.length).toBeGreaterThanOrEqual(3);
    }
  });

  it('should generate at most SWARM_CONFIG.maxEnemies', () => {
    for (let i = 0; i < 20; i++) {
      const enemies = generateSwarmEnemies(10);
      expect(enemies.length).toBeLessThanOrEqual(6);
    }
  });

  it('each enemy should have positive HP', () => {
    const enemies = generateSwarmEnemies(10);
    for (const enemy of enemies) {
      expect(enemy.hp).toBeGreaterThan(0);
      expect(enemy.maxHP).toBeGreaterThan(0);
    }
  });

  it('each enemy should have hp equal to maxHP', () => {
    const enemies = generateSwarmEnemies(10);
    for (const enemy of enemies) {
      expect(enemy.hp).toBe(enemy.maxHP);
    }
  });

  it('each enemy should have a valid id', () => {
    const enemies = generateSwarmEnemies(10);
    for (let i = 0; i < enemies.length; i++) {
      expect(enemies[i].id).toBe(`enemy_${i}`);
    }
  });

  it('each enemy should have a non-empty name', () => {
    const enemies = generateSwarmEnemies(10);
    for (const enemy of enemies) {
      expect(enemy.name.length).toBeGreaterThan(0);
    }
  });

  it('each enemy should have a valid element from the floor cycle', () => {
    const enemies = generateSwarmEnemies(10);
    for (const enemy of enemies) {
      expect(FLOOR_ELEM_CYCLE).toContain(enemy.element);
    }
  });

  it('should use the correct element for the given floor', () => {
    // Floor 1 → fire (index 0 in FLOOR_ELEM_CYCLE)
    const enemies = generateSwarmEnemies(1);
    for (const enemy of enemies) {
      expect(enemy.element).toBe('fire');
    }
  });

  it('should use the correct element for floor 2 (water)', () => {
    const enemies = generateSwarmEnemies(2);
    for (const enemy of enemies) {
      expect(enemy.element).toBe('water');
    }
  });

  it('should use the correct element for floor 7 (death)', () => {
    const enemies = generateSwarmEnemies(7);
    for (const enemy of enemies) {
      expect(enemy.element).toBe('death');
    }
  });

  it('should use the correct element for floor 8 (cycles back to fire)', () => {
    const enemies = generateSwarmEnemies(8);
    for (const enemy of enemies) {
      expect(enemy.element).toBe('fire');
    }
  });

  it('each enemy should have dodgeChance of 0', () => {
    const enemies = generateSwarmEnemies(10);
    for (const enemy of enemies) {
      expect(enemy.dodgeChance).toBe(0);
    }
  });

  it('each enemy should have armor that scales with floor', () => {
    const enemiesLow = generateSwarmEnemies(5);
    const enemiesHigh = generateSwarmEnemies(50);
    // Low floor: armor = 0 + floor(5/10) * 0.01 = 0
    // High floor: armor = 0 + floor(50/10) * 0.01 = 0.05
    expect(enemiesLow[0].armor).toBe(0);
    expect(enemiesHigh[0].armor).toBeCloseTo(0.05, 5);
  });

  it('swarm enemy HP should be a fraction of floor max HP', () => {
    const enemies = generateSwarmEnemies(10);
    // Each enemy has floorMaxHP * 0.4 (SWARM_CONFIG.hpMultiplier)
    // We can't check exact value without calling getFloorMaxHP, but we can check it's positive
    for (const enemy of enemies) {
      expect(enemy.hp).toBeGreaterThan(0);
      expect(enemy.hp).toBe(enemy.maxHP);
    }
  });

  it('should generate enemies for floor 1', () => {
    const enemies = generateSwarmEnemies(1);
    expect(enemies.length).toBeGreaterThanOrEqual(3);
    for (const enemy of enemies) {
      expect(enemy.hp).toBeGreaterThan(0);
    }
  });

  it('should generate enemies for high floors', () => {
    const enemies = generateSwarmEnemies(100);
    expect(enemies.length).toBeGreaterThanOrEqual(3);
    for (const enemy of enemies) {
      expect(enemy.hp).toBeGreaterThan(0);
    }
  });

  it('barrier should be a number', () => {
    const enemies = generateSwarmEnemies(50);
    for (const enemy of enemies) {
      expect(typeof enemy.barrier).toBe('number');
    }
  });

  it('barrier should be 0 for floors below 20', () => {
    // getEnemyBarrier returns 0 for floor < 20
    const enemies = generateSwarmEnemies(10);
    for (const enemy of enemies) {
      expect(enemy.barrier).toBe(0);
    }
  });
});
