using Theriapolis.Core.Data;
using Theriapolis.Core.Items;
using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.Rules.Stats;
using Xunit;

namespace Theriapolis.Tests.Rules;

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

    [Fact]
    public void ArmorClass_Unarmored_Is10PlusDexMod()
    {
        var c = MakeCharacter(dex: 14);  // DEX 15 after wolf+canid mods (DEX +0 species, +0 clade) → final DEX 14, +2 mod
        // Wolf-Folk: STR+1, no DEX mod from species; canid: CON+1, WIS+1.
        // Base DEX 14 → final 14 → mod +2.
        // Unarmored: 10 + 2 = 12.
        Assert.Equal(12, DerivedStats.ArmorClass(c));
    }

    [Fact]
    public void ArmorClass_LightArmor_AddsBaseAndDex()
    {
        var c = MakeCharacter(dex: 14);
        var hide = c.Inventory.Add(_content.Items["hide_vest"]);
        c.Inventory.TryEquip(hide, EquipSlot.Body, out _);
        // Hide vest base 11, max DEX -1 (unlimited), DEX mod +2 → AC 13.
        Assert.Equal(13, DerivedStats.ArmorClass(c));
    }

    [Fact]
    public void ArmorClass_MediumArmor_CapsDex()
    {
        var c = MakeCharacter(dex: 18);  // base 18 → final 18 → mod +4
        var chain = c.Inventory.Add(_content.Items["chain_shirt"]);
        c.Inventory.TryEquip(chain, EquipSlot.Body, out _);
        // Chain shirt base 13, max DEX 2 → effective DEX bonus 2 → AC 15.
        Assert.Equal(15, DerivedStats.ArmorClass(c));
    }

    [Fact]
    public void ArmorClass_HeavyArmor_IgnoresDex()
    {
        var c = MakeCharacter(dex: 18);
        var mail = c.Inventory.Add(_content.Items["chain_mail"]);
        c.Inventory.TryEquip(mail, EquipSlot.Body, out _);
        // Chain mail base 16, max DEX 0 → AC 16.
        Assert.Equal(16, DerivedStats.ArmorClass(c));
    }

    [Fact]
    public void ArmorClass_ShieldAdds()
    {
        var c = MakeCharacter(dex: 14);
        var chain = c.Inventory.Add(_content.Items["chain_shirt"]);
        c.Inventory.TryEquip(chain, EquipSlot.Body, out _);
        var shield = c.Inventory.Add(_content.Items["standard_shield"]);
        c.Inventory.TryEquip(shield, EquipSlot.OffHand, out _);
        // 13 + min(2, 2) + 2 (shield) = 17.
        Assert.Equal(17, DerivedStats.ArmorClass(c));
    }

    [Fact]
    public void Speed_BaseMatchesSpecies()
    {
        var c = MakeCharacter();
        Assert.Equal(c.Species.BaseSpeedFt, DerivedStats.SpeedFt(c));
    }

    [Fact]
    public void CarryCapacity_StrTimes15TimesSizeMult()
    {
        var c = MakeCharacter(str: 14);   // STR 15 after wolf+1
        // Wolf-Folk is medium_large → mult = 1.0
        Assert.Equal(15f * 15f * 1.0f, DerivedStats.CarryCapacityLb(c));
    }

    [Fact]
    public void Encumbrance_LightWhenWellUnderCap()
    {
        var c = MakeCharacter(str: 14);
        c.Inventory.Add(_content.Items["fang_knife"]);  // 0.5 lb
        Assert.Equal(DerivedStats.EncumbranceBand.Light, DerivedStats.Encumbrance(c));
        Assert.Equal(1.0f, DerivedStats.TacticalSpeedMult(c));
    }

    [Fact]
    public void Encumbrance_HeavyWhenOverSoftThreshold()
    {
        var c = MakeCharacter(str: 8);   // STR 9 after wolf+1, cap = 9 * 15 = 135 lb
        for (int i = 0; i < 60; i++) c.Inventory.Add(_content.Items["chain_mail"]);  // 60 * 40 lb = 2400 lb
        var enc = DerivedStats.Encumbrance(c);
        Assert.Equal(DerivedStats.EncumbranceBand.Over, enc);
        Assert.Equal(0.5f, DerivedStats.TacticalSpeedMult(c));
    }

    [Fact]
    public void Speed_DropsWhenEncumbered()
    {
        var c = MakeCharacter(str: 8);
        int baseSpeed = DerivedStats.SpeedFt(c);
        // Pile on chain mail to push past the hard threshold (1.5x cap).
        for (int i = 0; i < 60; i++) c.Inventory.Add(_content.Items["chain_mail"]);
        int encSpeed = DerivedStats.SpeedFt(c);
        Assert.True(encSpeed < baseSpeed, $"Encumbered speed ({encSpeed}) should be less than base ({baseSpeed})");
    }

    [Fact]
    public void Initiative_EqualsDexMod()
    {
        var c = MakeCharacter(dex: 18);
        // DEX 18 → mod +4 (no DEX mod from wolf-folk or canid clade)
        Assert.Equal(4, DerivedStats.Initiative(c));
    }

    private Character MakeCharacter(int str = 10, int dex = 10, int con = 10, int @int = 10, int wis = 10, int cha = 10)
    {
        var b = new CharacterBuilder
        {
            Clade        = _content.Clades["canidae"],
            Species      = _content.Species["wolf"],
            ClassDef     = _content.Classes["fangsworn"],
            Background   = _content.Backgrounds["pack_raised"],
            BaseAbilities = new AbilityScores(str, dex, con, @int, wis, cha),
            Name         = "Test",
        };
        b.ChosenClassSkills.Add(SkillId.Athletics);
        b.ChosenClassSkills.Add(SkillId.Intimidation);
        return b.Build();  // no starting kit — tests build inventory explicitly
    }
}
