import fc from 'fast-check';
import { describe, it } from 'vitest';
import { Character } from './characters/Character.ts';
import { Level } from './value-objects/Level.ts';

describe('Levels', () => {
  describe('CloseLevelNoModifier', () => {
    it('property: level gap < 5 → no modifier applied', () => {
      fc.assert(
        fc.property(
          fc.integer({ min: 1, max: 10 }),
          fc.integer({ min: 1, max: 10 }),
          fc.integer({ min: 0, max: 10000 }),
          (attackerLevel, targetLevel, baseDamage) => {
            const diff = Math.abs(attackerLevel - targetLevel);
            if (diff >= 5) return true; // skip non-applicable cases

            const attacker = Character.create({ name: 'a', level: Level.create(attackerLevel) });
            const target = Character.createWithHealth({
              name: 't',
              level: Level.create(targetLevel),
              health: 1000,
            });
            const result = attacker.dealDamage(target, baseDamage);
            return result.health.value === Math.max(0, 1000 - baseDamage);
          },
        ),
      );
    });
  });

  describe('HighLevelTargetModifier', () => {
    it('property: target ≥5 levels above → damage halved (floored)', () => {
      fc.assert(
        fc.property(
          fc.integer({ min: 1, max: 5 }),
          fc.integer({ min: 6, max: 10 }),
          fc.integer({ min: 0, max: 10000 }),
          (attackerLevel, targetLevel, baseDamage) => {
            const diff = targetLevel - attackerLevel;
            if (diff < 5) return true; // skip non-applicable cases

            const attacker = Character.create({ name: 'a', level: Level.create(attackerLevel) });
            const target = Character.createWithHealth({
              name: 't',
              level: Level.create(targetLevel),
              health: 1000,
            });
            const expectedDamage = Math.floor(baseDamage * 0.5);
            const result = attacker.dealDamage(target, baseDamage);
            return result.health.value === Math.max(0, 1000 - expectedDamage);
          },
        ),
      );
    });

    it('property: odd damage halved floors correctly', () => {
      fc.assert(
        fc.property(fc.integer({ min: 1, max: 9999 }), (oddDamage) => {
          if (oddDamage % 2 === 0) return true; // only odd values

          const attacker = Character.create({ name: 'a', level: Level.create(1) });
          const target = Character.createWithHealth({
            name: 't',
            level: Level.create(6),
            health: 1000,
          });
          const expectedDamage = Math.floor(oddDamage * 0.5);
          const result = attacker.dealDamage(target, oddDamage);
          return result.health.value === Math.max(0, 1000 - expectedDamage);
        }),
      );
    });
  });

  describe('LowLevelTargetModifier', () => {
    it('property: target ≥5 levels below → damage ×1.5 (floored)', () => {
      fc.assert(
        fc.property(
          fc.integer({ min: 6, max: 10 }),
          fc.integer({ min: 1, max: 5 }),
          fc.integer({ min: 0, max: 10000 }),
          (attackerLevel, targetLevel, baseDamage) => {
            const diff = attackerLevel - targetLevel;
            if (diff < 5) return true; // skip non-applicable cases

            const attacker = Character.create({ name: 'a', level: Level.create(attackerLevel) });
            const target = Character.createWithHealth({
              name: 't',
              level: Level.create(targetLevel),
              health: 1000,
            });
            const expectedDamage = Math.floor(baseDamage * 1.5);
            const result = attacker.dealDamage(target, baseDamage);
            return result.health.value === Math.max(0, 1000 - expectedDamage);
          },
        ),
      );
    });
  });

  describe('LevelDiffBoundary', () => {
    it('property: exactly 5 levels above → modifier applies (halved)', () => {
      fc.assert(
        fc.property(fc.integer({ min: 0, max: 10000 }), (damage) => {
          // Level 1 vs Level 6 → diff = -5
          const attacker = Character.create({ name: 'a', level: Level.create(1) });
          const target = Character.createWithHealth({
            name: 't',
            level: Level.create(6),
            health: 1000,
          });
          const expectedDamage = Math.floor(damage * 0.5);
          const result = attacker.dealDamage(target, damage);
          return result.health.value === Math.max(0, 1000 - expectedDamage);
        }),
      );
    });

    it('property: exactly 5 levels below → modifier applies (×1.5)', () => {
      fc.assert(
        fc.property(fc.integer({ min: 0, max: 10000 }), (damage) => {
          // Level 6 vs Level 1 → diff = 5
          const attacker = Character.create({ name: 'a', level: Level.create(6) });
          const target = Character.createWithHealth({
            name: 't',
            level: Level.create(1),
            health: 1000,
          });
          const expectedDamage = Math.floor(damage * 1.5);
          const result = attacker.dealDamage(target, damage);
          return result.health.value === Math.max(0, 1000 - expectedDamage);
        }),
      );
    });

    it('property: 4 levels → no modifier', () => {
      fc.assert(
        fc.property(fc.integer({ min: 0, max: 10000 }), (damage) => {
          // Level 1 vs Level 5 → diff = 4
          const attacker = Character.create({ name: 'a', level: Level.create(1) });
          const target = Character.createWithHealth({
            name: 't',
            level: Level.create(5),
            health: 1000,
          });
          const result = attacker.dealDamage(target, damage);
          return result.health.value === Math.max(0, 1000 - damage);
        }),
      );
    });

    it('property: 4 levels below → no modifier', () => {
      fc.assert(
        fc.property(fc.integer({ min: 0, max: 10000 }), (damage) => {
          // Level 5 vs Level 1 → diff = 4
          const attacker = Character.create({ name: 'a', level: Level.create(5) });
          const target = Character.createWithHealth({
            name: 't',
            level: Level.create(1),
            health: 1000,
          });
          const result = attacker.dealDamage(target, damage);
          return result.health.value === Math.max(0, 1000 - damage);
        }),
      );
    });
  });

  describe('LevelDiff', () => {
    it('property: diff(target) = this.value - target.value', () => {
      fc.assert(
        fc.property(fc.integer({ min: 1, max: 10 }), fc.integer({ min: 1, max: 10 }), (a, b) => {
          const levelA = Level.create(a);
          const levelB = Level.create(b);
          return levelA.diff(levelB) === a - b;
        }),
      );
    });
  });
});
