using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Moq;
using System.Security.Claims;
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 Form1099ReportServiceTests
{
    private static AppDbContext NewDb()
    {
        var httpContext = new DefaultHttpContext { User = new(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "t") })) };
        var accessorMock = new Mock<IHttpContextAccessor>();
        accessorMock.Setup(x => x.HttpContext).Returns(httpContext);
        return new AppDbContext(new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .AddInterceptors(new AuditSaveChangesInterceptor(new ROLAC.API.Services.Logging.CurrentUserAccessor(accessorMock.Object))).Options);
    }

    private static AppDbContext Seeded(out int necSubId, out int rentSubId, out int salarySubId)
    {
        var db = NewDb();
        db.Ministries.Add(new Ministry { Id = 1, Name_en = "Admin", DefaultFunctionalClass = "Program" });
        var nec  = new Form1099Box { Id = 1, BoxCode = Form1099.BoxNec1,  Name_en = "NEC",  FormType = "1099-NEC",  SortOrder = 1 };
        var rent = new Form1099Box { Id = 2, BoxCode = Form1099.BoxMisc1, Name_en = "Rent", FormType = "1099-MISC", SortOrder = 2 };
        db.Form1099Boxes.AddRange(nec, rent);
        db.ExpenseCategoryGroups.Add(new ExpenseCategoryGroup { Id = 1, Name_en = "Personnel" });
        db.ExpenseCategoryGroups.Add(new ExpenseCategoryGroup { Id = 2, Name_en = "Facility" });
        db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 1, GroupId = 1, Name_en = "Contract Labor", Form1099BoxId = 1 });
        db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 2, GroupId = 2, Name_en = "Rent",           Form1099BoxId = 2 });
        db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 3, GroupId = 1, Name_en = "Salary & Wages", Form1099BoxId = null });
        db.SaveChanges();
        necSubId = 1; rentSubId = 2; salarySubId = 3;
        return db;
    }

    private static void AddPaidExpense(AppDbContext db, int payeeId, int subId, int groupId, decimal amount, DateOnly paidOn)
    {
        var e = new Expense
        {
            MinistryId = 1, Type = "VendorPayment", Status = "Paid", PayeeId = payeeId,
            Amount = amount, Description = "x", ExpenseDate = paidOn,
            PaidAt = new DateTimeOffset(paidOn.ToDateTime(TimeOnly.MinValue), TimeSpan.Zero),
            Lines = [ new ExpenseLine { CategoryGroupId = groupId, SubCategoryId = subId, Amount = amount } ],
        };
        db.Expenses.Add(e);
        db.SaveChanges();
    }

    [Fact]
    public async Task Sums_tracked_recipient_by_box_and_flags_threshold_and_w9()
    {
        var db = Seeded(out var necSub, out var rentSub, out _);
        db.Payee1099s.Add(new Payee1099 { Id = 10, LegalName = "Pat Player", Is1099Tracked = true, W9Status = "Missing" });
        db.SaveChanges();
        AddPaidExpense(db, 10, necSub, 1, 700m, new DateOnly(2026, 3, 1));
        AddPaidExpense(db, 10, rentSub, 2, 500m, new DateOnly(2026, 4, 1));

        var svc = new Form1099ReportService(db);
        var sum = await svc.GetAnnualSummaryAsync(2026);

        var row = Assert.Single(sum.Rows);
        Assert.Equal(700m, row.NecTotal);
        Assert.Equal(500m, row.RentsTotal);
        Assert.Equal(1200m, row.GrandTotal);
        Assert.True(row.MeetsThreshold);
        Assert.True(row.W9Missing);
        Assert.Equal(1, sum.RecipientsAtThreshold);
        Assert.Equal(1, sum.RecipientsMissingW9);
    }

    [Fact]
    public async Task Excludes_untracked_recipients_and_unmapped_and_wrong_year()
    {
        var db = Seeded(out var necSub, out _, out var salarySub);
        db.Payee1099s.Add(new Payee1099 { Id = 10, LegalName = "Tracked Tim", Is1099Tracked = true, W9Status = "OnFile" });
        db.Payee1099s.Add(new Payee1099 { Id = 11, LegalName = "Corp Inc",    Is1099Tracked = false, W9Status = "OnFile" });
        db.SaveChanges();
        AddPaidExpense(db, 11, necSub,    1, 5000m, new DateOnly(2026, 5, 1)); // untracked
        AddPaidExpense(db, 10, salarySub, 1, 5000m, new DateOnly(2026, 6, 1)); // unmapped box
        AddPaidExpense(db, 10, necSub,    1, 5000m, new DateOnly(2025, 6, 1)); // wrong year

        var sum = await new Form1099ReportService(db).GetAnnualSummaryAsync(2026);
        Assert.Empty(sum.Rows);
    }

    [Fact]
    public async Task Threshold_flag_is_false_below_600()
    {
        var db = Seeded(out var necSub, out _, out _);
        db.Payee1099s.Add(new Payee1099 { Id = 10, LegalName = "Small Sam", Is1099Tracked = true, W9Status = "OnFile" });
        db.SaveChanges();
        AddPaidExpense(db, 10, necSub, 1, 599.99m, new DateOnly(2026, 7, 1));

        var sum = await new Form1099ReportService(db).GetAnnualSummaryAsync(2026);
        var row = Assert.Single(sum.Rows);
        Assert.False(row.MeetsThreshold);
        Assert.False(row.W9Missing);
        Assert.Equal(0, sum.RecipientsAtThreshold);
    }
}
