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

namespace Theriapolis.Tests.Dungeons;

/// <summary>
/// Phase 7 M2 — clade-responsive movement multiplier tests. Verifies the
/// table from Phase 7 plan §5.4 and that hybrid PCs use the dominant-
/// lineage's presenting size for the lookup.
/// </summary>
public sealed class ClademorphicMovementTests
{
    private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory));

    // ── Plan §5.4 table values ────────────────────────────────────────────

    [Fact]
    public void LargePc_InMustelidTunnel_PaysHeavyMultiplier()
    {
        Assert.Equal(C.MOVE_COST_MISMATCH_HEAVY,
            ClademorphicMovement.GetCostMultiplier(SizeCategory.Large, "mustelid"));
    }

    [Fact]
    public void MediumLargePc_InMustelidTunnel_PaysMediumMultiplier()
    {
        Assert.Equal(C.MOVE_COST_MISMATCH_MED,
            ClademorphicMovement.GetCostMultiplier(SizeCategory.MediumLarge, "mustelid"));
    }

    [Fact]
    public void MediumPc_InMustelidTunnel_PaysLightMultiplier()
    {
        Assert.Equal(C.MOVE_COST_MISMATCH_LIGHT,
            ClademorphicMovement.GetCostMultiplier(SizeCategory.Medium, "mustelid"));
    }

    [Fact]
    public void SmallPc_InMustelidTunnel_NoPenalty()
    {
        Assert.Equal(1.0f,
            ClademorphicMovement.GetCostMultiplier(SizeCategory.Small, "mustelid"));
    }

    [Fact]
    public void SmallPc_InUrsidHall_PaysExposedMultiplier()
    {
        Assert.Equal(C.MOVE_COST_MISMATCH_MED,
            ClademorphicMovement.GetCostMultiplier(SizeCategory.Small, "ursid"));
    }

    [Fact]
    public void LargePc_InCervidHall_PaysAntlerClearancePenalty()
    {
        Assert.Equal(C.MOVE_COST_MISMATCH_LIGHT,
            ClademorphicMovement.GetCostMultiplier(SizeCategory.Large, "cervid"));
    }

    [Fact]
    public void AnyPc_InImperiumOrNoneRoom_NoPenalty()
    {
        foreach (var size in new[] { SizeCategory.Small, SizeCategory.Medium, SizeCategory.MediumLarge, SizeCategory.Large })
        {
            Assert.Equal(1.0f, ClademorphicMovement.GetCostMultiplier(size, "imperium"));
            Assert.Equal(1.0f, ClademorphicMovement.GetCostMultiplier(size, "none"));
            Assert.Equal(1.0f, ClademorphicMovement.GetCostMultiplier(size, ""));
        }
    }

    [Fact]
    public void UnknownBuiltBy_NoPenalty()
    {
        Assert.Equal(1.0f, ClademorphicMovement.GetCostMultiplier(SizeCategory.Large, "garbage"));
    }

    // ── Hybrid PC presenting-size lookup ─────────────────────────────────

    [Fact]
    public void HybridPc_UsesPresentingClade_ForSizeLookup()
    {
        // Build a Wolf-Folk × Hare-Folk hybrid presenting as Hare-Folk
        // (Dam dominant). EffectiveSize should be the presenting species' size.
        var pc = BuildHybrid(
            sireClade: "canidae",   sireSpecies: "wolf",
            damClade:  "leporidae", damSpecies:  "hare",
            dominant:  ParentLineage.Dam);

        // The hybrid build path picked Hare-Folk as the presenting species,
        // so EffectiveSize should match Character.Size (which the builder
        // already set to Hare-Folk's size category).
        Assert.Equal(pc.Size, ClademorphicMovement.EffectiveSize(pc));

        // Whichever species the builder chose, the multiplier should match
        // its size's lookup in a Mustelid tunnel.
        var expected = ClademorphicMovement.GetCostMultiplier(pc.Size, "mustelid");
        Assert.Equal(expected, ClademorphicMovement.GetCostMultiplier(pc, "mustelid"));
    }

    [Fact]
    public void NonHybridPc_UsesOwnSize_ForLookup()
    {
        var pc = BuildPurebred("canidae", "wolf");  // wolf = MediumLarge
        Assert.Equal(SizeCategory.MediumLarge, pc.Size);
        Assert.Equal(C.MOVE_COST_MISMATCH_MED,
            ClademorphicMovement.GetCostMultiplier(pc, "mustelid"));
    }

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

    private Character BuildPurebred(string cladeId, string speciesId)
    {
        var b = new CharacterBuilder
        {
            Clade         = _content.Clades[cladeId],
            Species       = _content.Species[speciesId],
            ClassDef      = _content.Classes["fangsworn"],
            Background    = _content.Backgrounds["pack_raised"],
            BaseAbilities = new AbilityScores(15, 14, 13, 10, 12, 8),
        };
        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(_content.Items);
    }

    private Character BuildHybrid(
        string sireClade, string sireSpecies,
        string damClade,  string damSpecies,
        ParentLineage dominant)
    {
        var b = new CharacterBuilder
        {
            ClassDef      = _content.Classes["fangsworn"],
            Background    = _content.Backgrounds["pack_raised"],
            BaseAbilities = new AbilityScores(15, 14, 13, 10, 12, 8),
            IsHybridOrigin       = true,
            HybridSireClade      = _content.Clades[sireClade],
            HybridSireSpecies    = _content.Species[sireSpecies],
            HybridDamClade       = _content.Clades[damClade],
            HybridDamSpecies     = _content.Species[damSpecies],
            HybridDominantParent = dominant,
        };
        // Chosen skills don't matter for size-of-character — pick first N.
        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.TryBuildHybrid(_content.Items, out var character, out _));
        return character!;
    }
}
