using AyCode.Core.Loggers;
using FruitBank.Common.Dtos;
using FruitBank.Common.Interfaces;
using Mango.Nop.Core.Loggers;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Stores;
using Nop.Core.Domain.Tax;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
using Nop.Services.Catalog;
using Nop.Services.Localization;
using Nop.Services.Orders;
using Nop.Services.Tax;

namespace Nop.Plugin.Misc.FruitBankPlugin.Services;

/// <summary>
/// Shared service for creating, adding and removing order items.
/// Extracted from CustomOrderController so the same logic can be reused
/// by PreOrderConversionService without duplication.
/// </summary>
public class FruitBankOrderItemService
{
    private readonly FruitBankDbContext _dbContext;
    private readonly IProductService _productService;
    private readonly IPriceCalculationService _priceCalculationService;
    private readonly ITaxService _taxService;
    private readonly IOrderService _orderService;
    private readonly IStoreContext _storeContext;
    private readonly ILocalizationService _localizationService;
    private readonly ILogger _logger;

    public FruitBankOrderItemService(
        FruitBankDbContext dbContext,
        IProductService productService,
        IPriceCalculationService priceCalculationService,
        ITaxService taxService,
        IOrderService orderService,
        IStoreContext storeContext,
        ILocalizationService localizationService,
        IEnumerable<IAcLogWriterBase> logWriters)
    {
        _dbContext = dbContext;
        _productService = productService;
        _priceCalculationService = priceCalculationService;
        _taxService = taxService;
        _orderService = orderService;
        _storeContext = storeContext;
        _localizationService = localizationService;
        _logger = new Logger<FruitBankOrderItemService>(logWriters.ToArray());
    }

    // ── Create ────────────────────────────────────────────────────────────────

    /// <summary>
    /// Builds an OrderItem entity — no DB writes.
    /// unitPricesIncludeDiscounts=true recalculates price from product;
    /// false uses the supplied price as-is (manual override).
    /// </summary>
    public async Task<OrderItem> CreateOrderItemAsync(
        Product product,
        Order order,
        int productId,
        int quantity,
        decimal price,
        bool isMeasurable,
        bool unitPricesIncludeDiscounts,
        Customer? customer = null,
        Store? store = null)
    {
        store    ??= await _storeContext.GetCurrentStoreAsync();
        customer ??= new Customer { Id = order.CustomerId };

        decimal unitPriceInclTax;
        if (unitPricesIncludeDiscounts)
        {
            var calc = await _priceCalculationService.GetFinalPriceAsync(
                product, customer, store, includeDiscounts: true);
            unitPriceInclTax = calc.finalPrice;
        }
        else
        {
            unitPriceInclTax = price;
        }

        var (unitPriceExclTax, _) = await _taxService.GetProductPriceAsync(
            product, unitPriceInclTax, includingTax: false, customer);

        return new OrderItem
        {
            OrderId               = order.Id,
            ProductId             = productId,
            Quantity              = quantity,
            OrderItemGuid         = Guid.NewGuid(),
            UnitPriceInclTax      = unitPriceInclTax,
            UnitPriceExclTax      = unitPriceExclTax,
            PriceInclTax          = isMeasurable ? 0m : unitPriceInclTax * quantity,
            PriceExclTax          = isMeasurable ? 0m : unitPriceExclTax * quantity,
            OriginalProductCost   = await _priceCalculationService.GetProductCostAsync(product, null),
            AttributeDescription  = string.Empty,
            AttributesXml         = string.Empty,
            DiscountAmountInclTax = decimal.Zero,
            DiscountAmountExclTax = decimal.Zero,
            DownloadCount         = 0,
            IsDownloadActivated   = false,
            LicenseDownloadId     = 0,
            ItemWeight            = product.Weight * quantity
        };
    }

    // ── Add ───────────────────────────────────────────────────────────────────

    /// <summary>
    /// Inserts a single OrderItem, deducts inventory, and updates order totals.
    /// Does NOT check availability — caller is responsible for that guard.
    /// Does NOT call UpdateOrderAsync — caller must do that after all items are added.
    /// </summary>
    public async Task AddOrderItemToOrderAsync(
        Order order,
        OrderItem orderItem,
        string inventoryMessage)
    {
        await _orderService.InsertOrderItemAsync(orderItem);

        var product = await _productService.GetProductByIdAsync(orderItem.ProductId);
        if (product != null)
            await _productService.AdjustInventoryAsync(
                product, -orderItem.Quantity, orderItem.AttributesXml, inventoryMessage);

        order.OrderSubtotalInclTax += orderItem.UnitPriceInclTax * orderItem.Quantity;
        order.OrderSubtotalExclTax += orderItem.UnitPriceExclTax * orderItem.Quantity;
        order.OrderTotal            += orderItem.PriceInclTax;
    }

    /// <summary>
    /// Batch add — mirrors the original CustomOrderController.AddOrderItemsThenUpdateOrder.
    /// Checks availability, creates each OrderItem, adjusts inventory,
    /// updates order totals, then persists the order and writes an order note.
    /// </summary>
    public async Task AddOrderItemsThenUpdateOrderAsync(
        Order order,
        IReadOnlyList<IOrderProductItemBase> items,
        Customer? customer = null,
        Store? store = null,
        Customer? admin = null)
    {
        store    ??= await _storeContext.GetCurrentStoreAsync();
        customer ??= new Customer { Id = order.CustomerId };
        admin    ??= customer;

        var productDtosById = await _dbContext.ProductDtos
            .GetAllByIds(items.Select(x => x.Id).ToArray())
            .ToDictionaryAsync(k => k.Id, v => v);

        foreach (var item in items)
        {
            var product = await _productService.GetProductByIdAsync(item.Id);
            if (product == null)
            {
                _logger.Warning($"AddOrderItemsThenUpdateOrderAsync: product #{item.Id} not found, skipped");
                continue;
            }

            productDtosById.TryGetValue(item.Id, out var dto);
            var isMeasurable = dto?.IsMeasurable ?? false;
            var available    = product.StockQuantity + (dto?.IncomingQuantity ?? 0);

            if (available - item.Quantity < 0)
                throw new Exception(
                    $"Insufficient stock for product #{product.Id} " +
                    $"(available: {available}, requested: {item.Quantity})");

            bool useDiscountPrice = item.Price == product.Price;

            var orderItem = await CreateOrderItemAsync(
                product, order, item.Id, item.Quantity, item.Price,
                isMeasurable, useDiscountPrice, customer, store);

            var msg = string.Format(
                await _localizationService.GetResourceAsync(
                    "Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id);

            await AddOrderItemToOrderAsync(order, orderItem, msg);
        }

        await _orderService.UpdateOrderAsync(order);
        await InsertOrderNoteAsync(order.Id, false,
            $"Products added ({items.Count} item(s)) by " +
            $"{admin.FirstName} {admin.LastName} (Id: {admin.Id})");
    }

    // ── Remove ────────────────────────────────────────────────────────────────

    /// <summary>
    /// Removes an OrderItem: deletes pallets/GAs/weight constraints,
    /// restores inventory, deletes the item, adjusts order totals.
    /// Caller must call UpdateOrderAsync after.
    /// </summary>
    public async Task RemoveOrderItemFromOrderAsync(
        Order order,
        OrderItem orderItem,
        string inventoryMessage)
    {
        await _dbContext.DeleteOrderItemConstraintsAsync(orderItem);

        var product = await _productService.GetProductByIdAsync(orderItem.ProductId);
        if (product != null)
            await _productService.AdjustInventoryAsync(
                product, +orderItem.Quantity, orderItem.AttributesXml, inventoryMessage);

        await _orderService.DeleteOrderItemAsync(orderItem);

        order.OrderSubtotalInclTax -= orderItem.UnitPriceInclTax * orderItem.Quantity;
        order.OrderSubtotalExclTax -= orderItem.UnitPriceExclTax * orderItem.Quantity;
        order.OrderTotal            -= orderItem.PriceInclTax;
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    public Task InsertOrderNoteAsync(int orderId, bool displayToCustomer, string note)
        => _orderService.InsertOrderNoteAsync(new OrderNote
        {
            OrderId           = orderId,
            Note              = note,
            DisplayToCustomer = displayToCustomer,
            CreatedOnUtc      = DateTime.UtcNow
        });
}
