using Microsoft.EntityFrameworkCore; using Purrse.Core.DTOs; using Purrse.Core.Enums; using Purrse.Core.Interfaces.Services; using Purrse.Data; namespace Purrse.Api.Services; public class DashboardService : IDashboardService { private readonly PurrseDbContext _db; public DashboardService(PurrseDbContext db) => _db = db; public async Task GetDashboardAsync(Guid userId) { var accounts = await _db.Accounts .Where(a => a.UserId == userId && a.IsActive) .OrderBy(a => a.SortOrder) .ToListAsync(); var assetTypes = new[] { AccountType.Checking, AccountType.Savings, AccountType.Cash, AccountType.Brokerage, AccountType.Retirement401k, AccountType.IRA }; var totalAssets = accounts.Where(a => assetTypes.Contains(a.Type)).Sum(a => a.Balance); var totalLiabilities = accounts.Where(a => !assetTypes.Contains(a.Type)).Sum(a => Math.Abs(a.Balance)); var netWorth = totalAssets - totalLiabilities; var accountSummaries = accounts.Select(a => new AccountSummaryResponse(a.Id, a.Name, a.Type, a.Institution, a.Balance, a.CreditLimit)).ToList(); var now = DateTime.UtcNow; var startOfMonth = new DateTime(now.Year, now.Month, 1, 0, 0, 0, DateTimeKind.Utc); var startOfLastMonth = startOfMonth.AddMonths(-1); var recentTxns = await _db.Transactions .Include(t => t.Account).Include(t => t.Category) .Where(t => t.Account.UserId == userId && !t.IsVoid) .OrderByDescending(t => t.Date).ThenByDescending(t => t.CreatedAt) .Take(10) .Select(t => new TransactionResponse(t.Id, t.AccountId, t.Account.Name, t.Date, t.Amount, t.PayeeId, t.PayeeName, t.CategoryId, t.Category != null ? t.Category.Name : null, t.Memo, t.ReferenceNumber, t.CheckNumber, t.Type, t.Status, t.IsVoid, t.TransferTransactionId, t.ImportBatchId, null, t.CreatedAt)) .ToListAsync(); var thisMonthSpending = await _db.Transactions .Where(t => t.Account.UserId == userId && t.Date >= startOfMonth && t.Amount < 0 && !t.IsVoid) .SumAsync(t => Math.Abs(t.Amount)); var lastMonthSpending = await _db.Transactions .Where(t => t.Account.UserId == userId && t.Date >= startOfLastMonth && t.Date < startOfMonth && t.Amount < 0 && !t.IsVoid) .SumAsync(t => Math.Abs(t.Amount)); var changePercent = lastMonthSpending == 0 ? 0 : ((thisMonthSpending - lastMonthSpending) / lastMonthSpending) * 100; var spendingByCategory = await _db.Transactions .Where(t => t.Account.UserId == userId && t.Date >= startOfMonth && t.Amount < 0 && !t.IsVoid) .GroupBy(t => t.CategoryId) .Select(g => new { CategoryId = g.Key, Total = -g.Sum(t => t.Amount) }) .OrderByDescending(x => x.Total) .Take(10) .ToListAsync(); var categoryIds = spendingByCategory.Where(x => x.CategoryId.HasValue).Select(x => x.CategoryId!.Value); var categoryNames = await _db.Categories .Where(c => categoryIds.Contains(c.Id)) .ToDictionaryAsync(c => c.Id, c => c.Name); var totalSpending = spendingByCategory.Sum(x => x.Total); var topCategories = spendingByCategory.Select(x => new CategorySpending( x.CategoryId.HasValue ? categoryNames.GetValueOrDefault(x.CategoryId.Value, "Unknown") : "Uncategorized", x.Total, totalSpending > 0 ? (x.Total / totalSpending) * 100 : 0, null)).ToList(); var spendingSummary = new SpendingSummary(thisMonthSpending, lastMonthSpending, changePercent, topCategories); var upcomingBills = await _db.ScheduledTransactions .Include(s => s.Account) .Where(s => s.UserId == userId && s.IsActive && s.NextDueDate <= now.AddDays(14)) .OrderBy(s => s.NextDueDate) .Take(10) .Select(s => new UpcomingBill(s.Id, s.PayeeName ?? "", s.Amount, s.NextDueDate, s.Account.Name, s.AutoPost)) .ToListAsync(); return new DashboardResponse(netWorth, totalAssets, totalLiabilities, accountSummaries, recentTxns, spendingSummary, upcomingBills); } }