using AccountTracking.Api.Data;
using AccountTracking.Api.Models.Dtos;
using Microsoft.EntityFrameworkCore;

namespace AccountTracking.Api.Services;

public class DashboardService(AppDbContext db)
{
    public async Task<SummaryDto> GetSummaryAsync(int year, int? month)
    {
        var query = db.Transactions.Where(t => t.BookingDate.Year == year);
        if (month.HasValue)
            query = query.Where(t => t.BookingDate.Month == month.Value);

        var spent = await query
            .Where(t => t.Amount < 0)
            .SumAsync(t => (decimal?)t.Amount) ?? 0;
        var income = await query
            .Where(t => t.Amount > 0)
            .SumAsync(t => (decimal?)t.Amount) ?? 0;

        return new SummaryDto(Math.Abs(spent), income);
    }

    public async Task<List<CategorySpendingDto>> GetSpendingByCategoryAsync(int year, int? month)
    {
        var query = db.Transactions
            .Where(t => t.BookingDate.Year == year && t.Amount < 0);

        if (month.HasValue)
            query = query.Where(t => t.BookingDate.Month == month.Value);

        return await query
            .GroupBy(t => t.Category ?? "Nezatříděno")
            .Select(g => new CategorySpendingDto(g.Key, Math.Abs(g.Sum(t => t.Amount))))
            .OrderByDescending(c => c.Total)
            .ToListAsync();
    }

    public async Task<List<MonthlyBalanceDto>> GetMonthlyBalancesAsync(int year)
    {
        // Fetch minimal columns, then group in memory to get closing balance per month
        // (latest booking_date, highest id as tiebreaker)
        var rows = await db.Transactions
            .Where(t => t.BookingDate.Year == year)
            .Select(t => new { t.BookingDate, t.Id, t.Balance })
            .ToListAsync();

        return rows
            .GroupBy(t => t.BookingDate.Month)
            .Select(g => new MonthlyBalanceDto(
                g.Key,
                g.OrderByDescending(t => t.BookingDate).ThenByDescending(t => t.Id).First().Balance))
            .OrderBy(r => r.Month)
            .ToList();
    }

    public async Task<List<CumulativeSpendingDto>> GetCumulativeSpendingAsync(int year, int month)
    {
        // Get daily spending totals (absolute values, negative amounts only)
        var dailySpending = await db.Transactions
            .Where(t => t.BookingDate.Year == year
                     && t.BookingDate.Month == month
                     && t.Amount < 0)
            .GroupBy(t => t.BookingDate.Day)
            .Select(g => new { Day = g.Key, Spent = Math.Abs(g.Sum(t => t.Amount)) })
            .OrderBy(g => g.Day)
            .ToListAsync();

        if (dailySpending.Count == 0)
            return [];

        // Fill gaps: for each day from day 1 to last day with spending,
        // carry forward the previous cumulative value
        var result = new List<CumulativeSpendingDto>();
        decimal running = 0;
        int lastDay = dailySpending.Max(d => d.Day);
        var spendingByDay = dailySpending.ToDictionary(d => d.Day, d => d.Spent);

        for (int day = 1; day <= lastDay; day++)
        {
            if (spendingByDay.TryGetValue(day, out var spent))
                running += spent;

            if (day <= lastDay)
                result.Add(new CumulativeSpendingDto(day, running));
        }

        return result;
    }
}
