using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Moq;
using ROLAC.API.Data;
using ROLAC.API.Data.Interceptors;
using ROLAC.API.DTOs.Giving;
using ROLAC.API.Entities;
using ROLAC.API.Services;
using Xunit;

namespace ROLAC.API.Tests.Services;

public class GivingServiceTests
{
    private static IHttpContextAccessor BuildAccessor(string userId = "test-user")
    {
        var claims = new[] { new Claim(ClaimTypes.NameIdentifier, userId) };
        var ctx    = new DefaultHttpContext { User = new(new ClaimsIdentity(claims)) };
        var mock   = new Mock<IHttpContextAccessor>();
        mock.Setup(x => x.HttpContext).Returns(ctx);
        return mock.Object;
    }

    private static AppDbContext BuildDb(string userId = "test-user")
    {
        var interceptor = new AuditSaveChangesInterceptor(new ROLAC.API.Services.Logging.CurrentUserAccessor(BuildAccessor(userId)));
        return new AppDbContext(
            new DbContextOptionsBuilder<AppDbContext>()
                .UseInMemoryDatabase(Guid.NewGuid().ToString())
                .AddInterceptors(interceptor)
                .Options);
    }

    private static async Task<int> SeedCategoryAsync(AppDbContext db)
    {
        var c = new GivingCategory { Name_en = "Tithe", IsActive = true };
        db.GivingCategories.Add(c);
        await db.SaveChangesAsync();
        return c.Id;
    }

    [Fact]
    public async Task CreateAsync_PersistsGiving()
    {
        using var db = BuildDb();
        var catId = await SeedCategoryAsync(db);
        var svc = new GivingService(db);

        var id = await svc.CreateAsync(new CreateGivingRequest
        {
            GivingCategoryId = catId, Amount = 100m, PaymentMethod = "Cash",
            GivingDate = new DateOnly(2026, 5, 31), IsAnonymous = true,
        });

        var saved = await db.Givings.FindAsync(id);
        Assert.NotNull(saved);
        Assert.Equal(100m, saved!.Amount);
        Assert.Null(saved.OfferingSessionId);
    }

    [Fact]
    public async Task GetPagedAsync_FiltersByCategory()
    {
        using var db = BuildDb();
        var catId = await SeedCategoryAsync(db);
        var svc = new GivingService(db);
        await svc.CreateAsync(new CreateGivingRequest { GivingCategoryId = catId, Amount = 10m, PaymentMethod = "Cash", GivingDate = new DateOnly(2026,5,31) });

        var page = await svc.GetPagedAsync(1, 20, null, catId, null, null);

        Assert.Equal(1, page.TotalCount);
        Assert.Equal("Tithe", page.Items[0].CategoryName);
    }

    [Fact]
    public async Task UpdateAsync_Throws_WhenGivingBelongsToSubmittedSession()
    {
        using var db = BuildDb();
        var catId = await SeedCategoryAsync(db);
        var session = new OfferingSession { SessionDate = new DateOnly(2026,5,31), Status = "Submitted" };
        db.OfferingSessions.Add(session);
        await db.SaveChangesAsync();
        var giving = new Giving { GivingCategoryId = catId, Amount = 50m, PaymentMethod = "Cash",
            GivingDate = new DateOnly(2026,5,31), OfferingSessionId = session.Id };
        db.Givings.Add(giving);
        await db.SaveChangesAsync();

        var svc = new GivingService(db);

        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            svc.UpdateAsync(giving.Id, new UpdateGivingRequest
            { GivingCategoryId = catId, Amount = 999m, PaymentMethod = "Cash", GivingDate = new DateOnly(2026,5,31) }));
    }

    [Fact]
    public async Task DeleteAsync_Throws_WhenMissing()
    {
        using var db = BuildDb();
        var svc = new GivingService(db);
        await Assert.ThrowsAsync<KeyNotFoundException>(() => svc.DeleteAsync(999));
    }

    [Fact]
    public async Task CreateAsync_Anonymous_NullsProvidedMemberId()
    {
        using var db = BuildDb();
        var catId = await SeedCategoryAsync(db);
        var svc = new GivingService(db);

        var id = await svc.CreateAsync(new CreateGivingRequest
        {
            GivingCategoryId = catId, Amount = 25m, PaymentMethod = "Cash",
            GivingDate = new DateOnly(2026, 5, 31),
            IsAnonymous = true, MemberId = 12345,   // provided, but must be stripped
        });

        var saved = await db.Givings.FindAsync(id);
        Assert.True(saved!.IsAnonymous);
        Assert.Null(saved.MemberId);
    }

    [Fact]
    public async Task DeleteAsync_Throws_WhenGivingBelongsToSubmittedSession()
    {
        using var db = BuildDb();
        var catId = await SeedCategoryAsync(db);
        var session = new OfferingSession { SessionDate = new DateOnly(2026, 5, 31), Status = "Submitted" };
        db.OfferingSessions.Add(session);
        await db.SaveChangesAsync();
        var giving = new Giving { GivingCategoryId = catId, Amount = 50m, PaymentMethod = "Cash",
            GivingDate = new DateOnly(2026, 5, 31), OfferingSessionId = session.Id };
        db.Givings.Add(giving);
        await db.SaveChangesAsync();

        var svc = new GivingService(db);

        await Assert.ThrowsAsync<InvalidOperationException>(() => svc.DeleteAsync(giving.Id));
    }

    [Fact]
    public async Task GetPagedAsync_MatchesByMemberName()
    {
        using var db = BuildDb();
        var catId = await SeedCategoryAsync(db);
        var member = new Member { FirstName_en = "Grace", LastName_en = "Lee" };
        db.Members.Add(member);
        await db.SaveChangesAsync();
        var svc = new GivingService(db);
        await svc.CreateAsync(new CreateGivingRequest
        {
            GivingCategoryId = catId, Amount = 75m, PaymentMethod = "Cash",
            GivingDate = new DateOnly(2026, 5, 31), MemberId = member.Id,
        });

        var page = await svc.GetPagedAsync(1, 20, "grace", null, null, null);

        Assert.Equal(1, page.TotalCount);
        Assert.Equal(member.Id, page.Items[0].MemberId);
    }
}
