using LiteCharms.Features.MidrandBooks.Abstractions; using LiteCharms.Features.MidrandBooks.Extensions; using LiteCharms.Features.MidrandBooks.Orders.Models; using LiteCharms.Features.MidrandBooks.Postgres; using LiteCharms.Features.Models; namespace LiteCharms.Features.MidrandBooks.Orders; public sealed class OrderService(IDbContextFactory contextFactory) : IService { public async ValueTask> CreateOrderAsync(long customerId, CreateOrder 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("Customer not found."); var order = context.Orders.Add(new Entities.Order { CustomerId = customerId, Status = OrderStatus.Pending, Total = request.TotalPrice }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(order.Entity.Id) : Result.Fail("Failed to create order."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> AddItemToOrderAsync(long orderId, CreateOrderItem request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken)) return Result.Fail("Order not found."); if(!await context.Books.AnyAsync(ab => ab.Id == request.AuthorBookId, cancellationToken)) return Result.Fail("Author book not found."); if (!await context.Prices.AnyAsync(pp => pp.Id == request.ProductPriceId, cancellationToken)) return Result.Fail("Product price not found."); var orderItem = context.OrderItems.Add(new Entities.OrderItem { OrderId = orderId, AuthorBookId = request.AuthorBookId, ProductPriceId = request.ProductPriceId, Quantity = request.Quantity }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok(orderItem.Entity.Id) : Result.Fail("Failed to add item to order."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask AddItemsToOrderAsync(long orderId, CreateOrderItem[] items, CancellationToken cancellationToken = default) { try { if(items.Length == 0) return Result.Fail("No items to add."); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken)) return Result.Fail("Order not found."); var existingItems = context.OrderItems.Where(oi => oi.OrderId == orderId); context.OrderItems.RemoveRange(existingItems); foreach (var item in items) { if (!await context.Books.AnyAsync(ab => ab.Id == item.AuthorBookId, cancellationToken)) return Result.Fail($"Author book with ID {item.AuthorBookId} not found."); if (!await context.Prices.AnyAsync(pp => pp.Id == item.ProductPriceId, cancellationToken)) return Result.Fail($"Product price with ID {item.ProductPriceId} not found."); context.OrderItems.Add(new Entities.OrderItem { OrderId = orderId, AuthorBookId = item.AuthorBookId, ProductPriceId = item.ProductPriceId, Quantity = item.Quantity }); } return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to add items to order."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask RemoveItemFromOrderAsync(long orderId, long orderItemId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var orderItem = await context.OrderItems.FirstOrDefaultAsync(oi => oi.Id == orderItemId && oi.OrderId == orderId, cancellationToken); if (orderItem is null) return Result.Fail("Order item not found."); context.OrderItems.Remove(orderItem); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to remove item from order."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask ClearOrderItemasAsync(long orderId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var orderItems = context.OrderItems.Where(oi => oi.OrderId == orderId); context.OrderItems.RemoveRange(orderItems); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to clear order items."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask CancelOrderAsync(long orderId, CancellationToken cancellationToken = default) => await UpdateOrderStatusAsync(orderId, OrderStatus.Cancelled, cancellationToken); public async ValueTask> GetOrderAsync(long orderId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var order = await context.Orders.AsNoTracking().FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); return order is not null ? Result.Ok(order.ToModel()) : Result.Fail("Order not found."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetOrdersByCustomerAsync(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("Customer not found."); var orders = await context.Orders .AsNoTracking() .Where(o => o.CustomerId == customerId) .ToListAsync(cancellationToken); return Result.Ok(orders.Select(o => o.ToModel()).ToArray()); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetOrdersAsync(DateRange range, int index, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var orders = await context.Orders .AsNoTracking() .Where(o => o.CreatedAt >= range.From.ToDateTime(TimeOnly.MinValue) && o.CreatedAt <= range.To.ToDateTime(TimeOnly.MaxValue)) .Skip(index * range.MaxRecords) .ToListAsync(cancellationToken); return Result.Ok(orders.Select(o => o.ToModel()).ToArray()); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateOrderStatusAsync(long orderId, OrderStatus newStatus, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var order = await context.Orders.FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); if (order is null) return Result.Fail("Order not found."); order.UpdatedAt = DateTime.UtcNow; order.Status = newStatus; return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to update order status."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask AddShippingToOrderAsync(long orderId, CreateShipping request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if(!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken)) return Result.Fail("Order not found."); if(!await context.Addresses.AnyAsync(a => a.Id == request.AddressId, cancellationToken)) return Result.Fail("Address not found."); if(!await context.ShippingProviders.AnyAsync(sp => sp.Id == request.ShippingProviderId && sp.Enabled, cancellationToken)) return Result.Fail("Shipping provider not found or disabled."); if(await context.Shippings.AnyAsync(s => s.OrderId == orderId, cancellationToken)) return Result.Fail("Shipping already exists for this order."); var shipping = context.Shippings.Add(new Entities.Shipping { OrderId = orderId, AddressId = request.AddressId, ShippingProviderId = request.ShippingProviderId, Status = ShippingStatuses.Pending, TrackingNumber = request.TrackingNumber }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to add shipping to order."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateShippingStatusAsync(long orderId, ShippingStatuses newStatus, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var shipping = await context.Shippings.FirstOrDefaultAsync(s => s.OrderId == orderId, cancellationToken); if (shipping is null) return Result.Fail("Shipping not found for this order."); shipping.UpdatedAt = DateTime.UtcNow; shipping.Status = newStatus; return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to update shipping status."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async Task> GetShippingByOrderIdAsync(long orderId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var shipping = await context.Shippings .AsNoTracking() .FirstOrDefaultAsync(s => s.OrderId == orderId, cancellationToken); return shipping is not null ? Result.Ok(shipping.ToModel()) : Result.Fail("Shipping not found for this order."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async Task RemoveShippingFromOrderAsync(long orderId, long shippingId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if(!await context.Orders.AnyAsync(o => o.Id == orderId, cancellationToken)) return Result.Fail("Order not found."); var shipping = await context.Shippings.AsNoTracking() .FirstOrDefaultAsync(s => s.OrderId == orderId && s.Id == shippingId, cancellationToken); if (shipping is null) return Result.Fail("Shipping not found for this order."); context.Shippings.Remove(shipping); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to remove shipping from order."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateShippingTrackingNumberAsync(long orderId, long shippingId, string trackingNumber, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var shipping = await context.Shippings.FirstOrDefaultAsync(s => s.OrderId == orderId && s.Id == shippingId, cancellationToken); if (shipping is null) return Result.Fail("Shipping not found for this order."); shipping.UpdatedAt = DateTime.UtcNow; shipping.TrackingNumber = trackingNumber; return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to update shipping tracking number."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask CreateShippingProviderAsync(CreateShippingProvider request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if(await context.ShippingProviders.AnyAsync(sp => sp.Type == request.Type, cancellationToken)) return Result.Fail("Shipping provider with the same type already exists."); var shippingProvider = context.ShippingProviders.Add(new Entities.ShippingProvider { Name = request.Name, Type = request.Type, Price = request.Price, TrackingUrl = request.TrackingUrl }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to create shipping provider."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetShippingProvidersAsync(bool isEnabled, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var providers = await context.ShippingProviders.AsNoTracking().Where(sp => sp.Enabled == isEnabled) .ToListAsync(cancellationToken); return Result.Ok(providers.Select(sp => sp.ToModel()).ToArray()); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetShippingProviderAsync(long providerId, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var provider = await context.ShippingProviders.AsNoTracking() .FirstOrDefaultAsync(sp => sp.Id == providerId, cancellationToken); return provider is not null ? Result.Ok(provider.ToModel()) : Result.Fail("Shipping provider not found."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask UpdateShippingProviderAsync(UpdateShippingProvider request, CancellationToken cancellationToken = default) { try { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var provider = await context.ShippingProviders.FirstOrDefaultAsync(sp => sp.Id == request.ProviderId, cancellationToken); if (provider is null) return Result.Fail("Shipping provider not found."); provider.UpdatedAt = DateTime.UtcNow; provider.Enabled = request.Enabled; provider.Name = request.Name; provider.Price = request.Price; provider.TrackingUrl = request.TrackingUrl; return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to update shipping provider status."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } }