using Microsoft.EntityFrameworkCore; using LittleShop.Data; using LittleShop.Models; using LittleShop.DTOs; using LittleShop.Enums; namespace LittleShop.Services; public class OrderService : IOrderService { private readonly LittleShopContext _context; private readonly ILogger _logger; private readonly ICustomerService _customerService; public OrderService(LittleShopContext context, ILogger logger, ICustomerService customerService) { _context = context; _logger = logger; _customerService = customerService; } public async Task> GetAllOrdersAsync() { var orders = await _context.Orders .Include(o => o.Customer) .Include(o => o.Items) .ThenInclude(oi => oi.Product) .Include(o => o.Payments) .OrderByDescending(o => o.CreatedAt) .ToListAsync(); return orders.Select(MapToDto); } public async Task> GetOrdersByIdentityAsync(string identityReference) { var orders = await _context.Orders .Include(o => o.Customer) .Include(o => o.Items) .ThenInclude(oi => oi.Product) .Include(o => o.Payments) .Where(o => o.IdentityReference == identityReference) .OrderByDescending(o => o.CreatedAt) .ToListAsync(); return orders.Select(MapToDto); } public async Task> GetOrdersByCustomerIdAsync(Guid customerId) { var orders = await _context.Orders .Include(o => o.Customer) .Include(o => o.Items) .ThenInclude(oi => oi.Product) .Include(o => o.Payments) .Where(o => o.CustomerId == customerId) .OrderByDescending(o => o.CreatedAt) .ToListAsync(); return orders.Select(MapToDto); } public async Task GetOrderByIdAsync(Guid id) { var order = await _context.Orders .Include(o => o.Customer) .Include(o => o.Items) .ThenInclude(oi => oi.Product) .Include(o => o.Payments) .FirstOrDefaultAsync(o => o.Id == id); return order == null ? null : MapToDto(order); } public async Task CreateOrderAsync(CreateOrderDto createOrderDto) { using var transaction = await _context.Database.BeginTransactionAsync(); try { // Handle customer creation/linking during checkout Guid? customerId = null; string? identityReference = null; if (createOrderDto.CustomerInfo != null) { // Create customer during checkout process var customer = await _customerService.GetOrCreateCustomerAsync( createOrderDto.CustomerInfo.TelegramUserId, createOrderDto.CustomerInfo.TelegramDisplayName, createOrderDto.CustomerInfo.TelegramUsername, createOrderDto.CustomerInfo.TelegramFirstName, createOrderDto.CustomerInfo.TelegramLastName); customerId = customer?.Id; } else if (createOrderDto.CustomerId.HasValue) { // Order for existing customer customerId = createOrderDto.CustomerId; } else { // Anonymous order (legacy support) identityReference = createOrderDto.IdentityReference; } var order = new Order { Id = Guid.NewGuid(), CustomerId = customerId, IdentityReference = identityReference, Status = OrderStatus.PendingPayment, TotalAmount = 0, Currency = "GBP", ShippingName = createOrderDto.ShippingName, ShippingAddress = createOrderDto.ShippingAddress, ShippingCity = createOrderDto.ShippingCity, ShippingPostCode = createOrderDto.ShippingPostCode, ShippingCountry = createOrderDto.ShippingCountry, Notes = createOrderDto.Notes, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; _context.Orders.Add(order); decimal totalAmount = 0; foreach (var itemDto in createOrderDto.Items) { var product = await _context.Products.FindAsync(itemDto.ProductId); if (product == null || !product.IsActive) { throw new ArgumentException($"Product {itemDto.ProductId} not found or inactive"); } var orderItem = new OrderItem { Id = Guid.NewGuid(), OrderId = order.Id, ProductId = itemDto.ProductId, Quantity = itemDto.Quantity, UnitPrice = product.Price, TotalPrice = product.Price * itemDto.Quantity }; _context.OrderItems.Add(orderItem); totalAmount += orderItem.TotalPrice; } order.TotalAmount = totalAmount; await _context.SaveChangesAsync(); await transaction.CommitAsync(); if (customerId.HasValue) { _logger.LogInformation("Created order {OrderId} for customer {CustomerId} with total {Total}", order.Id, customerId.Value, totalAmount); } else { _logger.LogInformation("Created order {OrderId} for identity {Identity} with total {Total}", order.Id, identityReference, totalAmount); } // Reload order with includes var createdOrder = await GetOrderByIdAsync(order.Id); return createdOrder!; } catch { await transaction.RollbackAsync(); throw; } } public async Task UpdateOrderStatusAsync(Guid id, UpdateOrderStatusDto updateOrderStatusDto) { var order = await _context.Orders.FindAsync(id); if (order == null) return false; order.Status = updateOrderStatusDto.Status; if (!string.IsNullOrEmpty(updateOrderStatusDto.TrackingNumber)) { order.TrackingNumber = updateOrderStatusDto.TrackingNumber; } if (!string.IsNullOrEmpty(updateOrderStatusDto.Notes)) { order.Notes = updateOrderStatusDto.Notes; } if (updateOrderStatusDto.Status == OrderStatus.Shipped && order.ShippedAt == null) { order.ShippedAt = DateTime.UtcNow; } order.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Updated order {OrderId} status to {Status}", id, updateOrderStatusDto.Status); return true; } public async Task CancelOrderAsync(Guid id, string identityReference) { var order = await _context.Orders.FindAsync(id); if (order == null || order.IdentityReference != identityReference) return false; if (order.Status != OrderStatus.PendingPayment) { return false; // Can only cancel pending orders } order.Status = OrderStatus.Cancelled; order.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Cancelled order {OrderId} by identity {Identity}", id, identityReference); return true; } private static OrderDto MapToDto(Order order) { return new OrderDto { Id = order.Id, CustomerId = order.CustomerId, IdentityReference = order.IdentityReference, Status = order.Status, Customer = order.Customer != null ? new CustomerSummaryDto { Id = order.Customer.Id, DisplayName = !string.IsNullOrEmpty(order.Customer.TelegramDisplayName) ? order.Customer.TelegramDisplayName : !string.IsNullOrEmpty(order.Customer.TelegramUsername) ? $"@{order.Customer.TelegramUsername}" : $"{order.Customer.TelegramFirstName} {order.Customer.TelegramLastName}".Trim(), TelegramUsername = order.Customer.TelegramUsername, TotalOrders = order.Customer.TotalOrders, TotalSpent = order.Customer.TotalSpent, CustomerType = order.Customer.TotalOrders == 0 ? "New" : order.Customer.TotalOrders == 1 ? "First-time" : order.Customer.TotalOrders < 5 ? "Regular" : order.Customer.TotalOrders < 20 ? "Loyal" : "VIP", RiskScore = order.Customer.RiskScore, LastActiveAt = order.Customer.LastActiveAt, IsBlocked = order.Customer.IsBlocked } : null, TotalAmount = order.TotalAmount, Currency = order.Currency, ShippingName = order.ShippingName, ShippingAddress = order.ShippingAddress, ShippingCity = order.ShippingCity, ShippingPostCode = order.ShippingPostCode, ShippingCountry = order.ShippingCountry, Notes = order.Notes, TrackingNumber = order.TrackingNumber, CreatedAt = order.CreatedAt, UpdatedAt = order.UpdatedAt, PaidAt = order.PaidAt, ShippedAt = order.ShippedAt, Items = order.Items.Select(oi => new OrderItemDto { Id = oi.Id, ProductId = oi.ProductId, ProductName = oi.Product.Name, Quantity = oi.Quantity, UnitPrice = oi.UnitPrice, TotalPrice = oi.TotalPrice }).ToList(), Payments = order.Payments.Select(cp => new CryptoPaymentDto { Id = cp.Id, OrderId = cp.OrderId, Currency = cp.Currency, WalletAddress = cp.WalletAddress, RequiredAmount = cp.RequiredAmount, PaidAmount = cp.PaidAmount, Status = cp.Status, BTCPayInvoiceId = cp.BTCPayInvoiceId, TransactionHash = cp.TransactionHash, CreatedAt = cp.CreatedAt, PaidAt = cp.PaidAt, ExpiresAt = cp.ExpiresAt }).ToList() }; } }