using Microsoft.EntityFrameworkCore; using MoneyMap.Data; using MoneyMap.Models; namespace MoneyMap.Services; /// /// Service for calculating transaction statistics and aggregates. /// public interface ITransactionStatisticsService { /// /// Calculates statistics for a filtered set of transactions. /// Task CalculateStatsAsync(IQueryable query); /// /// Gets categorization statistics for the entire database. /// Task GetCategorizationStatsAsync(); /// /// Gets card statistics for a specific account. /// Task> GetCardStatsForAccountAsync(int accountId); } public class TransactionStatisticsService : ITransactionStatisticsService { private readonly MoneyMapContext _db; public TransactionStatisticsService(MoneyMapContext db) { _db = db; } public async Task CalculateStatsAsync(IQueryable query) { // Calculate stats at database level instead of loading all transactions into memory var stats = await query .GroupBy(_ => 1) // Group all into one group to aggregate .Select(g => new TransactionStats { Count = g.Count(), TotalDebits = g.Where(t => t.Amount < 0).Sum(t => t.Amount), TotalCredits = g.Where(t => t.Amount > 0).Sum(t => t.Amount), NetAmount = g.Sum(t => t.Amount) }) .FirstOrDefaultAsync(); return stats ?? new TransactionStats(); } public async Task GetCategorizationStatsAsync() { var totalTransactions = await _db.Transactions.CountAsync(); var uncategorized = await _db.Transactions .CountAsync(t => string.IsNullOrWhiteSpace(t.Category)); var categorized = totalTransactions - uncategorized; return new CategorizationStats { TotalTransactions = totalTransactions, Categorized = categorized, Uncategorized = uncategorized }; } public async Task> GetCardStatsForAccountAsync(int accountId) { // Single query with projection to avoid N+1 return await _db.Cards .Where(c => c.AccountId == accountId) .OrderBy(c => c.Owner) .ThenBy(c => c.Last4) .Select(c => new CardStats { Card = c, TransactionCount = c.Transactions.Count }) .ToListAsync(); } } // DTOs public class TransactionStats { public int Count { get; set; } public decimal TotalDebits { get; set; } public decimal TotalCredits { get; set; } public decimal NetAmount { get; set; } } public class CategorizationStats { public int TotalTransactions { get; set; } public int Categorized { get; set; } public int Uncategorized { get; set; } } public class CardStats { public Card Card { get; set; } = null!; public int TransactionCount { get; set; } }