using System.Security.Claims;
using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data;
using ROLAC.API.DTOs.Expense;
using ROLAC.API.Entities;
using ROLAC.API.Entities.Logging;
using ROLAC.API.Services.Logging;

namespace ROLAC.API.Services;

public class MonthlyStatementService : IMonthlyStatementService
{
    private readonly AppDbContext _db;
    private readonly IHttpContextAccessor _http;
    private readonly IAuditLogger _audit;
    public MonthlyStatementService(AppDbContext db, IHttpContextAccessor http, IAuditLogger audit)
    { _db = db; _http = http; _audit = audit; }

    // See ExpenseService: the user id lives in the "sub" claim at runtime; NameIdentifier is for tests.
    private string CurrentUserId =>
        _http.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier)
        ?? _http.HttpContext?.User.FindFirstValue("sub")
        ?? "system";

    public async Task<List<MonthlyStatementDto>> GetAllAsync(int? year)
    {
        var query = _db.MonthlyStatements.AsNoTracking().AsQueryable();
        if (year.HasValue) query = query.Where(s => s.Year == year.Value);
        var list = await query.OrderByDescending(s => s.Year).ThenByDescending(s => s.Month).ToListAsync();
        return list.Select(Map).ToList();
    }

    public async Task<MonthlyStatementDto?> GetByIdAsync(int id)
    {
        var s = await _db.MonthlyStatements.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
        return s is null ? null : Map(s);
    }

    public async Task<int> CreateAsync(CreateMonthlyStatementRequest r)
    {
        if (await _db.MonthlyStatements.AnyAsync(s => s.Year == r.Year && s.Month == r.Month))
            throw new InvalidOperationException($"A statement for {r.Year}-{r.Month:D2} already exists.");

        var s = new MonthlyStatement
        {
            Year = r.Year, Month = r.Month,
            OpeningBalance = r.OpeningBalance, TotalOtherIncome = r.TotalOtherIncome,
            BankStatementBalance = r.BankStatementBalance, Notes = r.Notes,
        };
        await RecomputeAsync(s);
        _db.MonthlyStatements.Add(s);
        await _db.SaveChangesAsync();
        return s.Id;
    }

    public async Task UpdateAsync(int id, UpdateMonthlyStatementRequest r)
    {
        var s = await _db.MonthlyStatements.FirstOrDefaultAsync(x => x.Id == id)
            ?? throw new KeyNotFoundException($"MonthlyStatement {id} not found.");
        if (s.IsFinalized) throw new InvalidOperationException("Statement is finalized and cannot be modified.");
        s.OpeningBalance = r.OpeningBalance; s.TotalOtherIncome = r.TotalOtherIncome;
        s.BankStatementBalance = r.BankStatementBalance; s.Notes = r.Notes;
        await RecomputeAsync(s);
        await _db.SaveChangesAsync();
    }

    public async Task FinalizeAsync(int id)
    {
        var s = await _db.MonthlyStatements.FirstOrDefaultAsync(x => x.Id == id)
            ?? throw new KeyNotFoundException($"MonthlyStatement {id} not found.");
        s.IsFinalized = true; s.FinalizedAt = DateTimeOffset.UtcNow; s.FinalizedBy = CurrentUserId;
        await _db.SaveChangesAsync();

        _audit.Write(
            AuditActions.StatementFinalized, AuditCategories.Business, LogLevelEnum.Information,
            entityName: nameof(MonthlyStatement), entityId: s.Id.ToString(),
            summary: $"Monthly statement {s.Year}-{s.Month:D2} finalized");
    }

    private async Task RecomputeAsync(MonthlyStatement s)
    {
        var first = new DateOnly(s.Year, s.Month, 1);
        var next  = first.AddMonths(1);

        s.TotalGiving = await _db.Givings
            .Where(g => g.GivingDate >= first && g.GivingDate < next)
            .SumAsync(g => (decimal?)g.Amount) ?? 0m;

        s.TotalExpenses = await _db.Expenses
            .Where(e => e.Status == "Paid" && e.ExpenseDate >= first && e.ExpenseDate < next)
            .SumAsync(e => (decimal?)e.Amount) ?? 0m;

        s.CalculatedClosingBalance = s.OpeningBalance + s.TotalGiving + s.TotalOtherIncome - s.TotalExpenses;
        s.Difference = s.CalculatedClosingBalance - s.BankStatementBalance;
    }

    private static MonthlyStatementDto Map(MonthlyStatement s) => new()
    {
        Id = s.Id, Year = s.Year, Month = s.Month,
        OpeningBalance = s.OpeningBalance, TotalGiving = s.TotalGiving, TotalOtherIncome = s.TotalOtherIncome,
        TotalExpenses = s.TotalExpenses, CalculatedClosingBalance = s.CalculatedClosingBalance,
        BankStatementBalance = s.BankStatementBalance, Difference = s.Difference,
        Notes = s.Notes, IsFinalized = s.IsFinalized,
    };
}
