using Microsoft.EntityFrameworkCore; using MoneyMap.Data; using MoneyMap.Models; namespace MoneyMap.Services { public interface IMerchantService { Task FindByNameAsync(string name); Task GetOrCreateAsync(string name); Task GetOrCreateIdAsync(string? name); Task GetMerchantByIdAsync(int id, bool includeRelated = false); Task> GetAllMerchantsWithStatsAsync(); Task UpdateMerchantAsync(int id, string newName); Task DeleteMerchantAsync(int id); } public class MerchantService : IMerchantService { private readonly MoneyMapContext _db; public MerchantService(MoneyMapContext db) { _db = db; } public async Task FindByNameAsync(string name) { if (string.IsNullOrWhiteSpace(name)) return null; return await _db.Merchants .FirstOrDefaultAsync(m => m.Name == name.Trim()); } public async Task GetOrCreateAsync(string name) { var trimmedName = name.Trim(); var existing = await _db.Merchants .FirstOrDefaultAsync(m => m.Name == trimmedName); if (existing != null) return existing; var merchant = new Merchant { Name = trimmedName }; _db.Merchants.Add(merchant); await _db.SaveChangesAsync(); return merchant; } public async Task GetOrCreateIdAsync(string? name) { if (string.IsNullOrWhiteSpace(name)) return null; var merchant = await GetOrCreateAsync(name); return merchant.Id; } public async Task GetMerchantByIdAsync(int id, bool includeRelated = false) { var query = _db.Merchants.AsQueryable(); if (includeRelated) { query = query .Include(m => m.Transactions) .Include(m => m.CategoryMappings); } return await query.FirstOrDefaultAsync(m => m.Id == id); } public async Task> GetAllMerchantsWithStatsAsync() { var merchants = await _db.Merchants .Include(m => m.Transactions) .Include(m => m.CategoryMappings) .OrderBy(m => m.Name) .ToListAsync(); return merchants.Select(m => new MerchantWithStats { Id = m.Id, Name = m.Name, TransactionCount = m.Transactions.Count, MappingCount = m.CategoryMappings.Count }).ToList(); } public async Task UpdateMerchantAsync(int id, string newName) { var merchant = await _db.Merchants.FindAsync(id); if (merchant == null) { return new MerchantUpdateResult { Success = false, Message = "Merchant not found." }; } var trimmedName = newName.Trim(); // Check if another merchant with the same name exists var existing = await _db.Merchants .FirstOrDefaultAsync(m => m.Name == trimmedName && m.Id != id); if (existing != null) { return new MerchantUpdateResult { Success = false, Message = $"Merchant '{trimmedName}' already exists." }; } merchant.Name = trimmedName; await _db.SaveChangesAsync(); return new MerchantUpdateResult { Success = true, Message = "Merchant updated successfully." }; } public async Task DeleteMerchantAsync(int id) { var merchant = await _db.Merchants .Include(m => m.Transactions) .Include(m => m.CategoryMappings) .FirstOrDefaultAsync(m => m.Id == id); if (merchant == null) { return new MerchantDeleteResult { Success = false, Message = "Merchant not found." }; } var transactionCount = merchant.Transactions.Count; var mappingCount = merchant.CategoryMappings.Count; _db.Merchants.Remove(merchant); await _db.SaveChangesAsync(); return new MerchantDeleteResult { Success = true, Message = $"Deleted merchant '{merchant.Name}'. {transactionCount} transactions and {mappingCount} category mappings are now unlinked.", TransactionCount = transactionCount, MappingCount = mappingCount }; } } // DTOs public class MerchantWithStats { public int Id { get; set; } public string Name { get; set; } = ""; public int TransactionCount { get; set; } public int MappingCount { get; set; } } public class MerchantUpdateResult { public bool Success { get; set; } public string Message { get; set; } = ""; } public class MerchantDeleteResult { public bool Success { get; set; } public string Message { get; set; } = ""; public int TransactionCount { get; set; } public int MappingCount { get; set; } } }