using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using LittleShop.Services; using LittleShop.DTOs; using LittleShop.Enums; namespace LittleShop.Controllers; [ApiController] [Route("api/[controller]")] [Authorize(AuthenticationSchemes = "Bearer")] public class ShippingController : ControllerBase { private readonly IRoyalMailService _royalMailService; private readonly IShippingRateService _shippingRateService; private readonly IOrderService _orderService; private readonly ILogger _logger; public ShippingController( IRoyalMailService royalMailService, IShippingRateService shippingRateService, IOrderService orderService, ILogger logger) { _royalMailService = royalMailService; _shippingRateService = shippingRateService; _orderService = orderService; _logger = logger; } [HttpPost("calculate")] [AllowAnonymous] public async Task> CalculateShipping([FromBody] CalculateShippingRequest request) { try { // First try Royal Mail services var services = await _royalMailService.GetAvailableServicesAsync(request.WeightInGrams, request.Country); if (services.Any()) { var result = new ShippingCalculationResult { Success = true, Services = services.Select(s => new ShippingOption { ServiceCode = s.Code, ServiceName = s.Name, Description = s.Description, Cost = s.Price, EstimatedDeliveryDays = s.EstimatedDeliveryDays, SupportsTracking = s.SupportsTracking }).ToList() }; return Ok(result); } // Fallback to local shipping rates var fallbackRate = await _shippingRateService.CalculateShippingAsync(request.WeightInGrams / 1000, request.Country); if (fallbackRate != null) { var fallbackResult = new ShippingCalculationResult { Success = true, Services = new List { new ShippingOption { ServiceCode = "Standard", ServiceName = fallbackRate.Name, Description = fallbackRate.Description ?? "Standard shipping", Cost = fallbackRate.Price, EstimatedDeliveryDays = (fallbackRate.MinDeliveryDays + fallbackRate.MaxDeliveryDays) / 2, SupportsTracking = false } } }; return Ok(fallbackResult); } return BadRequest(new { Error = "No shipping options available for this destination" }); } catch (Exception ex) { _logger.LogError(ex, "Error calculating shipping for weight {Weight}g to {Country}", request.WeightInGrams, request.Country); return StatusCode(500, new { Error = "Error calculating shipping costs" }); } } [HttpPost("create")] [Authorize(Roles = "Admin")] public async Task> CreateShipment([FromBody] CreateShipmentRequest request) { try { var result = await _royalMailService.CreateShipmentAsync(request); if (result.Success) { _logger.LogInformation("Created shipment {ShipmentId} with tracking {TrackingNumber}", result.ShipmentId, result.TrackingNumber); return Ok(result); } else { return BadRequest(new { Error = result.ErrorMessage }); } } catch (Exception ex) { _logger.LogError(ex, "Error creating shipment"); return StatusCode(500, new { Error = "Error creating shipment" }); } } [HttpPost("orders/{orderId}/ship")] [Authorize(Roles = "Admin")] public async Task> ShipOrder(Guid orderId, [FromBody] ShipOrderRequest request) { try { // Get the order var order = await _orderService.GetOrderByIdAsync(orderId); if (order == null) { return NotFound(new { Error = "Order not found" }); } // Calculate total weight from order items var totalWeight = order.Items.Sum(item => { // Estimate weight if not available (you might want to add weight to ProductDto) return item.Quantity * 200; // Default 200g per item }); // Create shipment request var shipmentRequest = new CreateShipmentRequest { RecipientName = order.ShippingName, AddressLine1 = order.ShippingAddress, City = order.ShippingCity, PostCode = order.ShippingPostCode, Country = order.ShippingCountry, WeightInGrams = totalWeight, Value = order.TotalAmount, ServiceCode = request.ServiceCode ?? "1st Class", Reference = $"Order-{order.Id}" }; var result = await _royalMailService.CreateShipmentAsync(shipmentRequest); if (result.Success && !string.IsNullOrEmpty(result.TrackingNumber)) { // Update order with tracking information var updateRequest = new UpdateOrderStatusDto { Status = Enums.OrderStatus.Shipped, TrackingNumber = result.TrackingNumber }; await _orderService.UpdateOrderStatusAsync(orderId, updateRequest); _logger.LogInformation("Order {OrderId} shipped with tracking {TrackingNumber}", orderId, result.TrackingNumber); } return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error shipping order {OrderId}", orderId); return StatusCode(500, new { Error = "Error creating shipment for order" }); } } [HttpGet("track/{trackingNumber}")] [AllowAnonymous] public async Task> GetTrackingInfo(string trackingNumber) { try { var result = await _royalMailService.GetTrackingInfoAsync(trackingNumber); return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error getting tracking info for {TrackingNumber}", trackingNumber); return StatusCode(500, new { Error = "Error retrieving tracking information" }); } } [HttpGet("services")] [AllowAnonymous] public async Task>> GetAvailableServices( [FromQuery] decimal weight = 500, [FromQuery] string country = "United Kingdom") { try { var services = await _royalMailService.GetAvailableServicesAsync(weight, country); return Ok(services); } catch (Exception ex) { _logger.LogError(ex, "Error getting Royal Mail services"); return StatusCode(500, new { Error = "Error retrieving shipping services" }); } } [HttpGet("labels/{shipmentId}")] [Authorize(Roles = "Admin")] public async Task GetShippingLabel(string shipmentId) { try { var labelData = await _royalMailService.GenerateShippingLabelAsync(shipmentId); if (labelData != null) { return File(labelData, "application/pdf", $"shipping-label-{shipmentId}.pdf"); } return NotFound(new { Error = "Shipping label not found" }); } catch (Exception ex) { _logger.LogError(ex, "Error generating shipping label for {ShipmentId}", shipmentId); return StatusCode(500, new { Error = "Error generating shipping label" }); } } [HttpDelete("{shipmentId}")] [Authorize(Roles = "Admin")] public async Task CancelShipment(string shipmentId) { try { var result = await _royalMailService.CancelShipmentAsync(shipmentId); if (result) { return Ok(new { Message = "Shipment cancelled successfully" }); } return BadRequest(new { Error = "Failed to cancel shipment" }); } catch (Exception ex) { _logger.LogError(ex, "Error cancelling shipment {ShipmentId}", shipmentId); return StatusCode(500, new { Error = "Error cancelling shipment" }); } } } public class CalculateShippingRequest { public decimal WeightInGrams { get; set; } public string Country { get; set; } = "United Kingdom"; } public class ShipOrderRequest { public string? ServiceCode { get; set; } } public class ShippingCalculationResult { public bool Success { get; set; } public List Services { get; set; } = new(); public string? ErrorMessage { get; set; } } public class ShippingOption { public string ServiceCode { get; set; } = string.Empty; public string ServiceName { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public decimal Cost { get; set; } public int EstimatedDeliveryDays { get; set; } public bool SupportsTracking { get; set; } }