using Theriapolis.Core.Rules.Stats;
using Xunit;

namespace Theriapolis.Tests.Rules;

public sealed class AbilityScoreTests
{
    [Theory]
    [InlineData(1,  -5)]
    [InlineData(8,  -1)]
    [InlineData(9,  -1)]
    [InlineData(10,  0)]
    [InlineData(11,  0)]
    [InlineData(12,  1)]
    [InlineData(15,  2)]
    [InlineData(18,  4)]
    [InlineData(20,  5)]
    [InlineData(30, 10)]
    public void Mod_MatchesD20Table(int score, int expected)
    {
        Assert.Equal(expected, AbilityScores.Mod(score));
    }

    [Fact]
    public void Mod_FloorsTowardNegativeInfinity()
    {
        // Score 9 → -1, score 7 → -2 (per d20 floor convention; not C# truncate)
        Assert.Equal(-1, AbilityScores.Mod(9));
        Assert.Equal(-2, AbilityScores.Mod(7));
        Assert.Equal(-3, AbilityScores.Mod(5));
    }

    [Fact]
    public void Constructor_ClampsToValidRange()
    {
        var a = new AbilityScores(0, 31, 50, -10, 100, 18);
        Assert.Equal(1,  a.STR);    // 0 clamped up to 1
        Assert.Equal(30, a.DEX);    // 31 clamped down to 30
        Assert.Equal(30, a.CON);
        Assert.Equal(1,  a.INT);    // -10 clamped up to 1
        Assert.Equal(30, a.WIS);
        Assert.Equal(18, a.CHA);
    }

    [Fact]
    public void StandardArray_IsCanonical()
    {
        Assert.Equal(new[] { 15, 14, 13, 12, 10, 8 }, AbilityScores.StandardArray);
    }

    [Fact]
    public void Get_ReturnsValueByAbilityId()
    {
        var a = new AbilityScores(11, 13, 15, 10, 12, 14);
        Assert.Equal(11, a.Get(AbilityId.STR));
        Assert.Equal(13, a.Get(AbilityId.DEX));
        Assert.Equal(15, a.Get(AbilityId.CON));
        Assert.Equal(10, a.Get(AbilityId.INT));
        Assert.Equal(12, a.Get(AbilityId.WIS));
        Assert.Equal(14, a.Get(AbilityId.CHA));
    }

    [Fact]
    public void With_ReturnsNewBlock_LeavesOriginalUnchanged()
    {
        var a = new AbilityScores(10, 10, 10, 10, 10, 10);
        var b = a.With(AbilityId.STR, 18);
        Assert.Equal(10, a.STR);  // unchanged
        Assert.Equal(18, b.STR);
        Assert.Equal(10, b.DEX);  // others copied
    }

    [Fact]
    public void Plus_AppliesAllMods()
    {
        var a = new AbilityScores(10, 10, 10, 10, 10, 10);
        var mods = new Dictionary<AbilityId, int> { { AbilityId.STR, 1 }, { AbilityId.WIS, 2 } };
        var b = a.Plus(mods);
        Assert.Equal(11, b.STR);
        Assert.Equal(12, b.WIS);
        Assert.Equal(10, b.DEX);
    }

    [Fact]
    public void ModFor_UsesStandardFormula()
    {
        var a = new AbilityScores(15, 14, 13, 12, 10, 8);
        Assert.Equal( 2, a.ModFor(AbilityId.STR));   // (15-10)/2 = 2
        Assert.Equal( 2, a.ModFor(AbilityId.DEX));   // (14-10)/2 = 2
        Assert.Equal( 1, a.ModFor(AbilityId.CON));   // (13-10)/2 = 1
        Assert.Equal( 1, a.ModFor(AbilityId.INT));
        Assert.Equal( 0, a.ModFor(AbilityId.WIS));
        Assert.Equal(-1, a.ModFor(AbilityId.CHA));
    }
}
