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.Entities;
using ROLAC.API.Services;
using Xunit;

namespace ROLAC.API.Tests.Services;

public class Form990ReportServiceTests
{
    private static AppDbContext BuildDb()
    {
        var ctx  = new DefaultHttpContext { User = new(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "t") })) };
        var mock = new Mock<IHttpContextAccessor>();
        mock.Setup(x => x.HttpContext).Returns(ctx);
        return new AppDbContext(new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .AddInterceptors(new AuditSaveChangesInterceptor(new ROLAC.API.Services.Logging.CurrentUserAccessor(mock.Object))).Options);
    }

    private static async Task SeedAsync(AppDbContext db)
    {
        db.Form990ExpenseLines.Add(new Form990ExpenseLine { Id = 7,  LineCode = "7",  Name_en = "Salaries", SortOrder = 5 });
        db.Form990ExpenseLines.Add(new Form990ExpenseLine { Id = 24, LineCode = "24", Name_en = "Other",    SortOrder = 21 });
        db.Ministries.Add(new Ministry { Id = 1, Name_en = "Admin",   DefaultFunctionalClass = "ManagementGeneral" });
        db.Ministries.Add(new Ministry { Id = 2, Name_en = "Worship", DefaultFunctionalClass = "Program" });
        db.ExpenseCategoryGroups.Add(new ExpenseCategoryGroup { Id = 1, Name_en = "Personnel", Form990LineId = 24 });
        db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 1, GroupId = 1, Name_en = "Salary", Form990LineId = 7 });
        db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 2, GroupId = 1, Name_en = "Misc",   Form990LineId = null });
        await db.SaveChangesAsync();
    }

    private static Expense Exp(int min, int sub, decimal amt, string status, string? fc = null) => new()
    {
        MinistryId = min, Type = "VendorPayment",
        Status = status, Amount = amt, Description = "x", ExpenseDate = new DateOnly(2026, 5, 10),
        Lines = { new ExpenseLine { CategoryGroupId = 1, SubCategoryId = sub, Amount = amt, FunctionalClass = fc } },
    };

    [Fact]
    public async Task Statement_AggregatesByLineAndFunction_WithFallbackAndUnmappedCount()
    {
        using var db = BuildDb();
        await SeedAsync(db);
        db.Expenses.Add(Exp(2, 1, 100m, "Paid"));
        db.Expenses.Add(Exp(1, 1, 40m,  "Approved"));
        db.Expenses.Add(Exp(2, 2, 25m,  "Paid"));
        db.Expenses.Add(Exp(2, 1, 999m, "Draft"));
        db.Expenses.Add(Exp(1, 1, 10m,  "Paid", fc: "Program"));
        await db.SaveChangesAsync();
        var svc = new Form990ReportService(db);

        var stmt = await svc.GetFunctionalExpenseStatementAsync(null, null);

        var line7 = stmt.Rows.Single(r => r.LineCode == "7");
        Assert.Equal(110m, line7.Program);
        Assert.Equal(40m,  line7.ManagementGeneral);
        Assert.Equal(150m, line7.Total);
        var line24 = stmt.Rows.Single(r => r.LineCode == "24");
        Assert.Equal(25m, line24.Program);
        Assert.Equal(1,   stmt.UnmappedExpenseCount);
        Assert.Equal(175m, stmt.GrandTotal);
        Assert.Equal(135m, stmt.ProgramTotal);
        Assert.Equal(40m,  stmt.ManagementGeneralTotal);
    }

    [Fact]
    public async Task Statement_RespectsDateRange()
    {
        using var db = BuildDb();
        await SeedAsync(db);
        db.Expenses.Add(Exp(2, 1, 100m, "Paid"));
        var older = Exp(2, 1, 500m, "Paid"); older.ExpenseDate = new DateOnly(2026, 1, 1);
        db.Expenses.Add(older);
        await db.SaveChangesAsync();
        var svc = new Form990ReportService(db);

        var stmt = await svc.GetFunctionalExpenseStatementAsync(new DateOnly(2026, 5, 1), new DateOnly(2026, 5, 31));
        Assert.Equal(100m, stmt.GrandTotal);
    }

    [Fact]
    public async Task Statement_SplitsOneExpenseAcrossLines()
    {
        // One invoice with two lines of different categories must land on two different 990 lines.
        using var db = BuildDb();
        await SeedAsync(db);
        db.Expenses.Add(new Expense
        {
            MinistryId = 2, Type = "VendorPayment", Status = "Paid", Amount = 70m,
            Description = "mixed", ExpenseDate = new DateOnly(2026, 5, 10),
            Lines =
            {
                new ExpenseLine { CategoryGroupId = 1, SubCategoryId = 1, Amount = 50m }, // sub→line 7
                new ExpenseLine { CategoryGroupId = 1, SubCategoryId = 2, Amount = 20m }, // sub unmapped→group fallback line 24
            },
        });
        await db.SaveChangesAsync();
        var svc = new Form990ReportService(db);

        var stmt = await svc.GetFunctionalExpenseStatementAsync(null, null);

        Assert.Equal(50m, stmt.Rows.Single(r => r.LineCode == "7").Program);   // ministry 2 default = Program
        Assert.Equal(20m, stmt.Rows.Single(r => r.LineCode == "24").Program);
        Assert.Equal(70m, stmt.GrandTotal);
        Assert.Equal(1,   stmt.UnmappedExpenseCount);                          // one unmapped line
    }
}
