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

namespace Theriapolis.Tests.Rules;

/// <summary>
/// Verifies that every class's <c>starting_kit</c> in classes.json
/// references real items, that auto-equipped items land in their declared
/// slot, and that fresh characters arrive armed and armoured.
/// </summary>
public sealed class StartingKitTests
{
    private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory));

    [Fact]
    public void EveryClass_HasNonEmptyStartingKit()
    {
        foreach (var c in _content.Classes.Values)
            Assert.NotEmpty(c.StartingKit);
    }

    [Fact]
    public void EveryStartingKitItem_ReferencesARealItem()
    {
        foreach (var c in _content.Classes.Values)
        foreach (var entry in c.StartingKit)
            Assert.True(
                _content.Items.ContainsKey(entry.ItemId),
                $"Class '{c.Id}' starting_kit references unknown item '{entry.ItemId}'");
    }

    [Fact]
    public void EveryAutoEquipEntry_HasValidEquipSlot()
    {
        foreach (var c in _content.Classes.Values)
        foreach (var entry in c.StartingKit)
        {
            if (!entry.AutoEquip) continue;
            Assert.False(
                string.IsNullOrEmpty(entry.EquipSlot),
                $"Class '{c.Id}' auto-equip entry '{entry.ItemId}' has empty equip_slot");
            Assert.NotNull(EquipSlotExtensions.FromJson(entry.EquipSlot));
        }
    }

    [Theory]
    [InlineData("fangsworn",       "rend_sword",   "chain_shirt",     "buckler")]
    [InlineData("bulwark",         "hoof_club",    "chain_mail",      "standard_shield")]
    [InlineData("covenant_keeper", "rend_sword",   "chain_shirt",     "standard_shield")]
    [InlineData("claw_wright",     "hoof_club",    "studded_leather", "buckler")]
    public void StartingKit_AppliedAndEquipped_FullKit(
        string classId, string mainHand, string body, string offHand)
    {
        var c = BuildWithKit(classId);
        AssertEquipped(c, EquipSlot.MainHand, mainHand);
        AssertEquipped(c, EquipSlot.Body,     body);
        AssertEquipped(c, EquipSlot.OffHand,  offHand);
    }

    [Theory]
    [InlineData("feral",          "paw_axe",     "hide_vest")]
    [InlineData("shadow_pelt",    "thorn_blade", "studded_leather")]
    [InlineData("scent_broker",   "fang_knife",  "leather_harness")]
    [InlineData("muzzle_speaker", "fang_knife",  "studded_leather")]
    public void StartingKit_AppliedAndEquipped_NoShield(string classId, string mainHand, string body)
    {
        var c = BuildWithKit(classId);
        AssertEquipped(c, EquipSlot.MainHand, mainHand);
        AssertEquipped(c, EquipSlot.Body,     body);
        Assert.Null(c.Inventory.GetEquipped(EquipSlot.OffHand));
    }

    [Fact]
    public void StartingKit_Skipped_WhenItemsTableNotPassed()
    {
        var c = MakeBuilder("fangsworn").Build();   // no items dict → no kit applied
        Assert.Empty(c.Inventory.Items);
    }

    [Fact]
    public void StartingKit_ProducesPositiveAcOverUnarmoredBaseline()
    {
        var c = BuildWithKit("fangsworn");
        int armored = DerivedStats.ArmorClass(c);
        Assert.True(armored >= 14, $"Fangsworn starting kit should produce AC ≥ 14 (chain shirt + buckler), got {armored}");
    }

    private Character BuildWithKit(string classId)
        => MakeBuilder(classId).Build(_content.Items);

    private CharacterBuilder MakeBuilder(string classId)
    {
        var classDef = _content.Classes[classId];
        var b = new CharacterBuilder
        {
            Clade        = _content.Clades["canidae"],
            Species      = _content.Species["wolf"],
            ClassDef     = classDef,
            Background   = _content.Backgrounds["pack_raised"],
            BaseAbilities = new AbilityScores(15, 14, 13, 12, 10, 8),
            Name         = "KitTest",
        };
        // Pick the right number of skills for this class.
        int n = classDef.SkillsChoose;
        foreach (var raw in classDef.SkillOptions)
        {
            if (b.ChosenClassSkills.Count >= n) break;
            try { b.ChosenClassSkills.Add(SkillIdExtensions.FromJson(raw)); } catch { }
        }
        return b;
    }

    private static void AssertEquipped(Character c, EquipSlot slot, string expectedItemId)
    {
        var inst = c.Inventory.GetEquipped(slot);
        Assert.NotNull(inst);
        Assert.Equal(expectedItemId, inst!.Def.Id);
    }
}
