using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data;
using ROLAC.API.DTOs.Finance;
using ROLAC.API.Entities;

namespace ROLAC.API.Services;

/// <summary>
/// Read-only aggregation that produces the IRS Form 990 Part IX Statement of Functional
/// Expenses. Expense scope matches FinanceDashboardService: Paid + Approved only.
/// Each expense line is categorized independently, so one invoice can span multiple lines.
/// </summary>
public class Form990ReportService : IForm990ReportService
{
    private readonly AppDbContext _db;
    public Form990ReportService(AppDbContext db) => _db = db;

    public async Task<List<Form990ExpenseLineDto>> GetLinesAsync() =>
        await _db.Form990ExpenseLines.AsNoTracking().Where(l => l.IsActive)
            .OrderBy(l => l.SortOrder)
            .Select(l => new Form990ExpenseLineDto
            {
                Id        = l.Id,
                LineCode  = l.LineCode,
                Name_en   = l.Name_en,
                Name_zh   = l.Name_zh,
                SortOrder = l.SortOrder,
            })
            .ToListAsync();

    public async Task<FunctionalExpenseStatementDto> GetFunctionalExpenseStatementAsync(DateOnly? from, DateOnly? to)
    {
        var lines = await _db.Form990ExpenseLines.AsNoTracking()
            .Where(l => l.IsActive).OrderBy(l => l.SortOrder).ToListAsync();
        var fallbackId = lines.FirstOrDefault(l => l.LineCode == "24")?.Id;

        var expenses = _db.Expenses.Where(e => e.Status == "Paid" || e.Status == "Approved");
        if (from.HasValue) expenses = expenses.Where(e => e.ExpenseDate >= from.Value);
        if (to.HasValue)   expenses = expenses.Where(e => e.ExpenseDate <= to.Value);

        var rows = await (
            from e in expenses
            join l   in _db.ExpenseLines           on e.Id              equals l.ExpenseId
            join m   in _db.Ministries            on e.MinistryId      equals m.Id
            join sub in _db.ExpenseSubCategories   on l.SubCategoryId   equals sub.Id
            join grp in _db.ExpenseCategoryGroups  on l.CategoryGroupId equals grp.Id
            select new
            {
                l.Amount,
                l.FunctionalClass,
                MinistryDefault = m.DefaultFunctionalClass,
                SubLineId   = sub.Form990LineId,
                GroupLineId = grp.Form990LineId,
            }).ToListAsync();

        var acc = new Dictionary<int, (decimal P, decimal M, decimal F)>();
        var unmapped = 0;

        foreach (var r in rows)
        {
            var function = FunctionalClasses.Normalize(r.FunctionalClass ?? r.MinistryDefault);
            var lineId   = r.SubLineId ?? r.GroupLineId ?? fallbackId;
            if (lineId is null) continue;

            if (r.SubLineId is null) unmapped++;

            var cur = acc.GetValueOrDefault(lineId.Value);
            acc[lineId.Value] = function switch
            {
                FunctionalClasses.ManagementGeneral => (cur.P, cur.M + r.Amount, cur.F),
                FunctionalClasses.Fundraising       => (cur.P, cur.M, cur.F + r.Amount),
                _                                   => (cur.P + r.Amount, cur.M, cur.F),
            };
        }

        var dto = new FunctionalExpenseStatementDto { UnmappedExpenseCount = unmapped };
        foreach (var line in lines)
        {
            var v = acc.GetValueOrDefault(line.Id);
            dto.Rows.Add(new FunctionalExpenseRowDto
            {
                LineCode = line.LineCode, Name_en = line.Name_en, Name_zh = line.Name_zh,
                Program = v.P, ManagementGeneral = v.M, Fundraising = v.F, Total = v.P + v.M + v.F,
            });
            dto.ProgramTotal           += v.P;
            dto.ManagementGeneralTotal += v.M;
            dto.FundraisingTotal       += v.F;
        }
        dto.GrandTotal = dto.ProgramTotal + dto.ManagementGeneralTotal + dto.FundraisingTotal;
        return dto;
    }
}
