using Microsoft.EntityFrameworkCore;
using WishNinja.Data;
using WishNinja.Data.Entities;

namespace WishNinja.Services;

public record ClaimResult(bool Succeeded, string? Error = null);

/// <summary>
/// Read/write operations for wishlists, items and claims. The defining rule lives here: a wishlist
/// owner viewing their own list never receives claim data. This is enforced when building views and
/// blocked again on every claim mutation, so the surprise cannot leak through the UI or an API call.
/// </summary>
public class WishlistService(IDbContextFactory<ApplicationDbContext> dbFactory)
{
    // --- Visibility ----------------------------------------------------------------------------

    /// <summary>Lists owned by the user (for "My Wishlists").</summary>
    public async Task<List<Wishlist>> GetOwnedAsync(string userId)
    {
        await using var db = await dbFactory.CreateDbContextAsync();
        return await db.Wishlists
            .Where(w => w.OwnerId == userId)
            .OrderByDescending(w => w.CreatedAt)
            .ToListAsync();
    }

    /// <summary>Lists the user can view but does not own (others' shared lists).</summary>
    public async Task<List<Wishlist>> GetSharedWithAsync(string userId)
    {
        await using var db = await dbFactory.CreateDbContextAsync();
        return await db.Wishlists
            .Include(w => w.Owner)
            .Where(w => w.OwnerId != userId && !w.IsArchived &&
                (w.Visibility == WishlistVisibility.AllMembers ||
                 w.Shares.Any(s => s.UserId == userId)))
            .OrderBy(w => w.Title)
            .ToListAsync();
    }

    public async Task<bool> CanViewAsync(int wishlistId, string userId)
    {
        await using var db = await dbFactory.CreateDbContextAsync();
        var w = await db.Wishlists.Include(x => x.Shares).FirstOrDefaultAsync(x => x.Id == wishlistId);
        return w is not null && CanView(w, userId);
    }

    private static bool CanView(Wishlist w, string userId) =>
        w.OwnerId == userId ||
        w.Visibility == WishlistVisibility.AllMembers ||
        w.Shares.Any(s => s.UserId == userId);

    // --- Detail view (owner-hidden claims) -----------------------------------------------------

    /// <summary>
    /// Builds the per-viewer detail. Returns null if the user may not view the list. Claim data is
    /// only loaded/returned when the viewer is NOT the owner.
    /// </summary>
    public async Task<WishlistDetailView?> GetDetailAsync(int wishlistId, string userId)
    {
        await using var db = await dbFactory.CreateDbContextAsync();
        var wishlist = await db.Wishlists
            .Include(w => w.Shares)
            .Include(w => w.Items.OrderBy(i => i.Priority).ThenBy(i => i.SortOrder).ThenBy(i => i.Id))
            .FirstOrDefaultAsync(w => w.Id == wishlistId);

        if (wishlist is null || !CanView(wishlist, userId))
            return null;

        var isOwner = wishlist.OwnerId == userId;

        if (isOwner)
        {
            // Owner: never query the Claims table at all.
            var ownerItems = wishlist.Items
                .Select(i => new WishlistItemView(i, true, 0, false, 0, Array.Empty<ClaimInfo>()))
                .ToList();
            return new WishlistDetailView(wishlist, true, ownerItems);
        }

        var itemIds = wishlist.Items.Select(i => i.Id).ToList();
        var claims = await db.Claims
            .Include(c => c.ClaimedByUser)
            .Where(c => itemIds.Contains(c.WishlistItemId))
            .ToListAsync();

        var byItem = claims.ToLookup(c => c.WishlistItemId);
        var items = wishlist.Items.Select(i =>
        {
            var itemClaims = byItem[i.Id].ToList();
            var viewerClaim = itemClaims.FirstOrDefault(c => c.ClaimedByUserId == userId);
            var others = itemClaims
                .Where(c => c.ClaimedByUserId != userId)
                .Select(c => new ClaimInfo(c.Id, c.ClaimedByUserId,
                    c.ClaimedByUser?.DisplayName ?? "Someone", c.Quantity, c.Note))
                .ToList();
            return new WishlistItemView(
                i,
                IsViewerOwner: false,
                ClaimedQuantity: itemClaims.Sum(c => c.Quantity),
                ClaimedByViewer: viewerClaim is not null,
                ViewerClaimId: viewerClaim?.Id ?? 0,
                OtherClaims: others);
        }).ToList();

        return new WishlistDetailView(wishlist, false, items);
    }

    // --- Claim mutations -----------------------------------------------------------------------

    public async Task<ClaimResult> ClaimAsync(int itemId, string userId, int quantity, string? note)
    {
        if (quantity < 1) quantity = 1;

        await using var db = await dbFactory.CreateDbContextAsync();
        var item = await db.WishlistItems
            .Include(i => i.Wishlist!).ThenInclude(w => w.Shares)
            .Include(i => i.Claims)
            .FirstOrDefaultAsync(i => i.Id == itemId);

        if (item is null) return new ClaimResult(false, "Item not found.");
        if (item.Wishlist!.OwnerId == userId)
            return new ClaimResult(false, "You can't claim items on your own wishlist.");
        if (!CanView(item.Wishlist, userId))
            return new ClaimResult(false, "You don't have access to this wishlist.");

        var alreadyClaimed = item.Claims.Sum(c => c.Quantity);
        var mine = item.Claims.FirstOrDefault(c => c.ClaimedByUserId == userId);
        var othersClaimed = alreadyClaimed - (mine?.Quantity ?? 0);

        if (othersClaimed + quantity > item.Quantity)
            return new ClaimResult(false, $"Only {item.Quantity - othersClaimed} left to claim.");

        if (mine is not null)
        {
            mine.Quantity = quantity;
            mine.Note = note;
        }
        else
        {
            db.Claims.Add(new Claim
            {
                WishlistItemId = itemId,
                ClaimedByUserId = userId,
                Quantity = quantity,
                Note = note,
            });
        }
        await db.SaveChangesAsync();
        return new ClaimResult(true);
    }

    public async Task<ClaimResult> UnclaimAsync(int itemId, string userId)
    {
        await using var db = await dbFactory.CreateDbContextAsync();
        var item = await db.WishlistItems.Include(i => i.Wishlist).FirstOrDefaultAsync(i => i.Id == itemId);
        if (item is null) return new ClaimResult(false, "Item not found.");
        if (item.Wishlist!.OwnerId == userId)
            return new ClaimResult(false, "Owners have no claims to remove.");

        var mine = await db.Claims.FirstOrDefaultAsync(c => c.WishlistItemId == itemId && c.ClaimedByUserId == userId);
        if (mine is null) return new ClaimResult(false, "You haven't claimed this item.");

        db.Claims.Remove(mine);
        await db.SaveChangesAsync();
        return new ClaimResult(true);
    }
}
