using AyCode.Core.Loggers;
using AyCode.Entities;
using FruitBank.Common.Dtos;
using Mango.Nop.Core.Loggers;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Discounts;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Stores;
using Nop.Data;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Services.Catalog;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Discounts;
using Nop.Services.Localization;

namespace Nop.Plugin.Misc.FruitBankPlugin.Services;

public class CustomPriceCalculationService : PriceCalculationService
{
    private FruitBankDbContext _dbContext;
    private readonly IProductAttributeService _productAttributeService;
    private readonly ISpecificationAttributeService _specificationAttributeService;
    private readonly ILocalizationService _localizationService;
    private readonly IStoreContext _storeContext;
    private ILogger _logger;

    public CustomPriceCalculationService(
        FruitBankDbContext dbContext,
        // inject all base deps
        CatalogSettings catalogSettings,
        CurrencySettings currencySettings,
        ICategoryService categoryService,
        ICurrencyService currencyService,
        IManufacturerService manufacturerService,
        IProductAttributeParser productAttributeParser,
        IProductAttributeService productAttributeService,
        ISpecificationAttributeService specificationAttributeService,
        IProductService productService,
        ICustomerService customerService,
        IDiscountService discountService,
        IDiscountPluginManager discountPluginManager,
        ILocalizationService localizationService,
        IStaticCacheManager cacheManager,
        IWorkContext workContext,
        IEnumerable<IAcLogWriterBase> logWriters,
        IStoreContext storeContext)
        : base(catalogSettings, currencySettings, categoryService, currencyService, customerService, discountService, manufacturerService,
            productAttributeParser, productService,
            cacheManager)
    {
        _logger = new Logger<CustomPriceCalculationService>(logWriters.ToArray());

        _dbContext = dbContext;
        // assign all base deps to local private vars if needed
        _productAttributeService = productAttributeService;
        _specificationAttributeService = specificationAttributeService;
        _localizationService = localizationService;
        _storeContext = storeContext;
    }

    public static decimal CalculateOrderItemFinalPrice(bool isMeasurable, decimal unitPrice, int quantity, double netWeight)
        => decimal.Round(unitPrice * (isMeasurable ? (decimal)netWeight: quantity), 0);

    private static (decimal finalPriceInclTax, decimal finalPriceExclTax) CalculateOrderItemFinalPrices(int quantity, decimal unitPriceInclTax, decimal unitPriceExclTax, bool isMeasurable, double netWeight)
    {
        var finalPriceInclTax = CalculateOrderItemFinalPrice(isMeasurable, unitPriceInclTax, quantity, netWeight);
        var finalPriceExclTax = CalculateOrderItemFinalPrice(isMeasurable, unitPriceExclTax, quantity, netWeight);

        return (finalPriceInclTax, finalPriceExclTax);
    }

    public async Task<bool> CheckAndUpdateOrderItemFinalPricesAsync(OrderItem orderItem, OrderItemDto? orderItemDtoHelper = null)
    {
        orderItemDtoHelper ??= await _dbContext.OrderItemDtos.GetByIdAsync(orderItem.Id, true);
        return await CheckAndUpdateOrderItemFinalPricesAsync(orderItem, orderItemDtoHelper.IsMeasurable, orderItemDtoHelper.NetWeight);
    }

    //public async Task<bool> CheckAndUpdateOrderItemFinalPricesAsync(OrderItemDto orderItemDto)
    //{
    //    var orderItem = await _dbContext.OrderItems.GetByIdAsync(orderItemDto.Id);
    //    return await CheckAndUpdateOrderItemFinalPricesAsync(orderItem, orderItemDto.IsMeasurable, orderItemDto.NetWeight);
    //}
    
    /// <summary>
    /// 
    /// </summary>
    /// <param name="orderItem"></param>
    /// <param name="isMeasurable"></param>
    /// /// <param name="netWeight"></param>
    /// <returns>true if has changes</returns>
    public async Task<bool> CheckAndUpdateOrderItemFinalPricesAsync(OrderItem orderItem, bool isMeasurable, double netWeight)
    {
        _logger.Info($"orderItem.Id: {orderItem.Id}");

        if (orderItem.UnitPriceInclTax == 0 || orderItem.UnitPriceExclTax == 0)
        {
            var orderDto = await _dbContext.OrderDtos.GetByIdAsync(orderItem.OrderId, false);
            var customer = await _dbContext.Customers.GetByIdAsync(orderDto.CustomerId);
            var product = await _dbContext.Products.GetByIdAsync(orderItem.ProductId);
            var pr = await GetFinalPriceAsync(product, customer, _storeContext.GetCurrentStore(), null, 0, true, 1, null, null);
            orderItem.UnitPriceInclTax = pr.finalPrice;
            orderItem.UnitPriceExclTax = pr.finalPrice / (decimal)1.27;
        }

        var finalPrices = CalculateOrderItemFinalPrices(orderItem.Quantity, orderItem.UnitPriceInclTax, orderItem.UnitPriceExclTax, isMeasurable, netWeight);

        if (finalPrices.finalPriceInclTax == orderItem.PriceInclTax && finalPrices.finalPriceExclTax == orderItem.PriceExclTax) return false;

        _logger.Info($"orderItem.PriceInclTax({orderItem.PriceInclTax}) != finalPriceInclTax({finalPrices.finalPriceInclTax}) || " +
                      $"orderItem.PriceExclTax({orderItem.PriceExclTax}) != finalPriceExclTax({finalPrices.finalPriceExclTax})");

        orderItem.PriceInclTax = finalPrices.finalPriceInclTax;
        orderItem.PriceExclTax = finalPrices.finalPriceExclTax;

        await _dbContext.OrderItems.UpdateAsync(orderItem, false);
        return true;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="order"></param>
    /// <returns>true if has changes</returns>
    public async Task<bool> CheckAndUpdateOrderTotalPrice(Order order)
    {
        var prevOrderTotal = order.OrderTotal;

        var orderItemDtosById = await _dbContext.OrderItemDtos.GetAllByOrderId(order.Id).ToDictionaryAsync(k => k.Id, v => v);
        var orderItems = await _dbContext.OrderItems.GetByIdsAsync(orderItemDtosById.Keys.ToList());

        order.OrderTotal = 0;

        foreach (var orderItem in orderItems)
        {
            var orderItemDto = orderItemDtosById[orderItem.Id];

            await CheckAndUpdateOrderItemFinalPricesAsync(orderItem, orderItemDto.IsMeasurable, orderItemDto.NetWeight);
            order.OrderTotal += orderItem.PriceInclTax;
        }

        if (order.OrderTotal == prevOrderTotal) return false;

        _logger.Info($"order.OrderTotal({order.OrderTotal}) == prevOrderTotal({prevOrderTotal})");

        order.OrderSubtotalInclTax = order.OrderTotal;
        order.OrderSubtotalExclTax = (order.OrderTotal / (decimal)1.27);

        //mivel csak csekkolunk, de nem adunk vissza semmilyen kedvezményt, így a subtotal discount értékek kiszámolááshoz meg kell hívni megint a calculate final price-t
        decimal orderSubTotalDiscountInclTax = 0;
        decimal orderSubTotalDiscountExclTax = 0;
        var store = await _storeContext.GetCurrentStoreAsync();

        foreach (var orderItem in orderItems)
        {
            var orderItemDto = orderItemDtosById[orderItem.Id];
            var product = await _dbContext.Products.GetByIdAsync(orderItem.ProductId);
            var customer = await _dbContext.Customers.GetByIdAsync(order.CustomerId);
            var itemPrice = await GetFinalPriceAsync(product, customer, store, 0, true, orderItemDto.Quantity);
            orderSubTotalDiscountInclTax += itemPrice.appliedDiscountAmount;
            orderSubTotalDiscountExclTax += itemPrice.appliedDiscountAmount / (decimal)1.27;
        }


        order.OrderSubTotalDiscountInclTax = orderSubTotalDiscountInclTax;
        order.OrderSubTotalDiscountExclTax = orderSubTotalDiscountExclTax;

        await _dbContext.Orders.UpdateAsync(order, false);
        return true;
    }

    //public override async Task<(decimal priceWithoutDiscounts, decimal finalPrice, decimal appliedDiscountAmount, List<Discount> appliedDiscounts)> GetFinalPriceAsync(
    //    Product product, Customer customer, Store store, decimal? overriddenProductPrice, decimal additionalCharge = 0, bool includeDiscounts = true,
    //    int quantity = 1, DateTime? rentalStartDate = null, DateTime? rentalEndDate = null)
    public override async Task<(decimal priceWithoutDiscounts, decimal finalPrice, decimal appliedDiscountAmount, List<Discount> appliedDiscounts)> GetFinalPriceAsync
    (Product product, Customer customer, Store store, decimal? overriddenProductPrice, decimal additionalCharge, bool includeDiscounts, int quantity,
        DateTime? rentalStartDate, DateTime? rentalEndDate)
    {
        var productDto = await _dbContext.ProductDtos.GetByIdAsync(product.Id, true);


        var finalPrice = await base.GetFinalPriceAsync(product, customer, store, overriddenProductPrice, additionalCharge, includeDiscounts, quantity, rentalStartDate, rentalEndDate);

        if (productDto.IsMeasurable)
        {
            // For measurable products the real price is weight × unit price, determined only after
            // physical weighing. Until then we expose 0 so the cart and checkout total are honest.
            // The actual PriceInclTax / PriceExclTax on OrderItem is set by
            // CheckAndUpdateOrderItemFinalPricesAsync after the order is weighed.
            //return (0m, 0m, 0m, new System.Collections.Generic.List<Nop.Core.Domain.Discounts.Discount>());

            //finalPrice.priceWithoutDiscounts = 0;
            //return (0, finalPrice.finalPrice, finalPrice.appliedDiscountAmount, []);
            return finalPrice;
            //return (overriddenProductPrice.GetValueOrDefault(0), overriddenProductPrice.GetValueOrDefault(0), 0m, []);
        }
        //var productAttributeMappings = await _specificationAttributeService.GetProductSpecificationAttributesAsync(product.Id);
        ////Product Attributes
        //foreach (var pam in productAttributeMappings)
        //{
        //    //get option
        //    var attributeOtion = await _specificationAttributeService.GetSpecificationAttributeOptionByIdAsync(pam.SpecificationAttributeOptionId);

        //    //get attribute
        //    var attribute = await _specificationAttributeService.GetSpecificationAttributeByIdAsync(attributeOtion.SpecificationAttributeId);

        //    // you can check for specific attribute by its name or id
        //    if (attribute.Name == "Needs to be measured for price")
        //    {
        //        return (0m, 0m, 0m, []);
        //    }
        //}

        return finalPrice;
    }
}
