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

namespace Theriapolis.Tests.Items;

public sealed class InventoryTests
{
    private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory));

    [Fact]
    public void Add_AppendsItemAndIncreasesWeight()
    {
        var inv = new Inventory();
        var sword = inv.Add(_content.Items["rend_sword"]);
        Assert.Single(inv.Items);
        Assert.Equal(_content.Items["rend_sword"].WeightLb, inv.TotalWeightLb);
        Assert.Equal(1, sword.Qty);
    }

    [Fact]
    public void TryEquip_PutsItemInRequestedSlot()
    {
        var inv = new Inventory();
        var sword = inv.Add(_content.Items["rend_sword"]);
        bool ok = inv.TryEquip(sword, EquipSlot.MainHand, out var err);
        Assert.True(ok, err);
        Assert.Equal(EquipSlot.MainHand, sword.EquippedAt);
        Assert.Same(sword, inv.GetEquipped(EquipSlot.MainHand));
    }

    [Fact]
    public void TryEquip_RefusesIfItemNotInInventory()
    {
        var inv = new Inventory();
        var sword = new ItemInstance(_content.Items["rend_sword"]);  // not added to inv
        bool ok = inv.TryEquip(sword, EquipSlot.MainHand, out var err);
        Assert.False(ok);
        Assert.Contains("not in this inventory", err);
    }

    [Fact]
    public void TryEquip_TwoHandedWeaponBlocksWhenOffHandOccupied()
    {
        var inv = new Inventory();
        var shield = inv.Add(_content.Items["buckler"]);
        var lance  = inv.Add(_content.Items["gore_lance"]);  // two_handed
        Assert.True(inv.TryEquip(shield, EquipSlot.OffHand, out _));
        bool ok = inv.TryEquip(lance, EquipSlot.MainHand, out var err);
        Assert.False(ok);
        Assert.Contains("two-handed", err.ToLowerInvariant());
    }

    [Fact]
    public void TryEquip_OffHandBlockedWhenMainHandHoldsTwoHanded()
    {
        var inv = new Inventory();
        var lance  = inv.Add(_content.Items["gore_lance"]);
        var shield = inv.Add(_content.Items["buckler"]);
        Assert.True(inv.TryEquip(lance, EquipSlot.MainHand, out _));
        bool ok = inv.TryEquip(shield, EquipSlot.OffHand, out var err);
        Assert.False(ok);
        Assert.Contains("two-handed", err.ToLowerInvariant());
    }

    [Fact]
    public void TryEquip_NaturalWeaponEnhancerRequiresMatchingSlot()
    {
        var inv = new Inventory();
        var fangCaps = inv.Add(_content.Items["fang_caps_steel"]);
        // Wrong slot:
        bool ok = inv.TryEquip(fangCaps, EquipSlot.NaturalWeaponClaw, out var err);
        Assert.False(ok);
        Assert.Contains("fits", err.ToLowerInvariant());
        // Correct slot:
        Assert.True(inv.TryEquip(fangCaps, EquipSlot.NaturalWeaponFang, out _));
    }

    [Fact]
    public void TryUnequip_ClearsSlot()
    {
        var inv = new Inventory();
        var sword = inv.Add(_content.Items["rend_sword"]);
        inv.TryEquip(sword, EquipSlot.MainHand, out _);
        Assert.True(inv.TryUnequip(EquipSlot.MainHand, out _));
        Assert.Null(sword.EquippedAt);
        Assert.Null(inv.GetEquipped(EquipSlot.MainHand));
    }

    [Fact]
    public void Remove_AlsoUnregistersFromEquippedSlot()
    {
        var inv = new Inventory();
        var sword = inv.Add(_content.Items["rend_sword"]);
        inv.TryEquip(sword, EquipSlot.MainHand, out _);
        Assert.True(inv.Remove(sword));
        Assert.Empty(inv.Items);
        Assert.Null(inv.GetEquipped(EquipSlot.MainHand));
    }

    // ── SizeMatch ────────────────────────────────────────────────────────

    [Fact]
    public void SizeMatch_DirectFitReturnsMatch()
    {
        var rendSword = _content.Items["rend_sword"];   // medium + large
        Assert.Equal(SizeMatch.MatchResult.Match, SizeMatch.Check(rendSword, SizeCategory.Medium));
    }

    [Fact]
    public void SizeMatch_AdaptivePropertyOverridesMissingSize()
    {
        var pack = _content.Items["adaptive_pack"];
        // adaptive_pack lists s/m/l explicitly, so match — but we test the Adaptive
        // path by querying with a Tiny wearer that's not in the list.
        var result = SizeMatch.Check(pack, SizeCategory.Tiny);
        // adaptive_pack lists "small" so Tiny falls through to "tiny" not in list,
        // but its "adaptive" property kicks in for the Adaptive branch.
        Assert.NotEqual(SizeMatch.MatchResult.WrongSize, result);
    }

    [Fact]
    public void SizeMatch_NonAdaptiveWrongSizeIsFlagged()
    {
        var rendSword = _content.Items["rend_sword"];   // medium + large only
        // Small wearer: not in sizes, no adaptive property → WrongSize.
        Assert.Equal(SizeMatch.MatchResult.WrongSize, SizeMatch.Check(rendSword, SizeCategory.Small));
    }
}
