using Theriapolis.Core;
using Theriapolis.Core.Data;
using Theriapolis.Core.Loot;
using Theriapolis.Core.Util;
using Xunit;

namespace Theriapolis.Tests.Loot;

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

    [Fact]
    public void LootTables_LoadCleanly()
    {
        Assert.True(_content.LootTables.Count >= 9, $"expected ≥9 loot tables, got {_content.LootTables.Count}");
        Assert.Contains("loot_brigand_low",  _content.LootTables.Keys);
        Assert.Contains("loot_brigand_high", _content.LootTables.Keys);
        Assert.Contains("loot_wild_low",     _content.LootTables.Keys);
    }

    [Fact]
    public void Roll_UnknownTable_ReturnsEmpty()
    {
        var rng = new SeededRng(0xCAFEUL);
        var drops = LootRoller.Roll("not_a_table", _content.LootTables, _content.Items, rng);
        Assert.Empty(drops);
    }

    [Fact]
    public void Roll_EmptyTableId_ReturnsEmpty()
    {
        var rng = new SeededRng(0xCAFEUL);
        var drops = LootRoller.Roll("", _content.LootTables, _content.Items, rng);
        Assert.Empty(drops);
    }

    [Fact]
    public void Roll_SameSeed_ProducesSameDrops()
    {
        var a = LootRoller.Roll("loot_brigand_high", _content.LootTables, _content.Items, new SeededRng(42UL));
        var b = LootRoller.Roll("loot_brigand_high", _content.LootTables, _content.Items, new SeededRng(42UL));
        Assert.Equal(a.Count, b.Count);
        for (int i = 0; i < a.Count; i++)
        {
            Assert.Equal(a[i].Def.Id, b[i].Def.Id);
            Assert.Equal(a[i].Qty,    b[i].Qty);
        }
    }

    [Fact]
    public void Roll_QtyAlwaysWithinBounds()
    {
        // Roll many times; verify no result has qty outside the table-defined range.
        var table = _content.LootTables["loot_brigand_high"];
        var bounds = table.Drops.ToDictionary(d => d.ItemId, d => (d.QtyMin, d.QtyMax));
        for (int seed = 0; seed < 50; seed++)
        {
            var drops = LootRoller.Roll("loot_brigand_high", _content.LootTables, _content.Items, new SeededRng((ulong)seed));
            foreach (var drop in drops)
            {
                var (mn, mx) = bounds[drop.Def.Id];
                Assert.InRange(drop.Qty, mn, mx);
            }
        }
    }

    [Fact]
    public void Roll_OneHundredSamples_AverageDropCountIsReasonable()
    {
        // The "high" brigand table has 5 drops with cumulative chance summing
        // around 1.95. Across 100 samples expect ≥50 total drops.
        int totalDrops = 0;
        for (int seed = 0; seed < 100; seed++)
        {
            var drops = LootRoller.Roll("loot_brigand_high", _content.LootTables, _content.Items, new SeededRng((ulong)seed));
            totalDrops += drops.Count;
        }
        Assert.True(totalDrops >= 50, $"expected ≥50 drops in 100 samples, got {totalDrops}");
    }
}
