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

namespace Theriapolis.Tests.Rules;

/// <summary>
/// Phase 6.5 M0 — LevelUpFlow + Character.ApplyLevelUp coverage.
///
/// LevelUpFlow.Compute is pure; same (character, level, seed) → same payload.
/// ApplyLevelUp mutates in place; per-level deltas land on Level, MaxHp,
/// CurrentHp, LearnedFeatureIds, LevelUpHistory, and (when applicable)
/// SubclassId / Abilities.
/// </summary>
public sealed class LevelUpFlowTests
{
    private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory));

    private Character MakeWolfFangsworn(int con = 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(15, 12, con, 10, 13, 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 { }
        }
        return b.Build();
    }

    [Fact]
    public void CanLevelUp_FalseAtZeroXp()
    {
        var c = MakeWolfFangsworn();
        Assert.Equal(1, c.Level);
        Assert.Equal(0, c.Xp);
        Assert.False(LevelUpFlow.CanLevelUp(c));
    }

    [Fact]
    public void CanLevelUp_TrueAt300Xp()
    {
        var c = MakeWolfFangsworn();
        c.Xp = 300;
        Assert.True(LevelUpFlow.CanLevelUp(c));
    }

    [Fact]
    public void CanLevelUp_FalseAtLevelCap()
    {
        var c = MakeWolfFangsworn();
        c.Level = C.CHARACTER_LEVEL_MAX;
        c.Xp    = 999_999;
        Assert.False(LevelUpFlow.CanLevelUp(c));
    }

    [Fact]
    public void Compute_TakeAverage_ReturnsExpectedHpGain()
    {
        var c = MakeWolfFangsworn(con: 14);  // Wolf+canid: con+1 → 15 → +2 mod
        var result = LevelUpFlow.Compute(c, targetLevel: 2, seed: 0xCAFE, takeAverage: true);
        // Fangsworn d10: average rounded up = 6. CON mod = +2. → 8
        Assert.Equal(2, result.NewLevel);
        Assert.Equal(8, result.HpGained);
        Assert.True(result.HpWasAveraged);
        Assert.False(result.GrantsAsiChoice);
        Assert.False(result.GrantsSubclassChoice);
    }

    [Fact]
    public void Compute_RolledHp_IsDeterministicForSameSeed()
    {
        var c = MakeWolfFangsworn();
        var a = LevelUpFlow.Compute(c, targetLevel: 2, seed: 0x1234, takeAverage: false);
        var b = LevelUpFlow.Compute(c, targetLevel: 2, seed: 0x1234, takeAverage: false);
        Assert.Equal(a.HpGained, b.HpGained);
        Assert.Equal(a.HpHitDieResult, b.HpHitDieResult);
    }

    [Fact]
    public void Compute_RolledHp_DifferentSeedsCanProduceDifferentResults()
    {
        var c = MakeWolfFangsworn();
        var seen = new HashSet<int>();
        for (ulong s = 1; s <= 50; s++)
        {
            var r = LevelUpFlow.Compute(c, targetLevel: 2, seed: s, takeAverage: false);
            seen.Add(r.HpHitDieResult);
        }
        // With 50 different seeds and a d10, we should see at least 3 distinct rolls.
        Assert.True(seen.Count >= 3, $"Expected variance across 50 seeds; saw {seen.Count} distinct rolls.");
    }

    [Fact]
    public void Compute_Level3_GrantsSubclassChoice()
    {
        var c = MakeWolfFangsworn();
        c.Level = 2;
        var result = LevelUpFlow.Compute(c, targetLevel: 3, seed: 0xCAFE);
        Assert.True(result.GrantsSubclassChoice);
        // Fangsworn has at least one subclass id in classes.json.
        Assert.NotEmpty(c.ClassDef.SubclassIds);
    }

    [Fact]
    public void Compute_Level3_DoesNotGrantSubclassChoice_IfAlreadyPicked()
    {
        var c = MakeWolfFangsworn();
        c.Level = 2;
        c.SubclassId = "pack_forged";  // already picked somehow
        var result = LevelUpFlow.Compute(c, targetLevel: 3, seed: 0xCAFE);
        Assert.False(result.GrantsSubclassChoice);
    }

    [Fact]
    public void Compute_Level4_GrantsAsiChoice()
    {
        var c = MakeWolfFangsworn();
        c.Level = 3;
        var result = LevelUpFlow.Compute(c, targetLevel: 4, seed: 0xCAFE);
        Assert.True(result.GrantsAsiChoice);
    }

    [Fact]
    public void Compute_ProficiencyBonus_FollowsTable()
    {
        var c = MakeWolfFangsworn();
        Assert.Equal(2, LevelUpFlow.Compute(c, targetLevel: 2, seed: 0).NewProficiencyBonus);
        Assert.Equal(3, LevelUpFlow.Compute(c, targetLevel: 5, seed: 0).NewProficiencyBonus);
        Assert.Equal(4, LevelUpFlow.Compute(c, targetLevel: 9, seed: 0).NewProficiencyBonus);
        Assert.Equal(5, LevelUpFlow.Compute(c, targetLevel: 13, seed: 0).NewProficiencyBonus);
        Assert.Equal(6, LevelUpFlow.Compute(c, targetLevel: 17, seed: 0).NewProficiencyBonus);
    }

    [Fact]
    public void Compute_FeaturesUnlocked_FollowsLevelTable()
    {
        var c = MakeWolfFangsworn();
        var lv2 = LevelUpFlow.Compute(c, targetLevel: 2, seed: 0);
        // Fangsworn level 2 grants Action Surge per the level table.
        Assert.NotEmpty(lv2.ClassFeaturesUnlocked);
    }

    [Fact]
    public void ApplyLevelUp_MutatesLevelHpAndHistory()
    {
        var c = MakeWolfFangsworn(con: 10);  // canid +1 → CON 11 → mod 0
        int hpBefore = c.MaxHp;
        var result = LevelUpFlow.Compute(c, 2, 0xCAFE);
        c.ApplyLevelUp(result, new LevelUpChoices { TakeAverageHp = true });

        Assert.Equal(2, c.Level);
        Assert.Equal(hpBefore + result.HpGained, c.MaxHp);
        Assert.Equal(c.MaxHp, c.CurrentHp);  // level-up restores HP
        Assert.Single(c.LevelUpHistory);
        Assert.Equal(2, c.LevelUpHistory[0].Level);
    }

    [Fact]
    public void ApplyLevelUp_RecordsClassFeatures()
    {
        var c = MakeWolfFangsworn();
        var result = LevelUpFlow.Compute(c, 2, 0xCAFE);
        int beforeCount = c.LearnedFeatureIds.Count;
        c.ApplyLevelUp(result, new LevelUpChoices());
        Assert.Equal(beforeCount + result.ClassFeaturesUnlocked.Length, c.LearnedFeatureIds.Count);
    }

    [Fact]
    public void ApplyLevelUp_WithSubclassChoice_WritesSubclassId()
    {
        var c = MakeWolfFangsworn();
        c.Level = 2;
        c.Xp    = XpTable.Threshold[3];
        var result = LevelUpFlow.Compute(c, 3, 0xCAFE);
        var subId = c.ClassDef.SubclassIds[0];
        c.ApplyLevelUp(result, new LevelUpChoices { SubclassId = subId });
        Assert.Equal(subId, c.SubclassId);
        Assert.Equal(subId, c.LevelUpHistory[^1].SubclassChosen);
    }

    [Fact]
    public void ApplyLevelUp_WithAsi_RaisesAbilityScores()
    {
        var c = MakeWolfFangsworn();
        c.Level = 3;
        c.Xp    = XpTable.Threshold[4];
        int strBefore = c.Abilities.STR;
        var result = LevelUpFlow.Compute(c, 4, 0xCAFE);
        c.ApplyLevelUp(result, new LevelUpChoices
        {
            AsiAdjustments = new() { { AbilityId.STR, 2 } },
        });
        Assert.Equal(strBefore + 2, c.Abilities.STR);
    }

    [Fact]
    public void ApplyLevelUp_AsiClampsAtAbilityCap()
    {
        var c = MakeWolfFangsworn();
        c.Level = 3;
        c.SetAbilities(c.Abilities.With(AbilityId.STR, C.ABILITY_SCORE_CAP_PRE_L20));
        var result = LevelUpFlow.Compute(c, 4, 0xCAFE);
        c.ApplyLevelUp(result, new LevelUpChoices
        {
            AsiAdjustments = new() { { AbilityId.STR, 2 } },
        });
        Assert.Equal(C.ABILITY_SCORE_CAP_PRE_L20, c.Abilities.STR);
    }

    [Fact]
    public void ApplyLevelUp_ChainedLevels_AccumulateHistoryInOrder()
    {
        var c = MakeWolfFangsworn();
        for (int target = 2; target <= 5; target++)
        {
            var r = LevelUpFlow.Compute(c, target, 0xCAFE_CAFE_CAFEUL ^ (ulong)target);
            var choices = new LevelUpChoices();
            if (r.GrantsSubclassChoice)
                choices.SubclassId = c.ClassDef.SubclassIds[0];
            if (r.GrantsAsiChoice)
                choices.AsiAdjustments = new() { { AbilityId.CON, 2 } };
            c.ApplyLevelUp(r, choices);
        }
        Assert.Equal(5, c.Level);
        Assert.Equal(4, c.LevelUpHistory.Count);
        for (int i = 0; i < 4; i++)
            Assert.Equal(i + 2, c.LevelUpHistory[i].Level);
    }
}
