using Microsoft.EntityFrameworkCore;
using SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Models;
using SVSim.Database.Services;
using SVSim.EmulatedEntrypoint.Models.Dtos;

namespace SVSim.EmulatedEntrypoint.Services;

public class CardAcquisitionService : ICardAcquisitionService
{
    private readonly SVSimDbContext _db;
    private readonly RewardGrantService _rewards;

    public CardAcquisitionService(SVSimDbContext db, RewardGrantService rewards)
    {
        _db = db;
        _rewards = rewards;
    }

    public async Task<CardGrantResult> GrantManyAsync(long viewerId, IEnumerable<long> newCardIds)
    {
        var viewer = await LoadViewerWithGraph(viewerId);
        var rewardList = new List<RewardListEntry>();

        // Bucket the input by id so multi-copy grants increment count once but cascade fires once.
        foreach (var grp in newCardIds.GroupBy(id => id))
        {
            int count = grp.Count();
            var granted = await _rewards.ApplyAsync(viewer, UserGoodsType.Card, grp.Key, count);
            foreach (var g in granted)
            {
                rewardList.Add(new RewardListEntry
                {
                    RewardType = g.RewardType, RewardId = g.RewardId, RewardNum = g.RewardNum,
                });
            }
        }

        await _db.SaveChangesAsync();
        return new CardGrantResult(rewardList);
    }

    public async Task<CardGrantResult> BackfillCosmeticsAsync(long viewerId)
    {
        var viewer = await LoadViewerWithGraph(viewerId);
        var rewardList = new List<RewardListEntry>();

        // Foil resolution: cascade rows live on non-foil ids. Apply the +1 convention.
        var lookupCardIds = viewer.Cards
            .Select(c => c.Card.IsFoil ? c.Card.Id - 1 : c.Card.Id)
            .Distinct()
            .ToList();

        var cascade = await _db.CardCosmeticRewards
            .Where(r => lookupCardIds.Contains(r.CardId))
            .ToListAsync();

        foreach (var reward in cascade)
        {
            // Skip if the viewer already owns this cosmetic. ApplyAsync's cosmetic branches
            // unconditionally return a wire entry (top-level grant semantics), so we must
            // filter at the caller side to avoid emitting "+0 received" lines for cosmetics
            // the viewer has owned for ages.
            if (AlreadyOwnsCosmetic(viewer, reward.Type, reward.CosmeticId)) continue;

            var goodsType = (UserGoodsType)(int)reward.Type;
            var granted = await _rewards.ApplyAsync(viewer, goodsType, reward.CosmeticId, 1);
            foreach (var g in granted)
            {
                rewardList.Add(new RewardListEntry
                {
                    RewardType = g.RewardType, RewardId = g.RewardId, RewardNum = g.RewardNum,
                });
            }
        }

        await _db.SaveChangesAsync();
        return new CardGrantResult(rewardList);
    }

    private static bool AlreadyOwnsCosmetic(Viewer viewer, CosmeticType type, long id) => type switch
    {
        CosmeticType.Sleeve   => viewer.Sleeves.Any(s => s.Id == id),
        CosmeticType.Emblem   => viewer.Emblems.Any(e => e.Id == id),
        CosmeticType.Skin     => viewer.LeaderSkins.Any(s => s.Id == id),
        CosmeticType.Degree   => viewer.Degrees.Any(d => d.Id == id),
        CosmeticType.MyPageBG => viewer.MyPageBackgrounds.Any(b => b.Id == id),
        _ => false,
    };

    private Task<Viewer> LoadViewerWithGraph(long viewerId) => _db.Viewers
        .Include(v => v.Cards).ThenInclude(c => c.Card)
        .Include(v => v.LeaderSkins)
        .Include(v => v.Sleeves)
        .Include(v => v.Emblems)
        .Include(v => v.Degrees)
        .Include(v => v.MyPageBackgrounds)
        .AsSplitQuery()
        .FirstAsync(v => v.Id == viewerId);
}
