using System.Text.Json;
using Meezi.API.Models.Orders;
using Meezi.API.Services.Printing;
using Meezi.Core.Delivery;
using Meezi.Core.Entities;
using Meezi.Core.Enums;
using Meezi.Core.Interfaces;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace Meezi.API.Services.Delivery;

public record DeliveryProcessResult(bool Success, string? MeeziOrderId, string? ErrorCode, string? Message);

public interface IDeliveryOrderProcessor
{
    Task<DeliveryProcessResult> ProcessAsync(
        string webhookLogId,
        UnifiedDeliveryOrder unified,
        CancellationToken ct = default);
}

public class DeliveryOrderProcessor : IDeliveryOrderProcessor
{
    private readonly AppDbContext _db;
    private readonly IKdsNotifier _kds;
    private readonly ICommissionCalculator _commission;
    private readonly IInventoryService _inventory;
    private readonly ISnappfoodClient _snappfood;
    private readonly ITap30Client _tap30;
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<DeliveryOrderProcessor> _logger;

    public DeliveryOrderProcessor(
        AppDbContext db,
        IKdsNotifier kds,
        ICommissionCalculator commission,
        IInventoryService inventory,
        ISnappfoodClient snappfood,
        ITap30Client tap30,
        IServiceScopeFactory scopeFactory,
        ILogger<DeliveryOrderProcessor> logger)
    {
        _db = db;
        _kds = kds;
        _commission = commission;
        _inventory = inventory;
        _snappfood = snappfood;
        _tap30 = tap30;
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    public async Task<DeliveryProcessResult> ProcessAsync(
        string webhookLogId,
        UnifiedDeliveryOrder unified,
        CancellationToken ct = default)
    {
        var log = await _db.WebhookLogs.FirstOrDefaultAsync(w => w.Id == webhookLogId, ct);
        if (log is null)
            return new DeliveryProcessResult(false, null, "LOG_NOT_FOUND", "Webhook log missing.");

        log.AttemptCount++;
        try
        {
            var cafe = await ResolveCafeAsync(unified, ct);
            if (cafe is null)
            {
                await FailLogAsync(log, "Unknown vendor.", ct);
                return new DeliveryProcessResult(false, null, "VENDOR_NOT_FOUND", "Unknown vendor.");
            }

            log.CafeId = cafe.Id;
            log.ExternalOrderId = unified.ExternalId;

            var duplicate = await _db.Orders.AnyAsync(
                o => o.CafeId == cafe.Id
                    && o.DeliveryPlatform == unified.Platform
                    && o.ExternalOrderId == unified.ExternalId,
                ct);

            if (duplicate)
            {
                await CompleteLogAsync(log, null, success: true, error: null, ct);
                return new DeliveryProcessResult(true, null, null, "Duplicate ignored.");
            }

            var branchId = await _db.Branches
                .Where(b => b.CafeId == cafe.Id && b.IsActive)
                .OrderBy(b => b.Name)
                .Select(b => b.Id)
                .FirstOrDefaultAsync(ct);

            var menuItems = await _db.MenuItems
                .Where(m => m.CafeId == cafe.Id && m.IsAvailable)
                .ToListAsync(ct);

            var orderItems = new List<OrderItem>();
            decimal subtotal = 0;

            foreach (var line in unified.Items)
            {
                var menuItem = menuItems.FirstOrDefault(m =>
                    (!string.IsNullOrEmpty(line.Sku) && m.Id == line.Sku)
                    || m.Name.Equals(line.Name, StringComparison.OrdinalIgnoreCase)
                    || (m.NameEn != null && m.NameEn.Equals(line.Name, StringComparison.OrdinalIgnoreCase)));

                if (menuItem is null)
                {
                    _logger.LogWarning(
                        "Delivery {Platform} item {Name} not matched for cafe {CafeId}",
                        unified.Platform,
                        line.Name,
                        cafe.Id);
                    continue;
                }

                subtotal += line.UnitPrice * line.Quantity;
                orderItems.Add(new OrderItem
                {
                    MenuItemId = menuItem.Id,
                    Quantity = line.Quantity,
                    UnitPrice = line.UnitPrice,
                    Notes = line.Notes ?? unified.Platform.ToString()
                });
            }

            if (orderItems.Count == 0)
            {
                await FailLogAsync(log, "No menu items matched.", ct);
                return new DeliveryProcessResult(false, null, "INVALID_MENU_ITEMS", "No menu items matched.");
            }

            var platformCommission = await _commission.CalculateForOrderAsync(cafe.Id, unified, ct);

            var taxRate = cafe.DefaultTaxRate > 0
                ? cafe.DefaultTaxRate
                : await _db.Taxes.Where(t => t.CafeId == cafe.Id && t.IsDefault)
                    .Select(t => t.Rate)
                    .FirstOrDefaultAsync(ct);

            if (taxRate == 0) taxRate = 9m;

            var gross = unified.Payment.Total > 0 ? unified.Payment.Total : subtotal;
            var taxTotal = Math.Round((gross - platformCommission) * taxRate / 100m, 0);
            var total = gross;

            var displayNumber = await AllocateDisplayNumberAsync(cafe.Id, ct);
            var order = new Order
            {
                CafeId = cafe.Id,
                BranchId = branchId,
                OrderType = unified.Delivery.Type == "pickup" ? OrderType.Takeaway : OrderType.Delivery,
                Source = MapSource(unified.Platform),
                Status = MapStatus(unified.Status),
                DisplayNumber = displayNumber,
                ExternalOrderId = unified.ExternalId,
                DeliveryPlatform = unified.Platform,
                PlatformCommission = platformCommission,
                DeliveryMetaJson = JsonSerializer.Serialize(unified.Delivery),
                Subtotal = subtotal,
                TaxTotal = taxTotal,
                Total = total,
                Items = orderItems
            };

            if (unified.Platform == DeliveryPlatform.Snappfood)
                order.SnappfoodOrderId = unified.ExternalId;

            if (!string.IsNullOrWhiteSpace(unified.Customer.Phone))
            {
                var phone = unified.Customer.Phone.Trim();
                var customer = await _db.Customers
                    .FirstOrDefaultAsync(c => c.CafeId == cafe.Id && c.Phone == phone, ct);
                if (customer is null)
                {
                    customer = new Customer
                    {
                        CafeId = cafe.Id,
                        Name = unified.Customer.Name,
                        Phone = phone,
                        Group = CustomerGroup.New
                    };
                    _db.Customers.Add(customer);
                    await _db.SaveChangesAsync(ct);
                }

                order.CustomerId = customer.Id;
                order.GuestName = unified.Customer.Name;
                order.GuestPhone = phone;
            }
            else
            {
                order.GuestName = unified.Customer.Name;
            }

            if (unified.Payment.IsPaid && total > 0)
            {
                order.Payments.Add(new Payment
                {
                    Method = unified.Payment.Method.Equals("cash", StringComparison.OrdinalIgnoreCase)
                        ? PaymentMethod.Cash
                        : PaymentMethod.Card,
                    Amount = total,
                    Status = PaymentStatus.Completed
                });
            }

            _db.Orders.Add(order);
            await _db.SaveChangesAsync(ct);

            await TryDeductInventoryAsync(cafe.Id, orderItems, ct);

            var loaded = await _db.Orders
                .Include(o => o.Items)
                .ThenInclude(i => i.MenuItem)
                .Include(o => o.Table)
                .FirstAsync(o => o.Id == order.Id, ct);

            await _kds.NotifyOrderCreatedAsync(cafe.Id, MapLive(loaded), ct);
            PrinterBackgroundJobs.QueueKitchenPrint(_scopeFactory, cafe.Id, order.Id);

            await AcknowledgePlatformAsync(unified, ct);

            await CompleteLogAsync(log, order.Id, success: true, error: null, ct);
            return new DeliveryProcessResult(true, order.Id, null, null);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Delivery order processing failed for log {LogId}", webhookLogId);
            await FailLogAsync(log, ex.Message, ct);
            throw;
        }
    }

    private async Task<Cafe?> ResolveCafeAsync(UnifiedDeliveryOrder unified, CancellationToken ct) =>
        unified.Platform switch
        {
            DeliveryPlatform.Snappfood => await _db.Cafes
                .FirstOrDefaultAsync(c => c.SnappfoodVendorId == unified.VendorId, ct),
            DeliveryPlatform.Tap30 => await _db.Cafes
                .FirstOrDefaultAsync(c => c.Tap30VendorId == unified.VendorId, ct),
            DeliveryPlatform.Digikala => await _db.Cafes
                .FirstOrDefaultAsync(c => c.DigikalaVendorId == unified.VendorId, ct),
            _ => null
        };

    private async Task AcknowledgePlatformAsync(UnifiedDeliveryOrder unified, CancellationToken ct)
    {
        switch (unified.Platform)
        {
            case DeliveryPlatform.Snappfood:
                await _snappfood.AcknowledgeOrderAsync(unified.ExternalId, ct);
                break;
            case DeliveryPlatform.Tap30:
                await _tap30.AcknowledgeOrderAsync(unified.ExternalId, ct);
                break;
        }
    }

    private async Task TryDeductInventoryAsync(
        string cafeId,
        List<OrderItem> items,
        CancellationToken ct)
    {
        if (items.Count == 0) return;
        var orderId = items[0].OrderId;
        await _inventory.DeductForOrderAsync(
            cafeId,
            orderId,
            items.Select(i => (i.MenuItemId, i.Quantity)).ToList(),
            ct);
    }

    private static OrderSource MapSource(DeliveryPlatform platform) => platform switch
    {
        DeliveryPlatform.Snappfood => OrderSource.SnappFood,
        DeliveryPlatform.Tap30 => OrderSource.Tap30,
        DeliveryPlatform.Digikala => OrderSource.Digikala,
        _ => OrderSource.Pos
    };

    private static OrderStatus MapStatus(UnifiedDeliveryStatus status) => status switch
    {
        UnifiedDeliveryStatus.Pending => OrderStatus.Pending,
        UnifiedDeliveryStatus.Confirmed => OrderStatus.Confirmed,
        UnifiedDeliveryStatus.Preparing => OrderStatus.Preparing,
        UnifiedDeliveryStatus.Ready => OrderStatus.Ready,
        UnifiedDeliveryStatus.Delivered => OrderStatus.Delivered,
        UnifiedDeliveryStatus.Cancelled => OrderStatus.Cancelled,
        _ => OrderStatus.Confirmed
    };

    private async Task FailLogAsync(WebhookLog log, string error, CancellationToken ct)
    {
        log.Success = false;
        log.Processed = true;
        log.ErrorMessage = error.Length > 2000 ? error[..2000] : error;
        log.ProcessedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(ct);

        if (log.AttemptCount >= 3)
            _logger.LogError(
                "Delivery webhook dead-letter: platform {Platform} external {ExternalId} — {Error}",
                log.Platform,
                log.ExternalOrderId,
                error);
    }

    private async Task CompleteLogAsync(
        WebhookLog log,
        string? meeziOrderId,
        bool success,
        string? error,
        CancellationToken ct)
    {
        log.Success = success;
        log.Processed = true;
        log.MeeziOrderId = meeziOrderId;
        log.ErrorMessage = error;
        log.ProcessedAt = DateTime.UtcNow;
        await _db.SaveChangesAsync(ct);
    }


    private async Task<int> AllocateDisplayNumberAsync(string cafeId, CancellationToken ct)
    {
        var max = await _db.Orders
            .Where(o => o.CafeId == cafeId)
            .MaxAsync(o => (int?)o.DisplayNumber, ct);
        return (max ?? 0) + 1;
    }    private static LiveOrderDto MapLive(Order o) => new(
        o.Id,
        o.DisplayNumber > 0 ? o.DisplayNumber : ReceiptPrintFormatting.StableDisplayNumberFromId(o.Id),
        o.Status,
        o.Table?.Number,
        o.OrderType,
        o.Total,
        o.CreatedAt,
        o.Items.Select(i => new OrderItemDto(
            i.Id,
            i.MenuItemId,
            i.MenuItem?.Name ?? "",
            i.Quantity,
            i.UnitPrice,
            i.Notes,
            i.IsVoided,
            i.VoidedAt)).ToList());
}
