import { describe, it, expect } from 'vitest';
import { dnd5e } from './dnd5e';
import { pf2e } from './pf2e';
import { applyRest } from './rest';
import type { CharacterRulesInput } from './types';
import type { Character } from '@/lib/schemas/character';
import { characterDefaults } from '@/lib/schemas/character';

const fighter5e: CharacterRulesInput = {
  level: 5,
  abilities: { str: 18, dex: 14, con: 16, int: 10, wis: 12, cha: 8 },
  skillRanks: { perception: 'trained' },
};

describe('5e derived combat math', () => {
  it('weapon attack: ability + proficiency + item, ability added to damage', () => {
    const r = dnd5e.weaponAttack(fighter5e, { ability: 'str', rank: 'trained', damageDice: '1d8', itemBonus: 1 });
    expect(r.toHit).toBe(4 + 3 + 1); // STR +4, PB +3, item +1
    expect(r.damage).toBe('1d8+5'); // STR +4 + item +1
  });

  it('off-hand damage omits the ability modifier', () => {
    const r = dnd5e.weaponAttack(fighter5e, { ability: 'str', rank: 'trained', damageDice: '1d6', addAbilityToDamage: false });
    expect(r.damage).toBe('1d6');
  });

  it('spell attack and passive perception', () => {
    expect(dnd5e.spellAttackBonus(fighter5e, 'wis')).toBe(3 + 1);
    expect(dnd5e.passivePerception(fighter5e)).toBe(10 + 1 + 3); // 10 + WIS + PB
  });

  it('carrying capacity is STR*15', () => {
    expect(dnd5e.carryingCapacity(fighter5e)).toEqual({ encumbered: 90, max: 270, unit: 'lb' });
  });
});

describe('pf2e derived combat math', () => {
  const pcg: CharacterRulesInput = {
    level: 3,
    abilities: { str: 18, dex: 12, con: 14, int: 10, wis: 14, cha: 10 },
    perceptionRank: 'expert',
    spellcastingRank: 'trained',
  };
  it('weapon attack adds level + rank bonus', () => {
    const r = pf2e.weaponAttack(pcg, { ability: 'str', rank: 'trained', damageDice: '1d8' });
    expect(r.toHit).toBe(4 + (3 + 2)); // STR +4, trained = level3 + 2
    expect(r.damage).toBe('1d8+4');
  });
  it('passive perception uses perception proficiency', () => {
    expect(pf2e.passivePerception(pcg)).toBe(10 + 2 + (3 + 4)); // 10 + WIS + (level + expert)
  });
  it('carrying capacity is Bulk', () => {
    expect(pf2e.carryingCapacity(pcg)).toEqual({ encumbered: 5 + 4, max: 10 + 4, unit: 'Bulk' });
  });
});

function makeCharacter(): Character {
  return {
    id: 'c', campaignId: 'camp', system: '5e', kind: 'pc',
    name: 'Test', ancestry: '', className: 'Wizard', level: 5,
    abilities: { str: 8, dex: 14, con: 12, int: 16, wis: 10, cha: 10 },
    hp: { current: 4, max: 30, temp: 0 }, speed: 30, armorBonus: 0,
    skillRanks: {}, saveRanks: {}, perceptionRank: 'trained',
    ...characterDefaults(),
    resources: [
      { id: 'r1', name: 'Sorcery Points', current: 0, max: 5, recovery: 'long' },
      { id: 'r2', name: 'Channel Divinity', current: 0, max: 1, recovery: 'short' },
    ],
    spellcasting: {
      slots: [{ level: 1, max: 4, current: 0 }, { level: 2, max: 3, current: 1 }],
      pact: { level: 1, max: 2, current: 0 },
      spells: [],
    },
    defenses: { ...characterDefaults().defenses, exhaustion: 2 },
    notes: '', createdAt: '', updatedAt: '',
  };
}

describe('applyRest', () => {
  it('short rest restores short resources + pact, not HP/slots/long resources', () => {
    const c = makeCharacter();
    const short = dnd5e.restOptions.find((o) => o.id === 'short')!;
    const patch = applyRest(c, short);
    expect(patch.hp).toBeUndefined();
    expect(patch.resources!.find((r) => r.id === 'r2')!.current).toBe(1); // short resource restored
    expect(patch.resources!.find((r) => r.id === 'r1')!.current).toBe(0); // long resource untouched
    expect(patch.spellcasting!.pact!.current).toBe(2); // pact restored
    expect(patch.spellcasting!.slots[0]!.current).toBe(0); // slots NOT restored on short rest
  });

  it('long rest restores HP, all slots, all resources, steps exhaustion down, clears death saves', () => {
    const c = makeCharacter();
    const long = dnd5e.restOptions.find((o) => o.id === 'long')!;
    const patch = applyRest(c, long);
    expect(patch.hp!.current).toBe(30);
    expect(patch.resources!.every((r) => r.current === r.max)).toBe(true);
    expect(patch.spellcasting!.slots.every((s) => s.current === s.max)).toBe(true);
    expect(patch.defenses!.exhaustion).toBe(1);
    expect(patch.defenses!.deathSaves).toEqual({ successes: 0, failures: 0 });
  });
});
