using SVSim.Database.Enums;
using SVSim.Database.Models;
using SVSim.Database.Repositories.PackDrawTables;
using SVSim.Database.Services;
using SVSim.EmulatedEntrypoint.Services;

namespace SVSim.UnitTests.Services;

public class PackOpenServiceTests
{
    private sealed class ScriptedRandom : IRandom
    {
        private readonly double[] _seq; private int _i;
        public ScriptedRandom(params double[] seq) { _seq = seq; }
        public double NextDouble() => _seq[_i++ % _seq.Length];
        public int Next(int maxExclusive) => (int)(NextDouble() * maxExclusive);
    }

    private sealed class NoFoil : ICardFoilLookup
    {
        public ShadowverseCardEntry? TryGetFoilTwin(long baseCardId) => null;
    }

    private static PackConfigEntry StandardPack(int id = 10000) => new()
    {
        Id = id, BasePackId = id, PackCategory = PackCategory.None,
    };

    private static PackDrawTable AllBronzeTable() => new()
    {
        Config = new PackDrawConfigEntry { Id = 10000, AnimationRatePct = 0 },
        SlotRates = new[]
        {
            new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Bronze, RatePct = 100.0 },
            new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.Eighth,  Tier = DrawTier.Bronze, RatePct = 100.0 },
        },
        CardWeights = new[]
        {
            new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Bronze, CardId = 1, RatePct = 70 },
            new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Bronze, CardId = 2, RatePct = 30 },
            new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.Eighth,  Tier = DrawTier.Bronze, CardId = 1, RatePct = 100 },
        },
    };

    [Test]
    public void Draw_returns_eight_cards_for_one_pack()
    {
        var svc = new PackOpenService();
        var rng = new ScriptedRandom(0.1);

        var result = svc.Draw(AllBronzeTable(), StandardPack(), 1,
            excludeCardIds: Array.Empty<long>(), ownedCardIds: Array.Empty<long>(),
            new NoFoil(), rng);

        Assert.That(result.Cards.Count, Is.EqualTo(8));
        Assert.That(result.Cards.All(c => c.CardId == 1), Is.True);
    }

    [Test]
    public void Draw_picks_card_by_per_card_weight_within_tier()
    {
        var svc = new PackOpenService();
        // Tier roll always lands in Bronze (only tier). Card pick rng=0.8 -> within Bronze
        // band > 0.7 -> card 2. Slot 8 has only card 1 in its pool so it always picks card 1.
        var rng = new ScriptedRandom(0.0, 0.8);

        var result = svc.Draw(AllBronzeTable(), StandardPack(), 1,
            Array.Empty<long>(), Array.Empty<long>(), new NoFoil(), rng);

        Assert.That(result.Cards.Take(7).All(c => c.CardId == 2), Is.True, "slots 1-7 should pick card 2");
        Assert.That(result.Cards[7].CardId, Is.EqualTo(1), "slot 8 pool only contains card 1");
    }

    [Test]
    public void Draw_rate_less_branch_picks_only_unowned()
    {
        var pack = new PackConfigEntry { Id = 98001, BasePackId = 98001, PackCategory = PackCategory.SpecialCardPack };
        var table = new PackDrawTable
        {
            Config = new PackDrawConfigEntry { Id = 98001, AnimationRatePct = 0, HasBonusSlot = true, SpecialKind = "leader_card" },
            SlotRates = new[]
            {
                new PackDrawSlotRateEntry { PackId = 98001, Slot = DrawSlot.General, Tier = DrawTier.Bronze,  RatePct = 100.0 },
                new PackDrawSlotRateEntry { PackId = 98001, Slot = DrawSlot.Eighth,  Tier = DrawTier.Bronze,  RatePct = 100.0 },
                new PackDrawSlotRateEntry { PackId = 98001, Slot = DrawSlot.Bonus,   Tier = DrawTier.Special, RatePct = 100.0 },
            },
            CardWeights = new[]
            {
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.General, Tier = DrawTier.Bronze,  CardId = 10, RatePct = 100 },
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.Eighth,  Tier = DrawTier.Bronze,  CardId = 10, RatePct = 100 },
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.Bonus,   Tier = DrawTier.Special, CardId = 300, RatePct = null, IsLeader = true },
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.Bonus,   Tier = DrawTier.Special, CardId = 301, RatePct = null, IsLeader = true },
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.Bonus,   Tier = DrawTier.Special, CardId = 302, RatePct = null, IsLeader = true },
            },
        };
        var svc = new PackOpenService();
        var rng = new ScriptedRandom(0.1);

        var result = svc.Draw(table, pack, packNumber: 10,
            excludeCardIds: Array.Empty<long>(),
            ownedCardIds: new long[] { 300, 301 },
            new NoFoil(), rng);

        Assert.That(result.Cards.Count, Is.EqualTo(81));   // 10 packs * 8 + 1 bonus
        var bonus = result.Cards[^1];
        Assert.That(bonus.CardId, Is.EqualTo(302));
    }

    [Test]
    public void Draw_rate_less_falls_back_to_full_pool_when_all_owned()
    {
        var pack = new PackConfigEntry { Id = 98001, BasePackId = 98001 };
        var table = new PackDrawTable
        {
            Config = new PackDrawConfigEntry { Id = 98001, AnimationRatePct = 0, HasBonusSlot = true },
            SlotRates = new[]
            {
                new PackDrawSlotRateEntry { PackId = 98001, Slot = DrawSlot.General, Tier = DrawTier.Bronze,  RatePct = 100.0 },
                new PackDrawSlotRateEntry { PackId = 98001, Slot = DrawSlot.Eighth,  Tier = DrawTier.Bronze,  RatePct = 100.0 },
                new PackDrawSlotRateEntry { PackId = 98001, Slot = DrawSlot.Bonus,   Tier = DrawTier.Special, RatePct = 100.0 },
            },
            CardWeights = new[]
            {
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.General, Tier = DrawTier.Bronze,  CardId = 10, RatePct = 100 },
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.Eighth,  Tier = DrawTier.Bronze,  CardId = 10, RatePct = 100 },
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.Bonus,   Tier = DrawTier.Special, CardId = 300, RatePct = null, IsLeader = true },
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.Bonus,   Tier = DrawTier.Special, CardId = 301, RatePct = null, IsLeader = true },
            },
        };
        var svc = new PackOpenService();
        var rng = new ScriptedRandom(0.1);

        var result = svc.Draw(table, pack, packNumber: 10,
            excludeCardIds: Array.Empty<long>(),
            ownedCardIds: new long[] { 300, 301 },
            new NoFoil(), rng);

        var bonus = result.Cards[^1];
        Assert.That(bonus.CardId, Is.AnyOf(300L, 301L));
    }

    [Test]
    [Category("Slow")]
    public void Draw_observed_tier_rates_track_seed_within_half_a_percent()
    {
        // Synthetic Classic pack: Bronze=76.5/Silver=16/Gold=6/Legendary=1.5 in general slots;
        // slot 8 is Silver=92.5/Gold=6/Legendary=1.5 (no Bronze).
        var table = new PackDrawTable
        {
            Config = new PackDrawConfigEntry { Id = 10000, AnimationRatePct = 0 },
            SlotRates = new[]
            {
                new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Bronze,    RatePct = 76.5 },
                new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Silver,    RatePct = 16.0 },
                new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Gold,      RatePct = 6.0  },
                new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Legendary, RatePct = 1.5  },
                new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.Eighth,  Tier = DrawTier.Silver,    RatePct = 92.5 },
                new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.Eighth,  Tier = DrawTier.Gold,      RatePct = 6.0  },
                new PackDrawSlotRateEntry { PackId = 10000, Slot = DrawSlot.Eighth,  Tier = DrawTier.Legendary, RatePct = 1.5  },
            },
            CardWeights = new[]
            {
                new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Bronze,    CardId = 1, RatePct = 76.5 },
                new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Silver,    CardId = 2, RatePct = 16.0 },
                new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Gold,      CardId = 3, RatePct = 6.0 },
                new PackDrawCardWeightEntry { PackId = 10000, Slot = DrawSlot.General, Tier = DrawTier.Legendary, CardId = 4, RatePct = 1.5 },
            },
        };
        var svc = new PackOpenService();
        var rng = new SystemRandom(42);
        var pack = new PackConfigEntry { Id = 10000 };
        int totalSlots = 200_000;
        int bronze = 0, silver = 0, gold = 0, legendary = 0;

        // 25_000 packs * 7 general slots = 175_000 general-slot observations.
        for (int i = 0; i < totalSlots / 8; i++)
        {
            var r = svc.Draw(table, pack, 1, Array.Empty<long>(), Array.Empty<long>(), new NoFoil(), rng);
            for (int s = 0; s < 7; s++)
            {
                switch (r.Cards[s].Rarity)
                {
                    case Rarity.Bronze:    bronze++; break;
                    case Rarity.Silver:    silver++; break;
                    case Rarity.Gold:      gold++; break;
                    case Rarity.Legendary: legendary++; break;
                }
            }
        }

        double n = bronze + silver + gold + legendary;
        Assert.That(100 * bronze    / n, Is.EqualTo(76.5).Within(0.5));
        Assert.That(100 * silver    / n, Is.EqualTo(16.0).Within(0.5));
        Assert.That(100 * gold      / n, Is.EqualTo(6.0).Within(0.5));
        Assert.That(100 * legendary / n, Is.EqualTo(1.5).Within(0.5));
    }

    [Test]
    public void Draw_does_not_emit_bonus_for_packNumber_less_than_10()
    {
        var pack = new PackConfigEntry { Id = 98001 };
        var table = new PackDrawTable
        {
            Config = new PackDrawConfigEntry { Id = 98001, HasBonusSlot = true, AnimationRatePct = 0 },
            SlotRates = new[]
            {
                new PackDrawSlotRateEntry { PackId = 98001, Slot = DrawSlot.General, Tier = DrawTier.Bronze,  RatePct = 100 },
                new PackDrawSlotRateEntry { PackId = 98001, Slot = DrawSlot.Eighth,  Tier = DrawTier.Bronze,  RatePct = 100 },
                new PackDrawSlotRateEntry { PackId = 98001, Slot = DrawSlot.Bonus,   Tier = DrawTier.Special, RatePct = 100 },
            },
            CardWeights = new[]
            {
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.General, Tier = DrawTier.Bronze,  CardId = 1, RatePct = 100 },
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.Eighth,  Tier = DrawTier.Bronze,  CardId = 1, RatePct = 100 },
                new PackDrawCardWeightEntry { PackId = 98001, Slot = DrawSlot.Bonus,   Tier = DrawTier.Special, CardId = 999, RatePct = null, IsLeader = true },
            },
        };
        var svc = new PackOpenService();
        var rng = new ScriptedRandom(0.1);

        var result = svc.Draw(table, pack, packNumber: 1,
            Array.Empty<long>(), Array.Empty<long>(), new NoFoil(), rng);

        Assert.That(result.Cards.Count, Is.EqualTo(8));
    }
}
