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

describe('DamageAndHealth', () => {
  describe('DamageReducesHealth', () => {
    it('property: health is reduced by the damage amount', () => {
      fc.assert(
        fc.property(
          fc.integer({ min: 0, max: 1000 }),
          fc.integer({ min: 0, max: 10000 }),
          (health, damage) => {
            const attacker = Character.create({ name: 'attacker', level: Level.create(1) });
            const target = Character.createWithHealth({
              name: 'target',
              level: Level.create(1),
              health,
            });
            const expected = Math.max(0, health - damage);
            const result = attacker.dealDamage(target, damage);
            return result.health.value === expected;
          },
        ),
      );
    });
  });

  describe('HealthNonNegative', () => {
    it('property: health never goes below zero after damage', () => {
      fc.assert(
        fc.property(
          fc.integer({ min: 0, max: 1000 }),
          fc.integer({ min: 0, max: 10000 }),
          (health, damage) => {
            const attacker = Character.create({ name: 'attacker', level: Level.create(1) });
            const target = Character.createWithHealth({
              name: 'target',
              level: Level.create(1),
              health,
            });
            const result = attacker.dealDamage(target, damage);
            return result.health.value >= 0;
          },
        ),
      );
    });
  });

  describe('DeathAtZeroHealth', () => {
    it('property: character dies when health reaches zero', () => {
      fc.assert(
        fc.property(
          fc.integer({ min: 0, max: 1000 }),
          fc.integer({ min: 0, max: 10000 }),
          (health, damage) => {
            const attacker = Character.create({ name: 'attacker', level: Level.create(1) });
            const target = Character.createWithHealth({
              name: 'target',
              level: Level.create(1),
              health,
            });
            const result = attacker.dealDamage(target, damage);
            const expected = Math.max(0, health - damage);
            if (expected === 0) {
              return result.status.kind === 'dead';
            }
            return result.status.kind === 'alive';
          },
        ),
      );
    });
  });

  describe('SelfDamageForbidden', () => {
    it('property: a character cannot deal damage to itself — returns same reference, state unchanged', () => {
      fc.assert(
        fc.property(
          fc.integer({ min: 0, max: 1000 }),
          fc.integer({ min: 0, max: 10000 }),
          (health, damage) => {
            const c = Character.createWithHealth({ name: 'hero', level: Level.create(1), health });
            const healthBefore = c.health.value;
            const statusBefore = c.status.kind;
            const result = c.dealDamage(c, damage);
            // Should return the same reference
            return (
              result === c &&
              result.health.value === healthBefore &&
              result.status.kind === statusBefore
            );
          },
        ),
      );
    });
  });

  describe('DeadCannotTakeDamage', () => {
    it('property: dead characters cannot take damage — returns same reference, state unchanged', () => {
      fc.assert(
        fc.property(fc.integer({ min: 0, max: 10000 }), (damage) => {
          const attacker = Character.create({ name: 'attacker', level: Level.create(1) });
          const target = Character.create({ name: 'target', level: Level.create(1) });
          // Kill the target first — capture the returned (dead) character
          const deadTarget = attacker.dealDamage(target, 10000);
          const healthBefore = deadTarget.health.value;
          const statusBefore = deadTarget.status.kind;
          // Then try to deal more damage to the dead character
          const result = attacker.dealDamage(deadTarget, damage);
          return (
            result === deadTarget &&
            result.health.value === healthBefore &&
            result.status.kind === statusBefore
          );
        }),
      );
    });
  });

  describe('NegativeDamageForbidden', () => {
    it('property: negative damage throws an error', () => {
      fc.assert(
        fc.property(fc.integer({ min: -10000, max: -1 }), (negativeDamage) => {
          const attacker = Character.create({ name: 'attacker', level: Level.create(1) });
          const target = Character.create({ name: 'target', level: Level.create(1) });
          let threw = false;
          try {
            attacker.dealDamage(target, negativeDamage);
          } catch {
            threw = true;
          }
          return threw;
        }),
      );
    });
  });
});
