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<OrderService> _logger;
    private readonly ICustomerService _customerService;

    public OrderService(LittleShopContext context, ILogger<OrderService> logger, ICustomerService customerService)
    {
        _context = context;
        _logger = logger;
        _customerService = customerService;
    }

    public async Task<IEnumerable<OrderDto>> GetAllOrdersAsync()
    {
        var orders = await _context.Orders
            .Include(o => o.Customer)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.Product)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.ProductVariation)
            .Include(o => o.Payments)
            .OrderByDescending(o => o.CreatedAt)
            .ToListAsync();

        return orders.Select(MapToDto);
    }

    public async Task<IEnumerable<OrderDto>> GetOrdersByIdentityAsync(string identityReference)
    {
        var orders = await _context.Orders
            .Include(o => o.Customer)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.Product)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.ProductVariation)
            .Include(o => o.Payments)
            .Where(o => o.IdentityReference == identityReference)
            .OrderByDescending(o => o.CreatedAt)
            .ToListAsync();

        return orders.Select(MapToDto);
    }

    public async Task<IEnumerable<OrderDto>> GetOrdersByCustomerIdAsync(Guid customerId)
    {
        var orders = await _context.Orders
            .Include(o => o.Customer)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.Product)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.ProductVariation)
            .Include(o => o.Payments)
            .Where(o => o.CustomerId == customerId)
            .OrderByDescending(o => o.CreatedAt)
            .ToListAsync();

        return orders.Select(MapToDto);
    }

    public async Task<OrderDto?> GetOrderByIdAsync(Guid id)
    {
        var order = await _context.Orders
            .Include(o => o.Customer)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.Product)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.ProductVariation)
            .Include(o => o.Payments)
            .FirstOrDefaultAsync(o => o.Id == id);

        return order == null ? null : MapToDto(order);
    }

    public async Task<OrderDto> 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");
                }

                ProductVariation? variation = null;
                decimal unitPrice = product.Price;

                if (itemDto.ProductVariationId.HasValue)
                {
                    variation = await _context.ProductVariations.FindAsync(itemDto.ProductVariationId.Value);
                    if (variation == null || !variation.IsActive || variation.ProductId != itemDto.ProductId)
                    {
                        throw new ArgumentException($"Product variation {itemDto.ProductVariationId} not found, inactive, or doesn't belong to product {itemDto.ProductId}");
                    }

                    // When using a variation, the quantity represents how many of that variation bundle
                    // For example: buying 2 of the "3 for £25" variation means 6 total items for £50
                    unitPrice = variation.Price;
                }

                var orderItem = new OrderItem
                {
                    Id = Guid.NewGuid(),
                    OrderId = order.Id,
                    ProductId = itemDto.ProductId,
                    ProductVariationId = itemDto.ProductVariationId,
                    Quantity = itemDto.Quantity,
                    UnitPrice = unitPrice,
                    TotalPrice = unitPrice * 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<bool> 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<bool> 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,

            // Workflow timestamps
            AcceptedAt = order.AcceptedAt,
            PackingStartedAt = order.PackingStartedAt,
            DispatchedAt = order.DispatchedAt,
            ExpectedDeliveryDate = order.ExpectedDeliveryDate,
            ActualDeliveryDate = order.ActualDeliveryDate,
            OnHoldAt = order.OnHoldAt,

            // Workflow details
            AcceptedByUser = order.AcceptedByUser,
            PackedByUser = order.PackedByUser,
            DispatchedByUser = order.DispatchedByUser,
            OnHoldReason = order.OnHoldReason,

            // Legacy field (for backward compatibility)
            ShippedAt = order.ShippedAt,
            Items = order.Items.Select(oi => new OrderItemDto
            {
                Id = oi.Id,
                ProductId = oi.ProductId,
                ProductVariationId = oi.ProductVariationId,
                ProductName = oi.Product.Name,
                ProductVariationName = oi.ProductVariation?.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()
        };
    }

    // Enhanced workflow methods
    public async Task<bool> AcceptOrderAsync(Guid id, string userName, AcceptOrderDto acceptDto)
    {
        var order = await _context.Orders.FindAsync(id);
        if (order == null || order.Status != OrderStatus.PaymentReceived)
            return false;

        order.Status = OrderStatus.Accepted;
        order.AcceptedAt = DateTime.UtcNow;
        order.AcceptedByUser = userName;
        if (!string.IsNullOrEmpty(acceptDto.Notes))
            order.Notes = acceptDto.Notes;
        order.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();
        _logger.LogInformation("Order {OrderId} accepted by {User}", id, userName);
        return true;
    }

    public async Task<bool> StartPackingAsync(Guid id, string userName, StartPackingDto packingDto)
    {
        var order = await _context.Orders.FindAsync(id);
        if (order == null || order.Status != OrderStatus.Accepted)
            return false;

        order.Status = OrderStatus.Packing;
        order.PackingStartedAt = DateTime.UtcNow;
        order.PackedByUser = userName;
        if (!string.IsNullOrEmpty(packingDto.Notes))
            order.Notes = packingDto.Notes;
        order.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();
        _logger.LogInformation("Order {OrderId} packing started by {User}", id, userName);
        return true;
    }

    public async Task<bool> DispatchOrderAsync(Guid id, string userName, DispatchOrderDto dispatchDto)
    {
        var order = await _context.Orders.FindAsync(id);
        if (order == null || order.Status != OrderStatus.Packing)
            return false;

        order.Status = OrderStatus.Dispatched;
        order.DispatchedAt = DateTime.UtcNow;
        order.DispatchedByUser = userName;
        order.TrackingNumber = dispatchDto.TrackingNumber;

        // Calculate expected delivery date (working days only)
        var expectedDate = DateTime.UtcNow.AddDays(dispatchDto.EstimatedDeliveryDays);
        while (expectedDate.DayOfWeek == DayOfWeek.Saturday || expectedDate.DayOfWeek == DayOfWeek.Sunday)
        {
            expectedDate = expectedDate.AddDays(1);
        }
        order.ExpectedDeliveryDate = expectedDate;

        if (!string.IsNullOrEmpty(dispatchDto.Notes))
            order.Notes = dispatchDto.Notes;
        order.UpdatedAt = DateTime.UtcNow;

        // Set legacy field for backward compatibility
        order.ShippedAt = order.DispatchedAt;

        await _context.SaveChangesAsync();
        _logger.LogInformation("Order {OrderId} dispatched by {User} with tracking {TrackingNumber}", id, userName, dispatchDto.TrackingNumber);
        return true;
    }

    public async Task<bool> PutOnHoldAsync(Guid id, string userName, PutOnHoldDto holdDto)
    {
        var order = await _context.Orders.FindAsync(id);
        if (order == null || order.Status == OrderStatus.OnHold)
            return false;

        order.Status = OrderStatus.OnHold;
        order.OnHoldAt = DateTime.UtcNow;
        order.OnHoldReason = holdDto.Reason;
        if (!string.IsNullOrEmpty(holdDto.Notes))
            order.Notes = holdDto.Notes;
        order.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();
        _logger.LogInformation("Order {OrderId} put on hold by {User}: {Reason}", id, userName, holdDto.Reason);
        return true;
    }

    public async Task<bool> RemoveFromHoldAsync(Guid id, string userName)
    {
        var order = await _context.Orders.FindAsync(id);
        if (order == null || order.Status != OrderStatus.OnHold)
            return false;

        // Return to appropriate status based on workflow progress
        if (order.AcceptedAt.HasValue && order.PackingStartedAt.HasValue)
            order.Status = OrderStatus.Packing;
        else if (order.AcceptedAt.HasValue)
            order.Status = OrderStatus.Accepted;
        else
            order.Status = OrderStatus.PaymentReceived;

        order.OnHoldAt = null;
        order.OnHoldReason = null;
        order.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();
        _logger.LogInformation("Order {OrderId} removed from hold by {User}, returned to {Status}", id, userName, order.Status);
        return true;
    }

    public async Task<bool> MarkDeliveredAsync(Guid id, MarkDeliveredDto deliveredDto)
    {
        var order = await _context.Orders.FindAsync(id);
        if (order == null || order.Status != OrderStatus.Dispatched)
            return false;

        order.Status = OrderStatus.Delivered;
        order.ActualDeliveryDate = deliveredDto.ActualDeliveryDate ?? DateTime.UtcNow;
        if (!string.IsNullOrEmpty(deliveredDto.Notes))
            order.Notes = deliveredDto.Notes;
        order.UpdatedAt = DateTime.UtcNow;

        await _context.SaveChangesAsync();
        _logger.LogInformation("Order {OrderId} marked as delivered", id);
        return true;
    }

    // Workflow queries
    public async Task<IEnumerable<OrderDto>> GetOrdersByStatusAsync(OrderStatus status)
    {
        var orders = await _context.Orders
            .Include(o => o.Customer)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.Product)
            .Include(o => o.Items)
            .ThenInclude(oi => oi.ProductVariation)
            .Include(o => o.Payments)
            .Where(o => o.Status == status)
            .OrderByDescending(o => o.CreatedAt)
            .ToListAsync();

        return orders.Select(MapToDto);
    }

    public async Task<IEnumerable<OrderDto>> GetOrdersRequiringActionAsync()
    {
        return await GetOrdersByStatusAsync(OrderStatus.PaymentReceived);
    }

    public async Task<IEnumerable<OrderDto>> GetOrdersForPackingAsync()
    {
        return await GetOrdersByStatusAsync(OrderStatus.Accepted);
    }

    public async Task<IEnumerable<OrderDto>> GetOrdersOnHoldAsync()
    {
        return await GetOrdersByStatusAsync(OrderStatus.OnHold);
    }
}