using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Services.Server.SignalRs;
using FruitBank.Common.Dtos;
using FruitBank.Common.Entities;
using FruitBank.Common.Interfaces;
using FruitBank.Common.Server.Services.SignalRs;
using FruitBank.Common.Services;
using Mango.Nop.Core.Extensions;
using Mango.Nop.Core.Loggers;
using Microsoft.CodeAnalysis.Operations;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Orders;
using Nop.Core.Events;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Services.Catalog;
using Nop.Services.Events;

namespace Nop.Plugin.Misc.FruitBankPlugin.Services;

#nullable enable
public class MeasurementService : MeasurementServiceBase<Logger>, IMeasurementService
{
    private readonly FruitBankDbContext _dbContext;
    private readonly IEventPublisher _eventPublisher;
    private readonly FruitBankAttributeService _fruitBankAttributeService;
    private readonly SignalRSendToClientService _signalRSendToClientService;
    private readonly CustomPriceCalculationService _customPriceCalculationService;   

    public MeasurementService(FruitBankDbContext dbContext, SignalRSendToClientService signalRSendToClientService, FruitBankAttributeService fruitBankAttributeService,
        IPriceCalculationService customPriceCalculationService, IEventPublisher eventPublisher, IEnumerable<IAcLogWriterBase> logWriters) : base(new Logger<MeasurementService>(logWriters.ToArray()))
    {
        _dbContext = dbContext;
        _eventPublisher = eventPublisher;
        _fruitBankAttributeService = fruitBankAttributeService;
        _signalRSendToClientService = signalRSendToClientService;
        _customPriceCalculationService = (CustomPriceCalculationService)customPriceCalculationService;
    }

    public async Task DeleteOrderItemConstraintsAsync(int orderItemId) => await DeleteOrderItemConstraintsAsync(await _dbContext.OrderItems.GetByIdAsync(orderItemId));

    public async Task DeleteOrderItemConstraintsAsync(OrderItem orderItem)
    {
        Logger.Info($"MeasurementService->DeleteOrderItemConstraintsAsync() invoked; orderItem.Id: {orderItem?.Id}");

        if (orderItem == null) return;

        await _dbContext.DeleteOrderItemConstraintsSafeAsync(orderItem);
        await _signalRSendToClientService.SendOrderItemDeleted(orderItem);

        var order = await _dbContext.Orders.GetByIdAsync(orderItem.OrderId);
        await _customPriceCalculationService.CheckAndUpdateOrderTotalPrice(order);
    }

    public async Task OrderItemInsertedOrUpdatedPostProcess(OrderItem orderItem)
    {
        var orderItemDto = await _dbContext.OrderItemDtos.GetByIdAsync(orderItem.Id, true);

        await CheckAndUpdateOrderItemFinalPricesAsync(orderItem, orderItemDto);
        await _signalRSendToClientService.SendOrderItemChanged(orderItemDto);
    }

    public async Task CheckAndUpdateOrderItemFinalPricesAsync(OrderItem orderItem, OrderItemDto? orderItemDtoHelper = null)
    {
        if (await _customPriceCalculationService.CheckAndUpdateOrderItemFinalPricesAsync(orderItem, orderItemDtoHelper))
        {
            var order = await _dbContext.Orders.GetByIdAsync(orderItem.OrderId);
            await _customPriceCalculationService.CheckAndUpdateOrderTotalPrice(order);
        }
    }

    public async Task<bool> OrderItemMeasuringReset(int orderItemId)
        => await OrderItemMeasuringReset(await _dbContext.OrderItems.GetByIdAsync(orderItemId));

    public async Task<bool> OrderItemMeasuringReset(OrderItem orderItem)
    {
        var order = await _dbContext.Orders.GetByIdAsync(orderItem.OrderId);
        var orderItemPallets = await _dbContext.OrderItemPallets.GetAllByOrderItemId(orderItem.Id, false).ToListAsync();

        var result = await _dbContext.TransactionSafeAsync(async _ =>
        {
            await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, int>(order.Id, nameof(IOrderDto.RevisorId), 0);

            foreach (var orderItemPallet in orderItemPallets)
            {
                orderItemPallet.RevisorId = 0;
                orderItemPallet.IsMeasured = false;

                await _dbContext.OrderItemPallets.UpdateAsync(orderItemPallet);
            }

            if (order.OrderStatus == OrderStatus.Complete)
                await _dbContext.SetOrderStatusToPendingAsync(order);

            return true;
        });

        if (!result) return result;

        foreach (var orderItemPallet in orderItemPallets)
            await _signalRSendToClientService.SendOrderItemPalletChanged(orderItemPallet);

        await _signalRSendToClientService.SendOrderChanged(await _dbContext.OrderDtos.GetByIdAsync(orderItem.OrderId, true));
        return result;
    }

    public async Task<List<Partner>?> ProcessAndSaveFullShippingJson(string fullShippingJson, int customerId)
    {
        var partners = fullShippingJson.JsonTo<List<Partner>>();
        if (partners == null || partners.Count == 0) return partners;

        var a = partners.SelectMany(x => x.ShippingDocuments?.SelectMany(sd => sd.ShippingItems?.Where(si => si.ProductId.GetValueOrDefault(0) > 0).Select(si => si.ProductId!.Value) ?? []) ?? []).ToHashSet();
        var productDtosById = await _dbContext.ProductDtos.GetAllByIds(a, false, false).ToDictionaryAsync(k => k.Id, v => v);

        var result = await _dbContext.TransactionSafeAsync(async _ =>
        {
            foreach (var partner in partners)
            {
                //await _dbContext.Partners.InsertAsync(partner, false);
                if (partner.ShippingDocuments == null) continue;

                foreach (var shippingDocument in partner.ShippingDocuments)
                {
                    //shippingDocument.PartnerId = 0;
                    await _dbContext.ShippingDocuments.InsertAsync(shippingDocument, false);

                    if (shippingDocument.ShippingItems == null) continue;

                    foreach (var shippingItem in shippingDocument.ShippingItems)
                    {
                        shippingItem.NameOnDocument = shippingItem.Name;
                        if (shippingItem.ProductId != null && productDtosById.TryGetValue(shippingItem.ProductId.Value, out var productDto))
                        {
                            shippingItem.Name = productDto.Name;
                            shippingItem.IsMeasurable = productDto.IsMeasurable;

                            //TODO: Update Product Incoming attribute! - J.
                        }
                        else shippingItem.ProductId = null;

                        shippingItem.ShippingDocumentId = shippingDocument.Id;
                        await _dbContext.ShippingItems.InsertAsync(shippingItem, false);
                    }
                }
            }

            return true;
        });

        return result ? partners : null;
    }

    public async Task CalculateAndSetAverageWeight(ShippingItemPallet shippingItemPallet)
    {
        var productDto = await _dbContext.ProductDtos.GetByIdAsync(shippingItemPallet.ShippingItem.ProductId);
        if (productDto != null)
        {
            double averageWeight = 0;


            //TODO if ismeasurable
            if (shippingItemPallet.IsValidMeasuringValues(shippingItemPallet.ShippingItem.IsMeasurable))
            {
                //this is only for the current pallet, the average weight should be calculated from all pallets of the product,
                //so we check if there are any pallets with valid measuring values and calculate the average weight from them,
                //otherwise we use the current pallet's weight as the average weight
                var shippingItemPallets = await _dbContext.ShippingItemPallets.GetAllByShippingItemIdAsync(shippingItemPallet.ShippingItemId, false).ToListAsync();
                var allOtherPalletsInThisShippingItem = await shippingItemPallets.Where(sp => sp.Id != shippingItemPallet.Id).ToListAsync();

                //we need an object to store the netweight and trayquantity for average calculation
                var validPalletsForAverageCalculation = new List<(double NetWeight, int TrayQuantity)>();

                if(allOtherPalletsInThisShippingItem.Count != 0)
                {
                    //get all pallets with valid measuring values
                    for (int i = 0; i < allOtherPalletsInThisShippingItem.Count; i++)
                    {
                        if (allOtherPalletsInThisShippingItem[i].IsValidMeasuringValues(shippingItemPallet.ShippingItem.IsMeasurable))
                        {
                            validPalletsForAverageCalculation.Add((allOtherPalletsInThisShippingItem[i].NetWeight, allOtherPalletsInThisShippingItem[i].TrayQuantity));
                        }
                    }
                }
                

                //add current pallet to the valid pallets if it has valid measuring values

                validPalletsForAverageCalculation.Add(new(shippingItemPallet.NetWeight, shippingItemPallet.TrayQuantity));

                //calculate the average weight from the valid pallets
                var totalNetWeight = validPalletsForAverageCalculation.Sum(x => x.NetWeight);
                var totalTrayQuantity = validPalletsForAverageCalculation.Sum(x => x.TrayQuantity);

                var TotalAverageWeight = totalTrayQuantity > 0 ? totalNetWeight / totalTrayQuantity : 0;
                averageWeight = TotalAverageWeight;
            }

            //TODO ELSE - we have to get the net weight from the document, not from measuring 


            await SetProductAverageWeight(productDto, averageWeight);
            await _dbContext.ProductDtos.UpdateAsync(productDto);
        }

    }

    public async Task SetProductAverageWeight(ProductDto productDto, double averageWeight)
    {
        await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, double>(productDto.Id, nameof(IProductDto.AverageWeight), averageWeight);
    }
}