using Theriapolis.Core.Data;
using Theriapolis.Core.Rules.Combat;
using Theriapolis.Core.Rules.Stats;
using Theriapolis.Core.Util;
using Xunit;

namespace Theriapolis.Tests.Combat;

public sealed class DeathSaveTrackerTests
{
    private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory));

    [Fact]
    public void ApplyDamage_PlayerDroppedToZero_InstallsDeathSaveTracker()
    {
        var (player, _) = MakeFight();
        Resolver.ApplyDamage(player, player.MaxHp + 50);
        Assert.Equal(0, player.CurrentHp);
        Assert.NotNull(player.DeathSaves);
        Assert.True(player.IsDown);
        Assert.True(player.IsAlive); // unconscious-but-not-dead is "alive"
    }

    [Fact]
    public void Heal_AbovZero_ResetsDeathSaves()
    {
        var (player, _) = MakeFight();
        Resolver.ApplyDamage(player, player.MaxHp + 5);
        player.DeathSaves!.Roll(MakeEnc(player), player); // 1 fail or success
        Resolver.Heal(player, 5);
        Assert.True(player.CurrentHp > 0);
        Assert.Equal(0, player.DeathSaves.Successes);
        Assert.Equal(0, player.DeathSaves.Failures);
    }

    [Fact]
    public void ThreeFailures_MarkDead()
    {
        var t = new DeathSaveTracker();
        // Use an encounter with a fixed seed and roll until we accumulate 3 failures
        // — then assert the Dead flag is set.
        var enc = MakeEncFromScratch(0x10UL);  // arbitrary seed
        var dummy = MakeDummyCombatant(enc);
        for (int i = 0; i < 50 && !t.Dead && !t.Stabilised; i++)
            t.Roll(enc, dummy);
        // Across 50 rolls (and with the 3-fail / 3-success thresholds) we always
        // resolve to one of the terminal states. Either is acceptable for this
        // smoke test; the important thing is the loop terminates and counters
        // can advance.
        Assert.True(t.Dead || t.Stabilised);
    }

    [Fact]
    public void Roll_Natural20_RevivesAtOneHp()
    {
        // Find a seed whose first death-save d20 is 20. Probe deterministically.
        for (int s = 0; s < 200; s++)
        {
            var enc = MakeEncFromScratch((ulong)s);
            var pc = MakeDummyCombatant(enc);
            pc.CurrentHp = 0;
            pc.Conditions.Add(Condition.Unconscious);
            pc.DeathSaves = new DeathSaveTracker();
            var outcome = pc.DeathSaves.Roll(enc, pc);
            if (outcome == DeathSaveOutcome.CriticalRevive)
            {
                Assert.Equal(1, pc.CurrentHp);
                Assert.DoesNotContain(Condition.Unconscious, pc.Conditions);
                Assert.Equal(0, pc.DeathSaves.Successes);
                Assert.Equal(0, pc.DeathSaves.Failures);
                return;
            }
        }
        Assert.Fail("Couldn't find a seed producing natural 20 in 200 attempts.");
    }

    // ── Helpers ──────────────────────────────────────────────────────────

    private (Combatant pc, Combatant foe) MakeFight()
    {
        var clade   = _content.Clades["canidae"];
        var species = _content.Species["wolf"];
        var classDef= _content.Classes["fangsworn"];
        var bg      = _content.Backgrounds["pack_raised"];
        var character = new Theriapolis.Core.Rules.Character.CharacterBuilder
        {
            Clade = clade, Species = species, ClassDef = classDef, Background = bg,
            BaseAbilities = new AbilityScores(15, 14, 13, 12, 10, 8),
        }
        .ChooseSkill(SkillId.Athletics)
        .ChooseSkill(SkillId.Intimidation)
        .Build(_content.Items);

        var pc = Combatant.FromCharacter(character, 1, "PC", new Vec2(0, 0),
            Theriapolis.Core.Rules.Character.Allegiance.Player);
        var foe = Combatant.FromNpcTemplate(_content.Npcs.Templates.First(t => t.Id == "wolf"),
            2, new Vec2(1, 0));
        return (pc, foe);
    }

    private Encounter MakeEnc(Combatant pc)
    {
        var foe = Combatant.FromNpcTemplate(_content.Npcs.Templates.First(t => t.Id == "wolf"), 2, new Vec2(1, 0));
        return new Encounter(0xCAFEUL, 1, new[] { pc, foe });
    }

    private Encounter MakeEncFromScratch(ulong seed)
    {
        var hero = Combatant.FromNpcTemplate(_content.Npcs.Templates.First(t => t.Id == "brigand_footpad"), 1, new Vec2(0, 0));
        var foe  = Combatant.FromNpcTemplate(_content.Npcs.Templates.First(t => t.Id == "wolf"),            2, new Vec2(1, 0));
        return new Encounter(seed, 1, new[] { hero, foe });
    }

    private Combatant MakeDummyCombatant(Encounter enc)
        => enc.Participants[0];
}
