using Microsoft.EntityFrameworkCore; using MoneyMap.Data; using MoneyMap.Models; namespace MoneyMap.Services; /// /// Service for account management including retrieval, validation, and deletion. /// public interface IAccountService { /// /// Gets an account by ID with optional related data. /// Task GetAccountByIdAsync(int id, bool includeRelated = false); /// /// Gets all accounts with optional statistics. /// Task> GetAllAccountsWithStatsAsync(); /// /// Gets account details with cards and transaction count. /// Task GetAccountDetailsAsync(int id); /// /// Checks if an account can be deleted (no transactions exist). /// Task CanDeleteAccountAsync(int id); /// /// Deletes an account if it has no associated transactions. /// Task DeleteAccountAsync(int id); } public class AccountService : IAccountService { private readonly MoneyMapContext _db; public AccountService(MoneyMapContext db) { _db = db; } public async Task GetAccountByIdAsync(int id, bool includeRelated = false) { var query = _db.Accounts.AsQueryable(); if (includeRelated) { query = query .Include(a => a.Cards) .Include(a => a.Transactions); } return await query.FirstOrDefaultAsync(a => a.Id == id); } public async Task> GetAllAccountsWithStatsAsync() { var accounts = await _db.Accounts .Include(a => a.Transactions) .OrderBy(a => a.Owner) .ThenBy(a => a.Institution) .ThenBy(a => a.Last4) .ToListAsync(); return accounts.Select(a => new AccountWithStats { Id = a.Id, Institution = a.Institution, AccountType = a.AccountType, Last4 = a.Last4, Owner = a.Owner, Nickname = a.Nickname, TransactionCount = a.Transactions.Count }).ToList(); } public async Task GetAccountDetailsAsync(int id) { var account = await _db.Accounts.FindAsync(id); if (account == null) return null; // Single query with projection to avoid N+1 var cardStats = await _db.Cards .Where(c => c.AccountId == id) .OrderBy(c => c.Owner) .ThenBy(c => c.Last4) .Select(c => new CardWithStats { Card = c, TransactionCount = c.Transactions.Count }) .ToListAsync(); // Get transaction count for this account var accountTransactionCount = await _db.Transactions.CountAsync(t => t.AccountId == id); return new AccountDetails { Account = account, Cards = cardStats, TransactionCount = accountTransactionCount }; } public async Task CanDeleteAccountAsync(int id) { var account = await _db.Accounts .Include(a => a.Transactions) .FirstOrDefaultAsync(a => a.Id == id); if (account == null) return new DeleteValidationResult { CanDelete = false, Reason = "Account not found." }; if (account.Transactions.Any()) return new DeleteValidationResult { CanDelete = false, Reason = $"Cannot delete account. It has {account.Transactions.Count} transaction(s) associated with it." }; return new DeleteValidationResult { CanDelete = true }; } public async Task DeleteAccountAsync(int id) { var validation = await CanDeleteAccountAsync(id); if (!validation.CanDelete) { return new DeleteResult { Success = false, Message = validation.Reason ?? "Cannot delete account." }; } var account = await _db.Accounts.FindAsync(id); if (account == null) { return new DeleteResult { Success = false, Message = "Account not found." }; } _db.Accounts.Remove(account); await _db.SaveChangesAsync(); return new DeleteResult { Success = true, Message = $"Deleted account {account.Institution} {account.Last4}" }; } } // DTOs public class AccountWithStats { public int Id { get; set; } public string Institution { get; set; } = ""; public AccountType AccountType { get; set; } public string Last4 { get; set; } = ""; public string Owner { get; set; } = ""; public string? Nickname { get; set; } public int TransactionCount { get; set; } } public class AccountDetails { public Account Account { get; set; } = null!; public List Cards { get; set; } = new(); public int TransactionCount { get; set; } } public class CardWithStats { public Card Card { get; set; } = null!; public int TransactionCount { get; set; } } public class DeleteValidationResult { public bool CanDelete { get; set; } public string? Reason { get; set; } } public class DeleteResult { public bool Success { get; set; } public string Message { get; set; } = ""; }