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

namespace Theriapolis.Tests.Dungeons;

/// <summary>
/// Phase 7 M0 — verifies the headless level-up loop the
/// <c>character-roll --level N</c> Tools flag uses. The Tools command
/// re-creates this loop in <see cref="Theriapolis.Tools.Commands.CharacterRoll"/>;
/// this test asserts the API contract works deterministically without
/// invoking the Tools assembly directly.
/// </summary>
public sealed class CharacterRollLevelFlagTests
{
    private static (Character pc, IReadOnlyDictionary<string, SubclassDef> subs) BuildBase()
    {
        var loader  = new ContentLoader(TestHelpers.DataDirectory);
        var content = new ContentResolver(loader);

        var b = new CharacterBuilder
        {
            Clade         = content.Clades["canidae"],
            Species       = content.Species["wolf"],
            ClassDef      = content.Classes["fangsworn"],
            Background    = content.Backgrounds["pack_raised"],
            BaseAbilities = new AbilityScores(15, 14, 13, 10, 12, 8),
            Name          = "Test",
        };
        int n = b.ClassDef.SkillsChoose;
        foreach (var raw in b.ClassDef.SkillOptions)
        {
            if (b.ChosenClassSkills.Count >= n) break;
            try { b.ChosenClassSkills.Add(SkillIdExtensions.FromJson(raw)); } catch { }
        }
        Assert.True(b.Validate(out _));
        return (b.Build(content.Items), content.Subclasses);
    }

    private static Character LevelTo(int target, ulong worldSeed = 12345UL, ulong msOverride = 0UL)
    {
        var (pc, subs) = BuildBase();
        for (int lv = 2; lv <= target; lv++)
        {
            ulong seed = worldSeed ^ msOverride ^ C.RNG_LEVELUP ^ (ulong)lv;
            var result = LevelUpFlow.Compute(pc, lv, seed, takeAverage: true, subclasses: subs);
            var choices = new LevelUpChoices
            {
                TakeAverageHp = true,
                SubclassId    = result.GrantsSubclassChoice && pc.ClassDef.SubclassIds.Length > 0
                                ? pc.ClassDef.SubclassIds[0]
                                : null,
            };
            if (result.GrantsAsiChoice)
                choices.AsiAdjustments[AbilityId.CON] = 2;
            pc.ApplyLevelUp(result, choices);
        }
        return pc;
    }

    [Fact]
    public void LevelN_ProducesExpectedLevelAndProficiency()
    {
        var pc1 = LevelTo(1);
        Assert.Equal(1, pc1.Level);
        Assert.Equal(2, pc1.ProficiencyBonus);

        var pc5 = LevelTo(5);
        Assert.Equal(5, pc5.Level);
        Assert.Equal(3, pc5.ProficiencyBonus);

        var pc11 = LevelTo(11);
        Assert.Equal(11, pc11.Level);
        Assert.Equal(4, pc11.ProficiencyBonus);
    }

    [Fact]
    public void LevelN_PicksSubclassAtLevelThree()
    {
        var pc3 = LevelTo(3);
        Assert.Equal(3, pc3.Level);
        Assert.False(string.IsNullOrEmpty(pc3.SubclassId),
            "level-3 character must have a subclass selected");
    }

    [Fact]
    public void LevelN_AppliesAsiAtLevelFour()
    {
        // Auto-pilot ASI puts +2 to CON at level 4 (one of the C.ASI_LEVELS).
        // Compare CON pre/post — clade + species mods are baked in by the
        // builder, so absolute values vary by build choices but the
        // delta is exactly 2.
        var pc3 = LevelTo(3);
        var pc4 = LevelTo(4);
        Assert.Equal(pc3.Abilities.CON + 2, pc4.Abilities.CON);
    }

    [Fact]
    public void LevelN_IsDeterministic()
    {
        var a = LevelTo(7, worldSeed: 99UL, msOverride: 12345UL);
        var b = LevelTo(7, worldSeed: 99UL, msOverride: 12345UL);
        Assert.Equal(a.Level,    b.Level);
        Assert.Equal(a.MaxHp,    b.MaxHp);
        Assert.Equal(a.SubclassId, b.SubclassId);
    }
}
