using LiteCharms.Features.Abstractions; using LiteCharms.Features.MidrandBooks.Customers.Models; using LiteCharms.Features.MidrandBooks.Extensions; using LiteCharms.Features.MidrandBooks.Postgres; namespace LiteCharms.Features.MidrandBooks.Customers; public sealed class CustomerService(IDbContextFactory contextFactory) : IService { public async ValueTask> CreateCustomerAsync(CreateCustomer request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (await context.Customers.AnyAsync(c => EF.Functions.ILike(c.Email!, $"%{request.Email}%"), cancellationToken)) return Result.Fail(new Error($"Customer with email '{request.Email}' already exists.")); var customer = context.Customers.Add(new Entities.Customer { Company = request.Company, VatNumber = request.VatNumber, Email = request.Email, Website = request.Website, Phone = request.Phone, SocialMedia = request.SocialMedia, Enabled = true }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(customer.Entity.Id) : Result.Fail(new Error("Failed to create customer.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> CreateCustomerContactAsync(long customerId, CreateCustomerContact request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken)) return Result.Fail(new Error($"Customer with ID '{customerId}' does not exist.")); if (await context.Contacts.AnyAsync(cc => cc.CustomerId == customerId && EF.Functions.ILike(cc.Email!, $"%{request.Email}%"), cancellationToken)) return Result.Fail(new Error($"Contact with email '{request.Email}' already exists for this customer.")); var contact = context.Contacts.Add(new Entities.Contact { CustomerId = customerId, Name = request.Name, Email = request.Email, Phone = request.Phone, LastName = request.LastName, Type = request.Type, Enabled = true }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(contact.Entity.Id) : Result.Fail(new Error("Failed to create customer contact.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> CreateCustomerAddressAsync(long customerId, CreateCustomerAddress request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken)) return Result.Fail(new Error($"Customer with ID '{customerId}' does not exist.")); var address = context.Addresses.Add(new Entities.Address { CustomerId = customerId, Street = request.Street, City = request.City, State = request.State, PostalCode = request.PostalCode, Country = request.Country, Type = request.Type, Enabled = true, BuildingType = request.BuildingType, IsPrimary = request.IsPrimary, Name = request.Name }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(address.Entity.Id) : Result.Fail(new Error("Failed to create customer address.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateCustomerAsync(long customerId, UpdateCustomer request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var customer = await context.Customers.FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken); if (customer is null) return Result.Fail(new Error($"Customer with ID '{customerId}' does not exist.")); customer.UpdatedAt = DateTime.UtcNow; customer.Company = request.Company; customer.VatNumber = request.VatNumber; customer.Email = request.Email; customer.Website = request.Website; customer.Phone = request.Phone; customer.SocialMedia = request.SocialMedia; return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail(new Error("Failed to update customer.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateCustomerContactAsync(long contactId, UpdateCustomerContact request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var rowsUpdated = await context.Contacts .Where(cc => cc.Id == contactId) .ExecuteUpdateAsync(setters => setters .SetProperty(cc => cc.Name, request.Name) .SetProperty(cc => cc.LastName, request.LastName) .SetProperty(cc => cc.Email, request.Email) .SetProperty(cc => cc.Phone, request.Phone) .SetProperty(cc => cc.Type, request.Type) .SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken); return rowsUpdated > 0 ? Result.Ok() : Result.Fail(new Error($"Contact with ID '{contactId}' does not exist.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateCustomerAddressAsync(long addressId, UpdateCustomerAddress request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var rowsUpdated = await context.Addresses .Where(a => a.Id == addressId) .ExecuteUpdateAsync(setters => setters .SetProperty(a => a.Street, request.Street) .SetProperty(a => a.City, request.City) .SetProperty(a => a.State, request.State) .SetProperty(a => a.PostalCode, request.PostalCode) .SetProperty(a => a.Country, request.Country) .SetProperty(a => a.Type, request.Type) .SetProperty(a => a.BuildingType, request.BuildingType) .SetProperty(a => a.IsPrimary, request.IsPrimary) .SetProperty(a => a.Name, request.Name) .SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken); return rowsUpdated > 0 ? Result.Ok() : Result.Fail(new Error($"Address with ID '{addressId}' does not exist.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateCustomerStatusAsync(long customerId, bool enabled, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var rowsUpdated = await context.Customers .Where(c => c.Id == customerId) .ExecuteUpdateAsync(setters => setters .SetProperty(c => c.Enabled, enabled) .SetProperty(c => c.UpdatedAt, DateTime.UtcNow), cancellationToken); return rowsUpdated > 0 ? Result.Ok() : Result.Fail(new Error($"Customer with ID '{customerId}' does not exist.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateCustomerContactStatusAsync(long contactId, bool enabled, bool isPrimary, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var rowsUpdated = await context.Contacts .Where(cc => cc.Id == contactId) .ExecuteUpdateAsync(setters => setters .SetProperty(cc => cc.Enabled, enabled) .SetProperty(cc => cc.IsPrimary, isPrimary) .SetProperty(cc => cc.UpdatedAt, DateTime.UtcNow), cancellationToken); return rowsUpdated > 0 ? Result.Ok() : Result.Fail(new Error($"Contact with ID '{contactId}' does not exist.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateCustomerAddressStatusAsync(long addressId, bool enabled, bool isPrimary, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var rowsUpdated = await context.Addresses .Where(a => a.Id == addressId) .ExecuteUpdateAsync(setters => setters .SetProperty(a => a.Enabled, enabled) .SetProperty(a => a.IsPrimary, isPrimary) .SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken); return rowsUpdated > 0 ? Result.Ok() : Result.Fail(new Error($"Address with ID '{addressId}' does not exist.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetCustomersAsync(CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var customers = await context.Customers .AsNoTracking() .Include(c => c.Contacts) .Include(c => c.Addresses) .OrderByDescending(c => c.CreatedAt) .ThenByDescending(c => c.UpdatedAt) .AsSplitQuery() .ToListAsync(cancellationToken); return customers?.Count > 0 ? Result.Ok(customers.Select(c => c.ToModel()).ToArray()) : Result.Fail(new Error("No customers found.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetCustomerContactsAsync(long customerId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken)) return Result.Fail(new Error($"Customer with ID '{customerId}' does not exist.")); var contacts = await context.Contacts .AsNoTracking() .Where(cc => cc.CustomerId == customerId) .OrderByDescending(cc => cc.CreatedAt) .ThenByDescending(cc => cc.UpdatedAt) .ToListAsync(cancellationToken); return contacts?.Count > 0 ? Result.Ok(contacts.Select(cc => cc.ToModel()).ToArray()) : Result.Fail(new Error("No contacts found for the specified customer.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetCustomerAddressesAsync(long customerId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Customers.AnyAsync(c => c.Id == customerId, cancellationToken)) return Result.Fail(new Error($"Customer with ID '{customerId}' does not exist.")); var addresses = await context.Addresses .AsNoTracking() .Where(a => a.CustomerId == customerId) .OrderByDescending(a => a.CreatedAt) .ThenByDescending(a => a.UpdatedAt) .ToListAsync(cancellationToken); return addresses?.Count > 0 ? Result.Ok(addresses.Select(a => a.ToModel()).ToArray()) : Result.Fail(new Error($"No addresses found for customer with ID '{customerId}'.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetCustomerAsync(long customerId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var customer = await context.Customers .AsNoTracking() .Include(c => c.Contacts) .Include(c => c.Addresses) .FirstOrDefaultAsync(c => c.Id == customerId, cancellationToken); return customer is not null ? Result.Ok(customer.ToModel()) : Result.Fail(new Error($"Customer with ID '{customerId}' does not exist.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetCustomerContactAsync(long contactId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var contact = await context.Contacts .AsNoTracking() .FirstOrDefaultAsync(cc => cc.Id == contactId, cancellationToken); return contact is not null ? Result.Ok(contact.ToModel()) : Result.Fail(new Error($"Contact with ID '{contactId}' does not exist.")); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetCustomerAddressAsync(long addressId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var address = await context.Addresses .AsNoTracking() .FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken); return address is not null ? Result.Ok(address.ToModel()) : Result.Fail
(new Error($"Address with ID '{addressId}' does not exist.")); } catch (Exception ex) { return Result.Fail
(new Error(ex.Message).CausedBy(ex)); } } }