using System.Net;
using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Models;
using SVSim.UnitTests.Infrastructure;

namespace SVSim.UnitTests.Controllers;

public class PackControllerGachaPointTests
{
    private static StringContent JsonBody(string json) =>
        new(json, Encoding.UTF8, "application/json");

    [Test]
    public async Task GetGachaPointRewards_returns_catalog_for_active_pack()
    {
        using var factory = new SVSimTestFactory();
        long viewerId = await factory.SeedViewerAsync();
        using (var scope = factory.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
            db.Classes.Add(new ClassEntry { Id = 0, Name = "Neutral" });
            var set = new ShadowverseCardSetEntry { Id = 10008, IsInRotation = true };
            db.CardSets.Add(set);
            set.Cards.Add(new ShadowverseCardEntry
            {
                Id = 108041010, Name = "leg", Rarity = Rarity.Legendary,
                Class = db.Classes.Local.First(), IsFoil = false,
            });
            db.CardCosmeticRewards.Add(new CardCosmeticReward
            {
                CardId = 108041010, Type = CosmeticType.Emblem, CosmeticId = 1080410100,
            });
            db.Packs.Add(new PackConfigEntry
            {
                Id = 10008, BasePackId = 10008, PackCategory = PackCategory.LegendCardPack,
                CommenceDate = DateTime.UtcNow.AddDays(-1), CompleteDate = DateTime.UtcNow.AddDays(30),
                GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 },
            });
            await db.SaveChangesAsync();
        }

        using var client = factory.CreateAuthenticatedClient(viewerId);
        var body = JsonBody("""{"odds_gacha_id":10008,"parent_gacha_id":10008,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
        var response = await client.PostAsync("/pack/get_gacha_point_rewards", body);
        var text = await response.Content.ReadAsStringAsync();

        Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), text);

        using var doc = JsonDocument.Parse(text);
        var rewards = doc.RootElement.GetProperty("gacha_point_rewards");
        Assert.That(rewards.GetArrayLength(), Is.EqualTo(1));
        var entry = rewards[0];
        Assert.That(entry.GetProperty("class_id").GetString(), Is.EqualTo("0"),
            "class_id must be wire-typed as a string");
        Assert.That(entry.GetProperty("card_id").GetInt64(), Is.EqualTo(108041010));
        Assert.That(entry.GetProperty("is_received").GetBoolean(), Is.False);
        var rewardList = entry.GetProperty("reward_list");
        Assert.That(rewardList.GetArrayLength(), Is.EqualTo(1));
        Assert.That(rewardList[0].GetProperty("reward_type").GetInt32(), Is.EqualTo(7));
        Assert.That(rewardList[0].GetProperty("reward_detail_id").GetInt64(), Is.EqualTo(1080410100));
        Assert.That(rewardList[0].GetProperty("reward_number").GetInt32(), Is.EqualTo(1));
    }

    [Test]
    public async Task GetGachaPointRewards_wire_keys_match_prod_capture()
    {
        using var factory = new SVSimTestFactory();
        long viewerId = await factory.SeedViewerAsync();
        using (var scope = factory.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
            db.Classes.Add(new ClassEntry { Id = 0, Name = "Neutral" });
            var set = new ShadowverseCardSetEntry { Id = 10008, IsInRotation = true };
            db.CardSets.Add(set);
            set.Cards.Add(new ShadowverseCardEntry
            {
                Id = 108041010, Name = "leg", Rarity = Rarity.Legendary,
                Class = db.Classes.Local.First(), IsFoil = false,
            });
            db.CardCosmeticRewards.Add(new CardCosmeticReward
            {
                CardId = 108041010, Type = CosmeticType.Emblem, CosmeticId = 1080410100,
            });
            db.Packs.Add(new PackConfigEntry
            {
                Id = 10008, BasePackId = 10008, PackCategory = PackCategory.LegendCardPack,
                CommenceDate = DateTime.UtcNow.AddDays(-1), CompleteDate = DateTime.UtcNow.AddDays(30),
                GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 },
            });
            await db.SaveChangesAsync();
        }

        using var client = factory.CreateAuthenticatedClient(viewerId);
        var body = JsonBody("""{"odds_gacha_id":10008,"parent_gacha_id":10008,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
        var response = await client.PostAsync("/pack/get_gacha_point_rewards", body);
        var text = await response.Content.ReadAsStringAsync();

        // Literal wire-key checks — verified against
        // data_dumps/traffic_prod_tradeables_capture.ndjson pack 10008 response.
        Assert.That(text, Does.Contain("\"gacha_point_rewards\""));
        Assert.That(text, Does.Contain("\"class_id\":\"0\""), "class_id MUST be a string");
        Assert.That(text, Does.Contain("\"reward_detail_id\":1080410100"),
            "per-card entry uses reward_detail_id (not reward_id)");
        Assert.That(text, Does.Contain("\"reward_number\":1"),
            "per-card entry uses reward_number (not reward_num)");
        Assert.That(text, Does.Contain("\"is_received\":false"));
        Assert.That(text, Does.Contain("\"is_display_prize\":false"));
    }

    [Test]
    public async Task ExchangeGachaPoint_grants_card_and_returns_post_state_reward_list()
    {
        using var factory = new SVSimTestFactory();
        long viewerId = await factory.SeedViewerAsync();
        using (var scope = factory.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
            db.Classes.Add(new ClassEntry { Id = 0, Name = "Neutral" });
            var set = new ShadowverseCardSetEntry { Id = 10008, IsInRotation = true };
            db.CardSets.Add(set);
            set.Cards.Add(new ShadowverseCardEntry
            {
                Id = 108041010, Name = "leg", Rarity = Rarity.Legendary,
                Class = db.Classes.Local.First(), IsFoil = false,
            });
            db.CardCosmeticRewards.Add(new CardCosmeticReward
            {
                CardId = 108041010, Type = CosmeticType.Emblem, CosmeticId = 1080410100,
            });
            db.Packs.Add(new PackConfigEntry
            {
                Id = 10008, BasePackId = 10008, PackCategory = PackCategory.LegendCardPack,
                CommenceDate = DateTime.UtcNow.AddDays(-1), CompleteDate = DateTime.UtcNow.AddDays(30),
                GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 },
            });
            var viewer = await db.Viewers
                .Include(v => v.GachaPointBalances)
                .FirstAsync(v => v.Id == viewerId);
            viewer.GachaPointBalances.Add(new ViewerGachaPointBalance { PackId = 10008, Points = 500 });
            await db.SaveChangesAsync();
        }

        using var client = factory.CreateAuthenticatedClient(viewerId);
        var body = JsonBody("""{"card_id":108041010,"parent_gacha_id":10008,"odds_gacha_id":10008,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
        var response = await client.PostAsync("/pack/exchange_gacha_point", body);
        var text = await response.Content.ReadAsStringAsync();

        Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), text);

        using var doc = JsonDocument.Parse(text);
        var rewardList = doc.RootElement.GetProperty("reward_list");
        Assert.That(rewardList.GetArrayLength(), Is.GreaterThan(0));
        // Verify the card grant entry (type=5/Card) is present with the granted card id.
        bool foundCard = false;
        foreach (var r in rewardList.EnumerateArray())
        {
            if (r.GetProperty("reward_type").GetInt32() == 5 &&
                r.GetProperty("reward_id").GetInt64() == 108041010)
            {
                foundCard = true;
                break;
            }
        }
        Assert.That(foundCard, Is.True, "card grant entry missing from reward_list");

        // Verify side-effects.
        using (var scope = factory.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
            var viewer = await db.Viewers
                .Include(v => v.GachaPointBalances)
                .Include(v => v.GachaPointReceived)
                .Include(v => v.Cards).ThenInclude(c => c.Card)
                .AsSplitQuery()
                .FirstAsync(v => v.Id == viewerId);
            Assert.That(viewer.GachaPointBalances.Single().Points, Is.EqualTo(100));
            Assert.That(viewer.GachaPointReceived.Single().CardId, Is.EqualTo(108041010));
            Assert.That(viewer.Cards.Any(c => c.Card.Id == 108041010), Is.True);
        }
    }

    [Test]
    public async Task ExchangeGachaPoint_rejects_when_balance_insufficient()
    {
        using var factory = new SVSimTestFactory();
        long viewerId = await factory.SeedViewerAsync();
        using (var scope = factory.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
            db.Classes.Add(new ClassEntry { Id = 0, Name = "Neutral" });
            var set = new ShadowverseCardSetEntry { Id = 10008, IsInRotation = true };
            db.CardSets.Add(set);
            set.Cards.Add(new ShadowverseCardEntry
            {
                Id = 108041010, Name = "leg", Rarity = Rarity.Legendary,
                Class = db.Classes.Local.First(), IsFoil = false,
            });
            db.CardCosmeticRewards.Add(new CardCosmeticReward
            {
                CardId = 108041010, Type = CosmeticType.Emblem, CosmeticId = 1080410100,
            });
            db.Packs.Add(new PackConfigEntry
            {
                Id = 10008, BasePackId = 10008, PackCategory = PackCategory.LegendCardPack,
                CommenceDate = DateTime.UtcNow.AddDays(-1), CompleteDate = DateTime.UtcNow.AddDays(30),
                GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 },
            });
            await db.SaveChangesAsync();
        }

        using var client = factory.CreateAuthenticatedClient(viewerId);
        var body = JsonBody("""{"card_id":108041010,"parent_gacha_id":10008,"odds_gacha_id":10008,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
        var response = await client.PostAsync("/pack/exchange_gacha_point", body);
        Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest),
            await response.Content.ReadAsStringAsync());
    }
}
