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

namespace WishNinja.Tests;

public class WishlistServiceTests : IDisposable
{
    private readonly TestDb _db = new();
    private readonly WishlistService _svc;

    private const string OwnerId = "owner-1";
    private const string FriendId = "friend-1";
    private const string OutsiderId = "outsider-1";

    public WishlistServiceTests()
    {
        _svc = new WishlistService(_db);
        Seed();
    }

    private void Seed()
    {
        using var ctx = _db.CreateDbContext();
        ctx.Users.AddRange(
            new ApplicationUser { Id = OwnerId, UserName = "owner@x", DisplayName = "Owner" },
            new ApplicationUser { Id = FriendId, UserName = "friend@x", DisplayName = "Friend" },
            new ApplicationUser { Id = OutsiderId, UserName = "out@x", DisplayName = "Outsider" });

        var list = new Wishlist
        {
            Id = 1,
            OwnerId = OwnerId,
            Title = "Birthday",
            Visibility = WishlistVisibility.AllMembers,
            Items =
            {
                new WishlistItem { Id = 10, Name = "Book", Quantity = 1 },
                new WishlistItem { Id = 11, Name = "Mug", Quantity = 3 },
            },
        };
        ctx.Wishlists.Add(list);
        ctx.SaveChanges();
    }

    // --- The defining rule: the owner never sees claim data --------------------------------------

    [Fact]
    public async Task Owner_view_never_exposes_claims()
    {
        // Friend claims both items.
        await _svc.ClaimAsync(10, FriendId, 1, "got it");
        await _svc.ClaimAsync(11, FriendId, 2, null);

        var view = await _svc.GetDetailAsync(1, OwnerId);

        Assert.NotNull(view);
        Assert.True(view!.IsViewerOwner);
        Assert.All(view.Items, iv =>
        {
            Assert.True(iv.IsViewerOwner);
            Assert.Equal(0, iv.ClaimedQuantity);
            Assert.False(iv.ClaimedByViewer);
            Assert.Empty(iv.OtherClaims);
            // Remaining always equals the wanted quantity for the owner — no leakage.
            Assert.Equal(iv.Item.Quantity, iv.RemainingQuantity);
        });
    }

    [Fact]
    public async Task NonOwner_view_shows_claims()
    {
        await _svc.ClaimAsync(11, FriendId, 2, "two mugs");

        // Outsider (also a member) sees the claim made by Friend.
        var outsiderView = await _svc.GetDetailAsync(1, OutsiderId);
        var mug = outsiderView!.Items.Single(i => i.Item.Id == 11);
        Assert.False(outsiderView.IsViewerOwner);
        Assert.Equal(2, mug.ClaimedQuantity);
        Assert.False(mug.ClaimedByViewer);
        Assert.Single(mug.OtherClaims);
        Assert.Equal("Friend", mug.OtherClaims[0].ClaimedByDisplayName);

        // Friend sees their own claim flagged.
        var friendView = await _svc.GetDetailAsync(1, FriendId);
        var friendMug = friendView!.Items.Single(i => i.Item.Id == 11);
        Assert.True(friendMug.ClaimedByViewer);
        Assert.Equal(1, friendMug.RemainingQuantity); // 3 wanted - 2 claimed
    }

    // --- Claim mutation rules --------------------------------------------------------------------

    [Fact]
    public async Task Owner_cannot_claim_their_own_item()
    {
        var result = await _svc.ClaimAsync(10, OwnerId, 1, null);
        Assert.False(result.Succeeded);
    }

    [Fact]
    public async Task Claim_cannot_exceed_remaining_quantity()
    {
        var first = await _svc.ClaimAsync(11, FriendId, 2, null);
        Assert.True(first.Succeeded);

        // Only 1 of 3 remains; outsider asking for 2 should fail.
        var second = await _svc.ClaimAsync(11, OutsiderId, 2, null);
        Assert.False(second.Succeeded);

        // But claiming the last 1 succeeds.
        var third = await _svc.ClaimAsync(11, OutsiderId, 1, null);
        Assert.True(third.Succeeded);
    }

    [Fact]
    public async Task Unclaim_frees_the_quantity()
    {
        await _svc.ClaimAsync(10, FriendId, 1, null);
        var blocked = await _svc.ClaimAsync(10, OutsiderId, 1, null);
        Assert.False(blocked.Succeeded); // single-quantity item already taken

        await _svc.UnclaimAsync(10, FriendId);
        var nowOk = await _svc.ClaimAsync(10, OutsiderId, 1, null);
        Assert.True(nowOk.Succeeded);
    }

    // --- Visibility ------------------------------------------------------------------------------

    [Fact]
    public async Task SpecificUsers_visibility_blocks_unshared_users()
    {
        using (var ctx = _db.CreateDbContext())
        {
            var list = ctx.Wishlists.Single(w => w.Id == 1);
            list.Visibility = WishlistVisibility.SpecificUsers;
            ctx.WishlistShares.Add(new WishlistShare { WishlistId = 1, UserId = FriendId });
            ctx.SaveChanges();
        }

        Assert.NotNull(await _svc.GetDetailAsync(1, FriendId));   // explicitly shared
        Assert.Null(await _svc.GetDetailAsync(1, OutsiderId));    // not shared
        Assert.NotNull(await _svc.GetDetailAsync(1, OwnerId));    // owner always
    }

    public void Dispose() => _db.Dispose();
}
