using AyCode.Core.Loggers;
using FruitBank.Common.Entities;
using FruitBank.Common.Interfaces;
using Mango.Nop.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Events;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Events;
using Mango.Nop.Core.Extensions;
using Nop.Core.Domain.Orders;
using Nop.Services.Catalog;

namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.EventConsumers;

public class FruitBankEventConsumer :
    MgEventConsumerBase,
    IConsumer<EntityDeletedEvent<Shipping>>,
    IConsumer<EntityInsertedEvent<ShippingItem>>,
    IConsumer<EntityUpdatedEvent<ShippingItem>>,
    IConsumer<EntityDeletedEvent<ShippingItem>>,
    IConsumer<EntityInsertedEvent<ShippingDocument>>,
    IConsumer<EntityUpdatedEvent<ShippingDocument>>,
    IConsumer<EntityDeletedEvent<ShippingDocument>>,
    IConsumer<EntityInsertedEvent<ShippingItemPallet>>,
    IConsumer<EntityUpdatedEvent<ShippingItemPallet>>,
    IConsumer<EntityDeletedEvent<ShippingItemPallet>>,

    IConsumer<EntityInsertedEvent<OrderItem>>,
    IConsumer<EntityUpdatedEvent<OrderItem>>,
    IConsumer<EntityDeletedEvent<OrderItem>>
{
    //private readonly CustomPriceCalculationService _customPriceCalculationService;

    private readonly FruitBankDbContext _ctx;
    private readonly MeasurementService _measurementService;
    private readonly FruitBankAttributeService _fruitBankAttributeService;
    private readonly PreOrderConversionService _preorderConversionService;
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public FruitBankEventConsumer(IHttpContextAccessor httpContextAcc, FruitBankDbContext ctx, MeasurementService measurementService,
        FruitBankAttributeService fruitBankAttributeService, PreOrderConversionService preorderConversionService,
        IServiceScopeFactory serviceScopeFactory, IEnumerable<IAcLogWriterBase> logWriters) : base(ctx, httpContextAcc, logWriters)
    {
        _ctx = ctx;
        _measurementService = measurementService;
        _fruitBankAttributeService = fruitBankAttributeService;
        _preorderConversionService = preorderConversionService;
        _serviceScopeFactory = serviceScopeFactory;
    }

    public override async Task HandleEventAsync(EntityUpdatedEvent<Product> eventMessage)
    {
        var product = await CheckAndUpdateProductManageInventoryMethodToManageStock(eventMessage.Entity);

        var saveProductCustomAttributesResult = await SaveProductCustomAttributesAsync(product);

        //var isMeasurableProduct = await fruitBankAttributeService.IsMeasurableEntityAsync<Product>(product.Id);

        if (saveProductCustomAttributesResult is { IsMeasurableChanged: true, IsMeasurable: not null })
        {
            var shippingItems = await _ctx.ShippingItems.Table
                .Where(si => si.ProductId == product.Id && !si.IsMeasured && si.IsMeasurable != saveProductCustomAttributesResult.IsMeasurable.Value)
                .ToListAsync();

            if (shippingItems.Count > 0)
            {
                foreach (var shippingItem in shippingItems) shippingItem.IsMeasurable = saveProductCustomAttributesResult.IsMeasurable.Value;
                await _ctx.ShippingItems.UpdateAsync(shippingItems, false);
            }
        }

        await base.HandleEventAsync(eventMessage);
    }

    public override async Task HandleEventAsync(EntityInsertedEvent<Product> eventMessage)
    {
        var product = await CheckAndUpdateProductManageInventoryMethodToManageStock(eventMessage.Entity);

        await SaveProductCustomAttributesAsync(product); //TODO: ez ide miért kell? - J.
        await base.HandleEventAsync(eventMessage);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="product"></param>
    /// <returns>IsMeasureable</returns>
    /// <exception cref="Exception"></exception>
    private async Task<(bool IsMeasurableChanged, bool? IsMeasurable)> SaveProductCustomAttributesAsync(Product product)
    {
        if (product == null) return (false, null);

        var hasForm = HttpContextAccessor.HttpContext?.Request?.HasFormContentType ?? false;
        var form = hasForm ? HttpContextAccessor.HttpContext.Request.Form : null;

        if (form == null || form.Count == 0 ||
            !form.ContainsKey(nameof(IMeasurable.IsMeasurable)) || !form.ContainsKey(nameof(IMeasuringNetWeight.NetWeight)) ||
            !form.ContainsKey(nameof(IIncomingQuantity.IncomingQuantity)) || !form.ContainsKey(nameof(ITare.Tare))) return (false, null);

        bool? isMeasurable = null;
        var isMeasurableChanged = false;

        try
        {
            var productDto = product.Id > 0 ? await _ctx.ProductDtos.GetByIdAsync(product.Id, false) : null;

            //IsMeasurable
            isMeasurable = form[nameof(IMeasurable.IsMeasurable)].ToString().Contains("true");
            var productDtoIsMeasurable = productDto?.GenericAttributes.GetValueOrNull<bool>(nameof(IMeasurable.IsMeasurable));

            if (productDtoIsMeasurable == null || productDtoIsMeasurable.Value != isMeasurable.Value)
            {
                await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, bool>(product.Id, nameof(IMeasurable.IsMeasurable), isMeasurable.Value);
                isMeasurableChanged = true;
            }

            //NetWeight
            var netWeight = double.Round(CommonHelper.To<double>(form[nameof(IMeasuringNetWeight.NetWeight)].ToString()), 1);
            var productDtoNetWeight = productDto?.GenericAttributes.GetValueOrNull<double>(nameof(IMeasuringNetWeight.NetWeight));

            if (productDtoNetWeight == null || double.Round(productDtoNetWeight.Value, 1) != netWeight)
            {
                //await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, double>(product.Id, nameof(IMeasuringNetWeight.NetWeight), netWeight);
                await _ctx.UpdateStockQuantityAndWeightAsync(product, 0, $"Manuális készlet súly változtatás az admin felületen.", netWeight - productDtoNetWeight.GetValueOrDefault(0));
            }

            //Tára
            var tare = double.Round(CommonHelper.To<double>(form[nameof(ITare.Tare)].ToString()), 1);
            if (tare < 0) throw new Exception($"FruitBankEventConsumer->SaveProductCustomAttributesAsync(); (tare < 0); productId: {product.Id}; tare: {tare}");

            if (productDto == null || productDto.Tare != tare)
                await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, double>(product.Id, nameof(ITare.Tare), tare);

            //AverageWeight
            var averageWeight = double.Round(CommonHelper.To<double>(form[nameof(IProductDto.AverageWeight)].ToString()), 1);
            if (averageWeight < 0) throw new Exception($"FruitBankEventConsumer->SaveProductCustomAttributesAsync(); (averageWeight < 0); productId: {product.Id}; averageWeight: {averageWeight}");

            if (productDto == null || productDto.AverageWeight != averageWeight)
                await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, double>(product.Id, nameof(IProductDto.AverageWeight), averageWeight);

            //AverageWeightTreshold
            var averageWeightTreshold = double.Round(CommonHelper.To<double>(form[nameof(IProductDto.AverageWeightTreshold)].ToString()), 1);
            if (averageWeightTreshold < 0) throw new Exception($"FruitBankEventConsumer->SaveProductCustomAttributesAsync(); (averageWeightTreshold < 0); productId: {product.Id}; averageWeight: {averageWeightTreshold}");

            if (productDto == null || productDto.AverageWeightTreshold != averageWeightTreshold)
                await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, double>(product.Id, nameof(IProductDto.AverageWeightTreshold), averageWeightTreshold);

            //IncomingQuantity
            var incomingQuantity = CommonHelper.To<int>(form[nameof(IIncomingQuantity.IncomingQuantity)].ToString());
            if (incomingQuantity < 0) throw new Exception($"FruitBankEventConsumer->SaveProductCustomAttributesAsync(); (incomingQuantity < 0); productId: {product.Id}; incomingQuantity: {incomingQuantity}");

            if (productDto == null || productDto.IncomingQuantity != incomingQuantity)
                await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, int>(product.Id, nameof(IIncomingQuantity.IncomingQuantity), incomingQuantity);
        }
        catch (Exception ex)
        {
            Logger.Error($"FruitBankEventConsumer->SaveProductCustomAttributesAsync; {ex.Message}", ex);
        }

        return (isMeasurableChanged, isMeasurable);
    }

    public async Task HandleEventAsync(EntityInsertedEvent<ShippingItemPallet> eventMessage)
    {
        return;

        Logger.Info($"HandleEventAsync->EntityInsertedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");
        await UpdateShippingItemMeasuringValuesAsync(eventMessage.Entity);
    }

    public async Task HandleEventAsync(EntityUpdatedEvent<ShippingItemPallet> eventMessage)
    {
        return;

        Logger.Info($"HandleEventAsync->EntityUpdatedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");
        await UpdateShippingItemMeasuringValuesAsync(eventMessage.Entity);
    }

    public async Task HandleEventAsync(EntityDeletedEvent<ShippingItemPallet> eventMessage)
    {
        Logger.Info($"HandleEventAsync->EntityDeletedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");

        await UpdateShippingItemMeasuringValuesAsync(eventMessage.Entity);
    }

    private async Task UpdateShippingItemMeasuringValuesAsync(ShippingItemPallet shippingItemPallet)
    {
        var shippingItem = await _ctx.ShippingItems.GetByIdAsync(shippingItemPallet.ShippingItemId, false);
        await _ctx.UpdateShippingItemAsync(shippingItem);
    }

    public async Task HandleEventAsync(EntityInsertedEvent<ShippingItem> eventMessage)
    {
        Logger.Info($"HandleEventAsync->EntityInsertedEvent<ShippingItemPallet>; id: {eventMessage.Entity.Id}");

        await UpdateShippingDocumentIsAllMeasuredAsync(eventMessage.Entity);

        // Trigger preorder conversion if the item has a matched product and a quantity
        var item = eventMessage.Entity;
        if (item.ProductId != null && item.QuantityOnDocument > 0)
        {
            _ = Task.Run(async () =>
            {
                // Suppress the ambient TransactionScope from the parent context —
                // TransactionScope flows through async by default and would cause
                // MSDTC promotion failures if the background task enlists in it.
                using var suppress = new System.Transactions.TransactionScope(
                    System.Transactions.TransactionScopeOption.Suppress,
                    System.Transactions.TransactionScopeAsyncFlowOption.Enabled);
                using var scope = _serviceScopeFactory.CreateScope();
                var conversion = scope.ServiceProvider.GetRequiredService<PreOrderConversionService>();
                try { await conversion.ConvertPreOrdersForProductsAsync(new List<int> { item.ProductId.Value }, item.ShippingDocumentId); }
                catch (Exception ex) { Logger.Error($"[FruitBankEventConsumer] PreOrder conversion failed for ProductId={item.ProductId}: {ex.Message}", ex); }
            });
        }
    }

    #region Update

    public async Task HandleEventAsync(EntityUpdatedEvent<ShippingItem> eventMessage)
    {
        Logger.Info($"HandleEventAsync->EntityUpdatedEvent<ShippingItem>; id: {eventMessage.Entity.Id}");

        var shippingItem = eventMessage.Entity;

        await UpdateShippingDocumentIsAllMeasuredAsync(shippingItem);

        // Trigger preorder conversion when quantity or product assignment changes
        if (shippingItem.ProductId != null && shippingItem.QuantityOnDocument > 0)
        {
            _ = Task.Run(async () =>
            {
                using var suppress = new System.Transactions.TransactionScope(
                    System.Transactions.TransactionScopeOption.Suppress,
                    System.Transactions.TransactionScopeAsyncFlowOption.Enabled);
                using var scope = _serviceScopeFactory.CreateScope();
                var conversion = scope.ServiceProvider.GetRequiredService<PreOrderConversionService>();
                try { await conversion.ConvertPreOrdersForProductsAsync(new List<int> { shippingItem.ProductId.Value }, shippingItem.ShippingDocumentId); }
                catch (Exception ex) { Logger.Error($"[FruitBankEventConsumer] PreOrder conversion failed for ProductId={shippingItem.ProductId}: {ex.Message}", ex); }
            });
        }
    }

    private async Task UpdateShippingDocumentIsAllMeasuredAsync(ShippingItem shippingItem)
    {
        //TODO: where: && IsMeasurable!!!! - J.
        var isAllShippingItemMeasured = _ctx.ShippingItems.GetAll(false).Where(si => si.ShippingDocumentId == shippingItem.ShippingDocumentId).All(si => si.IsMeasured);
        var shippingDocument = await _ctx.ShippingDocuments.GetByIdAsync(shippingItem.ShippingDocumentId);

        if (shippingDocument != null && shippingDocument.IsAllMeasured != isAllShippingItemMeasured)
        {
            shippingDocument.IsAllMeasured = isAllShippingItemMeasured;
            await _ctx.ShippingDocuments.UpdateAsync(shippingDocument);
        }
    }

    public async Task HandleEventAsync(EntityInsertedEvent<ShippingDocument> eventMessage)
    {
        Logger.Info($"HandleEventAsync->EntityInsertedEvent<ShippingDocument>; id: {eventMessage.Entity.Id}");

        await UpdateShippingIsAllMeasuredAsync(eventMessage.Entity);
    }

    public async Task HandleEventAsync(EntityUpdatedEvent<ShippingDocument> eventMessage)
    {
        Logger.Info($"HandleEventAsync->EntityUpdatedEvent<ShippingDocument>; id: {eventMessage.Entity.Id}");

        await UpdateShippingIsAllMeasuredAsync(eventMessage.Entity);
    }

    private async Task UpdateShippingIsAllMeasuredAsync(ShippingDocument shippingDocument)
    {
        var isAllShippingDocumentMeasured = _ctx.ShippingDocuments.GetAll(false).Where(si => si.ShippingId == shippingDocument.ShippingId).All(si => si.IsAllMeasured);
        var shipping = await _ctx.Shippings.GetByIdAsync(shippingDocument.ShippingId);

        if (shipping != null && shipping.IsAllMeasured != isAllShippingDocumentMeasured)
        {
            shipping.IsAllMeasured = isAllShippingDocumentMeasured;
            await _ctx.Shippings.UpdateAsync(shipping);
        }
    }

    #endregion Update

    #region Delete

    public async Task HandleEventAsync(EntityDeletedEvent<Shipping> eventMessage)
    {
        Logger.Info($"HandleEventAsync->EntityDeletedEvent<Shipping>; id: {eventMessage.Entity.Id}");

        await _ctx.ShippingDocuments.DeleteAsync(sd => sd.ShippingId == eventMessage.Entity.Id, true);
    }

    public async Task HandleEventAsync(EntityDeletedEvent<ShippingDocument> eventMessage)
    {
        Logger.Info($"HandleEventAsync->EntityDeletedEvent<ShippingDocument>; id: {eventMessage.Entity.Id}");

        await _ctx.ShippingItems.DeleteAsync(si => si.ShippingDocumentId == eventMessage.Entity.Id, true);
    }

    public async Task HandleEventAsync(EntityDeletedEvent<ShippingItem> eventMessage)
    {
        Logger.Info($"HandleEventAsync->EntityDeletedEvent<ShippingItem>; id: {eventMessage.Entity.Id}");

        await _ctx.ShippingItemPallets.DeleteAsync(sp => sp.ShippingItemId == eventMessage.Entity.Id, false);
    }

    public async Task HandleEventAsync(EntityDeletedEvent<OrderItem> eventMessage)
    {
        await _measurementService.DeleteOrderItemConstraintsAsync(eventMessage.Entity);
    }

    public async Task HandleEventAsync(EntityUpdatedEvent<OrderItem> eventMessage)
    {
        await _measurementService.OrderItemInsertedOrUpdatedPostProcess(eventMessage.Entity);
    }

    public async Task HandleEventAsync(EntityInsertedEvent<OrderItem> eventMessage)
    {
        await _measurementService.OrderItemInsertedOrUpdatedPostProcess(eventMessage.Entity);
    }


}

#endregion Delete
