using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Services.Server.SignalRs;
using AyCode.Services.SignalRs;
using AyCode.Utils.Extensions;
using DocumentFormat.OpenXml.Spreadsheet;
using FluentMigrator.Runner.Generators.Base;
using FruitBank.Common.Dtos;
using FruitBank.Common.Entities;
using FruitBank.Common.Enums;
using FruitBank.Common.Interfaces;
using FruitBank.Common.Server;
using FruitBank.Common.Server.Interfaces;
using FruitBank.Common.Server.Services.SignalRs;
using FruitBank.Common.SignalRs;
using Mango.Nop.Core.Extensions;
using Mango.Nop.Core.Loggers;
using MessagePack.Resolvers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Newtonsoft.Json;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Stores;
using Nop.Core.Domain.Tax;
using Nop.Core.Events;
using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.Order;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Factories;
using Nop.Plugin.Misc.FruitBankPlugin.Models.Orders;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.ExportImport;
using Nop.Services.Helpers;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Messages;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Plugins;
using Nop.Services.Security;
using Nop.Services.Tax;
using Nop.Web.Areas.Admin.Controllers;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Areas.Admin.Models.Orders;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Mvc.Filters;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using static Nop.Services.Security.StandardPermission;

namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
{
    [Area(AreaNames.ADMIN)]
    [AuthorizeAdmin]
    public class CustomOrderController : BaseAdminController, ICustomOrderSignalREndpointServer
    {
        private readonly FruitBankDbContext _dbContext;
        private readonly SignalRSendToClientService _sendToClient;

        private readonly IOrderService _orderService;
        private readonly CustomOrderModelFactory _orderModelFactory;
        private readonly ICustomOrderSignalREndpointServer _customOrderSignalREndpoint;
        private readonly IPermissionService _permissionService;
        //private readonly IGenericAttributeService _genericAttributeService;
        private readonly FruitBankAttributeService _fruitBankAttributeService;
        private readonly INotificationService _notificationService;
        private readonly ICustomerService _customerService;
        private readonly IProductService _productService;
        private readonly IStoreContext _storeContext;
        private readonly IWorkContext _workContext;
        private readonly IPriceCalculationService _priceCalculationService;
        protected readonly IEventPublisher _eventPublisher;
        protected readonly ILocalizationService _localizationService;
        protected readonly ICustomerActivityService _customerActivityService;
        protected readonly IExportManager _exportManager;
        protected readonly IGiftCardService _giftCardService;
        protected readonly IImportManager _importManager;
        protected readonly IDateTimeHelper _dateTimeHelper;
        protected readonly ITaxService _taxService;
        protected readonly MeasurementService _measurementService;
        protected readonly IWorkflowMessageService _workflowMessageService;
        protected readonly FruitBankNotificationService _fruitBankNotificationService;
        protected readonly IAddressService _addressService;
        private readonly FruitBankOrderItemService _orderItemService;
        private static readonly char[] _separator = [','];
        // ... other dependencies

        private readonly Mango.Nop.Core.Loggers.ILogger _logger;

        protected virtual async ValueTask<bool> HasAccessToOrderAsync(Order order)
        {
            return order != null && await HasAccessToOrderAsync(order.Id);
        }

        protected virtual async Task<bool> HasAccessToOrderAsync(int orderId)
        {
            if (orderId == 0)
                return false;

            var currentVendor = await _workContext.GetCurrentVendorAsync();
            if (currentVendor == null)
                //not a vendor; has access
                return true;

            var vendorId = currentVendor.Id;
            var hasVendorProducts = (await _orderService.GetOrderItemsAsync(orderId, vendorId: vendorId)).Any();

            return hasVendorProducts;
        }

        public CustomOrderController(FruitBankDbContext fruitBankDbContext, SignalRSendToClientService sendToClient,
            IOrderService orderService,
            IPriceCalculationService priceCalculationService,
            IOrderModelFactory orderModelFactory,
            ICustomOrderSignalREndpointServer customOrderSignalREndpoint,
            IPermissionService permissionService,
            //IGenericAttributeService genericAttributeService,
            FruitBankAttributeService fruitBankAttributeService,
            INotificationService notificationService,
            ICustomerService customerService,
            IProductService productService,
            IEnumerable<IAcLogWriterBase> logWriters,
            IStoreContext storeContext,
            IWorkContext workContext,
            IEventPublisher eventPublisher,
            ILocalizationService localizationService,
            ICustomerActivityService customerActivityService,
            IExportManager exportManager,
            IGiftCardService giftCardService,
            IImportManager importManager,
            IDateTimeHelper dateTimeHelper,
            ITaxService taxService,
            MeasurementService measurementService,
            IWorkflowMessageService workflowMessageService, 
            FruitBankNotificationService fruitBankNotificationService,
            IAddressService addressService,
            FruitBankOrderItemService orderItemService)
        {
            _logger = new Logger<CustomOrderController>(logWriters.ToArray());

            _dbContext = fruitBankDbContext;
            _sendToClient = sendToClient;

            _orderService = orderService;
            _orderModelFactory = orderModelFactory as CustomOrderModelFactory;
            _customOrderSignalREndpoint = customOrderSignalREndpoint;
            _permissionService = permissionService;
            //_genericAttributeService = genericAttributeService;
            _fruitBankAttributeService = fruitBankAttributeService;
            _notificationService = notificationService;
            _customerService = customerService;
            _productService = productService;
            _storeContext = storeContext;
            _workContext = workContext;
            _priceCalculationService = priceCalculationService;
            _eventPublisher = eventPublisher;
            _localizationService = localizationService;
            _customerActivityService = customerActivityService;

            _exportManager = exportManager;
            _giftCardService = giftCardService;
            _importManager = importManager;
            _dateTimeHelper = dateTimeHelper;
            _taxService = taxService;
            _measurementService = measurementService;
            _workflowMessageService = workflowMessageService;
            _fruitBankNotificationService = fruitBankNotificationService;
            _addressService = addressService;
            _orderItemService = orderItemService;

            // ... initialize other deps

        }

        #region CustomOrderSignalREndpoint

        [NonAction]
        public Task<List<OrderDto>> GetAllOrderDtos() => _customOrderSignalREndpoint.GetAllOrderDtos();

        [NonAction]
        public Task<OrderDto> GetOrderDtoById(int orderId) => _customOrderSignalREndpoint.GetOrderDtoById(orderId);

        [NonAction]
        public Task<List<OrderDto>> GetPendingOrderDtos() => _customOrderSignalREndpoint.GetPendingOrderDtos();

        [NonAction]
        public Task<List<OrderDto>> GetPendingOrderDtosForMeasuring(int lastDaysCount) => _customOrderSignalREndpoint.GetPendingOrderDtosForMeasuring(lastDaysCount);

        [NonAction]
        public Task<OrderDto> StartMeasuring(int orderId, int userId) => _customOrderSignalREndpoint.StartMeasuring(orderId, userId);

        [NonAction]
        public Task<OrderDto> SetOrderStatusToComplete(int orderId, int revisorId) => _customOrderSignalREndpoint.SetOrderStatusToComplete(orderId, revisorId);

        [NonAction]
        public Task<List<OrderDto>> GetAllOrderDtoByIds(int[] orderIds) => _customOrderSignalREndpoint.GetAllOrderDtoByIds(orderIds);

        [NonAction]
        public Task<List<OrderItemDto>> GetAllOrderItemDtos() => _customOrderSignalREndpoint.GetAllOrderItemDtos();

        [NonAction]
        public Task<List<OrderDto>> GetAllOrderDtoByProductId(int productId) => _customOrderSignalREndpoint.GetAllOrderDtoByProductId(productId);

        [NonAction]
        public Task<OrderItemDto> GetOrderItemDtoById(int orderItemId) => _customOrderSignalREndpoint.GetOrderItemDtoById(orderItemId);

        [NonAction]
        public Task<List<OrderItemDto>> GetAllOrderItemDtoByOrderId(int orderId) => _customOrderSignalREndpoint.GetAllOrderItemDtoByOrderId(orderId);

        [NonAction]
        public Task<List<OrderItemDto>> GetAllOrderItemDtoByProductId(int productId) => _customOrderSignalREndpoint.GetAllOrderItemDtoByProductId(productId);

        [NonAction]
        public Task<List<OrderItemPallet>> GetAllOrderItemPallets() => _customOrderSignalREndpoint.GetAllOrderItemPallets();

        [NonAction]
        public Task<OrderItemPallet> GetOrderItemPalletById(int orderItemPalletId) => _customOrderSignalREndpoint.GetOrderItemPalletById(orderItemPalletId);

        [NonAction]
        public Task<List<OrderItemPallet>> GetAllOrderItemPalletByOrderItemId(int orderItemId) => _customOrderSignalREndpoint.GetAllOrderItemPalletByOrderItemId(orderItemId);

        [NonAction]
        public Task<List<OrderItemPallet>> GetAllOrderItemPalletByOrderId(int orderId) => _customOrderSignalREndpoint.GetAllOrderItemPalletByOrderId(orderId);

        [NonAction]
        public Task<List<OrderItemPallet>> GetAllOrderItemPalletByProductId(int productId) => _customOrderSignalREndpoint.GetAllOrderItemPalletByProductId(productId);

        [NonAction]
        public Task<OrderItemPallet> AddOrUpdateMeasuredOrderItemPallet(OrderItemPallet orderItemPallet) => _customOrderSignalREndpoint.AddOrUpdateMeasuredOrderItemPallet(orderItemPallet);

        #endregion CustomOrderSignalREndpoint

        [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)]
        public virtual async Task<IActionResult> List(List<int> orderStatuses = null, List<int> paymentStatuses = null, List<int> shippingStatuses = null)
        {
            //prepare model
            var model = await _orderModelFactory.PrepareOrderSearchModelAsync(new OrderSearchModelExtended
            {
                OrderStatusIds = orderStatuses,
                PaymentStatusIds = paymentStatuses,
                ShippingStatusIds = shippingStatuses,
                Length = 50,
                AvailablePageSizes = "20,50,100,500",
                SortColumn = "Id",
                SortColumnDirection = "desc",
            });

            model.SetGridSort("Id", "desc");
            model.SetGridPageSize(50, "20,50,100,500");

            return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/List.cshtml", model);
        }

            [HttpPost]
        public virtual async Task<IActionResult> AdminQuickCreateOrder(int customerId, string orderProductsJson, string deliveryDateTime)
        {
            if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE))
                return Json(new { success = false, error = "Hozzáférés megtagadva" });
            try
            {
                var customer = await _customerService.GetCustomerByIdAsync(customerId);
                if (customer == null) return Json(new { success = false, error = "Az ügyfél nem található" });

                var billingAddress = await _customerService.GetCustomerBillingAddressAsync(customer);
                if (billingAddress == null)
                {
                    var addresses = await _customerService.GetAddressesByCustomerIdAsync(customer.Id);
                    if (addresses?.Count > 0) { billingAddress = addresses[0]; customer.BillingAddressId = billingAddress.Id; await _customerService.UpdateCustomerAsync(customer); }
                    else return Json(new { success = false, error = "Az ügyfélnek nincs számlázási címe" });
                }

                var orderProducts = string.IsNullOrEmpty(orderProductsJson)
                    ? new List<OrderProductItem>()
                    : Newtonsoft.Json.JsonConvert.DeserializeObject<List<OrderProductItem>>(orderProductsJson);

                var store = await _storeContext.GetCurrentStoreAsync();
                var admin = await _workContext.GetCurrentCustomerAsync();

                var order = new Order
                {
                    OrderGuid = Guid.NewGuid(), CustomOrderNumber = "", CustomerId = customerId,
                    CustomerLanguageId = customer.LanguageId ?? 2,
                    CustomerTaxDisplayType = Nop.Core.Domain.Tax.TaxDisplayType.IncludingTax,
                    CustomerIp = string.Empty,
                    OrderStatus  = Nop.Core.Domain.Orders.OrderStatus.Pending,
                    PaymentStatus = Nop.Core.Domain.Payments.PaymentStatus.Pending,
                    ShippingStatus = Nop.Core.Domain.Shipping.ShippingStatus.ShippingNotRequired,
                    CreatedOnUtc = DateTime.UtcNow,
                    BillingAddressId = customer.BillingAddressId ?? 0,
                    ShippingAddressId = customer.ShippingAddressId,
                    PaymentMethodSystemName = "Payments.CheckMoneyOrder",
                    CustomerCurrencyCode = "HUF", CurrencyRate = 1,
                    OrderTotal = 0, OrderSubtotalInclTax = 0, OrderSubtotalExclTax = 0,
                    OrderSubTotalDiscountInclTax = 0, OrderSubTotalDiscountExclTax = 0,
                };

                var ok = await _dbContext.TransactionSafeAsync(async _ =>
                {
                    await _orderService.InsertOrderAsync(order);
                    order.CustomOrderNumber = order.Id.ToString();
                    await AddOrderItemsThenUpdateOrder(order, orderProducts, true, customer, store, admin);
                    return true;
                });

                if (!ok) return Json(new { success = false, error = "Rendelés létrehozása meghiúsult" });

                if (!string.IsNullOrWhiteSpace(deliveryDateTime) && DateTime.TryParse(deliveryDateTime, out var deliveryDate))
                {
                    var formatted = deliveryDate.ToString("MM/dd/yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
                    await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, string>(
                        order.Id, nameof(IOrderDto.DateOfReceipt), formatted, store.Id);
                }

                _logger.Info($"[AdminQuickCreateOrder] Order #{order.Id} for customer #{customerId}");
                return Json(new { success = true, orderId = order.Id });
            }
            catch (Exception ex)
            {
                _logger.Error($"[AdminQuickCreateOrder] {ex.Message}", ex);
                return Json(new { success = false, error = ex.Message });
            }
        }

    [HttpPost]
        [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)]
        public async Task<IActionResult> OrderList(OrderSearchModelExtended searchModel)
        {
            //prepare model
            //if (searchModel.SortColumn.IsNullOrWhiteSpace())
            //{
            //    searchModel.SortColumn = "Id";
            //    searchModel.SortColumnDirection = "desc";
            //}

            var orderListModel = await GetOrderListModelByFilter(searchModel);
            //var orderListModel = new OrderListModel();

            var valami = Json(orderListModel);
            Console.WriteLine(valami);
            return valami;
        }

        [HttpPost, ActionName("List")]
        [FormValueRequired("go-to-order-by-number")]
        [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)]
        public virtual async Task<IActionResult> GoToOrderId(OrderSearchModel model)
        {
            var order = await _orderService.GetOrderByCustomOrderNumberAsync(model.GoDirectlyToCustomOrderNumber);

            if (order == null)
                return await List();

            return RedirectToAction("Edit", new { id = order.Id });
        }

        [HttpGet]
        //[Route("Edit/{id}")]
        [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)]
        public virtual async Task<IActionResult> Edit(int id)
        {
            //try to get an order with the specified id
            var order = await _orderService.GetOrderByIdAsync(id);
            if (order == null || order.Deleted)
                return RedirectToAction("List");

            //a vendor does not have access to this functionality
            if (await _workContext.GetCurrentVendorAsync() != null)
                return RedirectToAction("List");

            //prepare model
            var model = await _orderModelFactory.PrepareOrderModelExtendedAsync(null, order);

            return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/Edit.cshtml", model);
        }

        //public async Task<OrderListModelExtended> GetOrderListModelByFilter(OrderSearchModelExtended searchModel)
        //{
        //    //return _customOrderService.
        //    var orderListModel = await _orderModelFactory.PrepareOrderListModelExtendedAsync(searchModel);

        //    _logger.Detail($"Total: {orderListModel.RecordsTotal}, Data Count: {orderListModel.Data.Count()}");
        //    foreach (var item in orderListModel.Data.Take(3))
        //    {
        //        _logger.Detail($"Order: {item.Id}, {item.CustomOrderNumber}");
        //    }

        //    return orderListModel;
        //}

        public async Task<OrderListModelExtended> GetOrderListModelByFilter(OrderSearchModelExtended searchModel)
        {
            //if (searchModel.SortColumn.IsNullOrWhiteSpace())
            {
                var sortColumnIndex = Request.Form["order[0][column]"].FirstOrDefault();
                var sortDirection = Request.Form["order[0][dir]"].FirstOrDefault();

                if (!string.IsNullOrEmpty(sortColumnIndex))
                {
                    // Get the column name from the column index
                    var columnName = Request.Form[$"columns[{sortColumnIndex}][data]"].FirstOrDefault();

                    searchModel.SortColumn = columnName;

                    if (int.Parse(sortColumnIndex) > 0) searchModel.SortColumnDirection = sortDirection; // "asc" or "desc"
                    else searchModel.SortColumnDirection = "desc";
                }
                //else
                //{
                //    searchModel.SortColumn = "Id";
                //    searchModel.SortColumnDirection = "desc";
                //}
            }


            // Get the paginated data
            var orderListModel = await _orderModelFactory.PrepareOrderListModelExtendedAsync(searchModel);

            _logger.Detail($"Total: {orderListModel.RecordsTotal}, Data Count: {orderListModel.Data.Count()}");

            // Apply sorting if specified
            if (!string.IsNullOrEmpty(searchModel.SortColumn) && orderListModel.Data.Any())
            {
                var sortedData = orderListModel.Data.AsQueryable();

                sortedData = searchModel.SortColumn.ToLowerInvariant() switch
                {
                    "id" => searchModel.SortColumnDirection == "asc"
                        ? sortedData.OrderBy(o => o.Id)
                        : sortedData.OrderByDescending(o => o.Id),
                    "customercompany" => searchModel.SortColumnDirection == "asc"
                        ? sortedData.OrderBy(o => o.CustomerCompany)
                        : sortedData.OrderByDescending(o => o.CustomerCompany),
                    "customordernumber" => searchModel.SortColumnDirection == "asc"
                        ? sortedData.OrderBy(o => o.CustomOrderNumber)
                        : sortedData.OrderByDescending(o => o.CustomOrderNumber),
                    "ordertotal" => searchModel.SortColumnDirection == "asc"
                        ? sortedData.OrderBy(o => o.OrderTotal)
                        : sortedData.OrderByDescending(o => o.OrderTotal),
                    "createdon" => searchModel.SortColumnDirection == "asc"
                        ? sortedData.OrderBy(o => o.CreatedOn)
                        : sortedData.OrderByDescending(o => o.CreatedOn),
                    "orderstatusid" => searchModel.SortColumnDirection == "asc"
                        ? sortedData.OrderBy(o => o.OrderStatusId)
                        : sortedData.OrderByDescending(o => o.OrderStatusId),
                    "paymentstatusid" => searchModel.SortColumnDirection == "asc"
                        ? sortedData.OrderBy(o => o.PaymentStatusId)
                        : sortedData.OrderByDescending(o => o.PaymentStatusId),
                    "shippingstatusid" => searchModel.SortColumnDirection == "asc"
                        ? sortedData.OrderBy(o => o.ShippingStatusId)
                        : sortedData.OrderByDescending(o => o.ShippingStatusId),
                    _ => sortedData
                };

                orderListModel.Data = sortedData.ToList();
                orderListModel.RecordsTotal = orderListModel.Data.Count();
                //orderListModel.Draw = searchModel.Draw;

                Console.WriteLine($"Sorted Data Count: {orderListModel.Data.Count()}");
                Console.WriteLine($"Total Records: {orderListModel.RecordsTotal}");
                Console.WriteLine($"Filtered Records: {orderListModel.RecordsFiltered}");
                Console.WriteLine($"Draw: {orderListModel.Draw}");


                _logger.Detail($"Sorted by {searchModel.SortColumn} {searchModel.SortColumnDirection}");
            }

            foreach (var item in orderListModel.Data.Take(3))
            {
                _logger.Detail($"Order: {item.Id}, {item.CustomOrderNumber}");
            }

            return orderListModel;
        }


        public virtual IActionResult Test()
        {
            // Your custom logic here
            // This will use your custom List.cshtml view
            return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/Test.cshtml");
        }

        //[HttpPost]
        //[CheckPermission(Nop.Services.Security.StandardPermission.Orders.ORDERS_VIEW)]
        //public virtual async Task<IActionResult> OrderList(OrderSearchModel searchModel)
        //{
        //    //prepare model
        //    var model = await _orderModelFactory.PrepareOrderListModelAsync(searchModel);

        //    return Json(model);
        //}

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> SaveOrderAttributes(OrderAttributesModel model)
        {
            if (!ModelState.IsValid)
            {
                // reload order page with errors
                return RedirectToAction("Edit", "Order", new { id = model.OrderId });
            }

            var order = await _orderService.GetOrderByIdAsync(model.OrderId);
            if (order == null)
                return RedirectToAction("List", "Order");

            //TODO: A FruitBankAttributeService-t használjuk és akkor az OrderDto lehet lekérni és beadni a SaveAttribute-ba! - J.

            // store attributes in GenericAttribute table
            //await _genericAttributeService.SaveAttributeAsync(order, nameof(IMeasurable.IsMeasurable), model.IsMeasurable, _storeContext.GetCurrentStore().Id);
            //await _genericAttributeService.SaveAttributeAsync(order, nameof(IOrderDto.DateOfReceipt), model.DateOfReceipt, _storeContext.GetCurrentStore().Id);
            await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, string>(
                order.Id,
                nameof(IOrderDto.DateOfReceipt),
                model.DateOfReceipt.HasValue
                    ? model.DateOfReceipt.Value.ToString("MM/dd/yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture)
                    : null,
                _storeContext.GetCurrentStore().Id);


            var orderDto = await _dbContext.OrderDtos.GetByIdAsync(model.OrderId, true);
            await _sendToClient.SendOrderChanged(orderDto);

            _notificationService.SuccessNotification("Custom attributes saved successfully.");
            return RedirectToAction("Edit", "Order", new { id = model.OrderId });
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> AllowRevision(OrderRevisionModel model)
        {
            if (!ModelState.IsValid)
            {
                // reload order page with errors
                return RedirectToAction("Edit", "Order", new { id = model.OrderId });
            }

            var order = await _orderService.GetOrderByIdAsync(model.OrderId);
            if (order == null)
                return RedirectToAction("List", "Order");


            //MeasurementService.OrderItemMeasuringReset
            //Todo: ezt orderitiemnként kéne kirakni?? - Á. 
            var valami = await _measurementService.OrderItemMeasuringReset(model.OrderItemId);

            return RedirectToAction("Edit", "Order", new { id = model.OrderId });
        }



        [HttpPost]
        //[CheckPermission(StandardPermission.Orders.ORDERS_CREATE)]
        public virtual async Task<IActionResult> Create(int customerId, string orderProductsJson)
        {
            if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE))
                return AccessDeniedView();

            // Validate customer
            var customer = await _customerService.GetCustomerByIdAsync(customerId);
            if (customer == null) return RedirectToAction("List");

            var billingAddress = await _customerService.GetCustomerBillingAddressAsync(customer);
            if (billingAddress == null)
            {
                //let's see if he has any address at all
                var addresses = await _customerService.GetAddressesByCustomerIdAsync(customer.Id);
                if (addresses != null && addresses.Count > 0)
                {
                    //set the first one as billing
                    billingAddress = addresses[0];
                    customer.BillingAddressId = billingAddress.Id;
                    await _customerService.UpdateCustomerAsync(customer);
                }
                else
                {
                    //no address at all, cannot create order
                    _logger.Error($"Cannot create order for customer {customer.Id}, no billing address found.");
                    _notificationService.ErrorNotification("Cannot create order for customer, no billing address found. Please create a billing address for the customer first.");
                    return RedirectToAction("List");
                }
            }

            //var currency = await _workContext.GetWorkingCurrencyAsync();
            //customer.CurrencyId = currency.Id;

            // Parse products
            var orderProducts = string.IsNullOrEmpty(orderProductsJson) ? [] : JsonConvert.DeserializeObject<List<OrderProductItem>>(orderProductsJson);

            // Create order
            var order = new Order
            {
                OrderGuid = Guid.NewGuid(),
                CustomOrderNumber = "",
                CustomerId = customerId,
                CustomerLanguageId = customer.LanguageId ?? 2,
                CustomerTaxDisplayType = TaxDisplayType.IncludingTax,
                CustomerIp = string.Empty,
                OrderStatus = OrderStatus.Pending,
                PaymentStatus = PaymentStatus.Pending,
                ShippingStatus = ShippingStatus.ShippingNotRequired,
                CreatedOnUtc = DateTime.UtcNow,
                BillingAddressId = customer.BillingAddressId ?? 0,
                ShippingAddressId = customer.ShippingAddressId,
                PaymentMethodSystemName = "Payments.CheckMoneyOrder", // Default payment method
                CustomerCurrencyCode = "HUF", // TODO: GET Default currency - A.
                CurrencyRate = 1,
                OrderTotal = 0,
                OrderSubtotalInclTax = 0,
                OrderSubtotalExclTax = 0,
                OrderSubTotalDiscountInclTax = 0,
                OrderSubTotalDiscountExclTax = 0,
            };

            //var productDtosById = await _dbContext.ProductDtos.GetAllByIds(orderProducts.Select(op => op.Id)).ToDictionaryAsync(p => p.Id, prodDto => prodDto);
            var store = await _storeContext.GetCurrentStoreAsync();
            var admin = await _workContext.GetCurrentCustomerAsync();

            var transactionSuccess = await _dbContext.TransactionSafeAsync(async _ =>
            {
                await _orderService.InsertOrderAsync(order);
                order.CustomOrderNumber = order.Id.ToString();

                await AddOrderItemsThenUpdateOrder(order, orderProducts, true, customer, store, admin);
                return true;
            });

            if (transactionSuccess)
            {
                //var orderDto = await _dbContext.OrderDtos.GetByIdAsync(order.Id, true);
                //await _sendToClient.SendMeasuringNotification("Módosult a rendelés, mérjétek újra!", orderDto);
                //var updatedOrder = await _orderService.GetOrderByIdAsync(order.Id);
                //await _workflowMessageService.SendOrderPlacedCustomerNotificationAsync(order, order.CustomerLanguageId);
                if(customer.BillingAddressId.HasValue)
                {
                    //var billingAddress = await _addressService.GetAddressByIdAsync((int)customer.BillingAddressId);
                    if (billingAddress.Email != null)
                    {
                        if (!billingAddress.Email.EndsWith("inval.id"))
                        {
                            var messageResult = await _fruitBankNotificationService.SendOrderPlacedCustomerNotificationAsync(order, order.CustomerLanguageId);
                            if (messageResult.First() != -1)
                            {
                                _notificationService.SuccessNotification("Order placed email sent to customer.");
                            }
                            else
                            {
                                _logger.Warning($"Order placed email was not sent to customer {customer.Id} because of invalid email address: {billingAddress.Email}");
                                _notificationService.WarningNotification("Order placed email was not sent to customer because of invalid email address.");
                            }
                        }
                    }
                    
                }
                return RedirectToAction("Edit", "Order", new { id = order.Id });
            }

            _logger.Error($"(transactionSuccess == false)");
            return RedirectToAction("Error", new { id = order.Id });
        }

        private async Task AddOrderItemsThenUpdateOrder<TOrderProductItem>(Order order, IReadOnlyList<TOrderProductItem> orderProductItems, bool unitPricesIncludeDiscounts, Customer customer = null, Store store = null, Customer admin = null)
            where TOrderProductItem : IOrderProductItemBase
        {
            store ??= await _storeContext.GetCurrentStoreAsync();
            admin ??= await _workContext.GetCurrentCustomerAsync();
            customer ??= await _workContext.GetCurrentCustomerAsync();

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

            foreach (var orderProductItem in orderProductItems)
            {
                var product = await _productService.GetProductByIdAsync(orderProductItem.Id);
                if (product == null)
                {
                    _logger.Warning($"Product with ID {orderProductItem.Id} not found");
                    continue;

                    //var errorText = $"product == null; productId: {item.Id};";

                    //_logger.Error($"{errorText}");
                    //throw new Exception($"{errorText}");
                }

                //var stockQuantity = await _productService.GetTotalStockQuantityAsync(product);
                var productDto = helperProductDtosByOrderItemId[orderProductItem.Id];
                var isMeasurable = productDto.IsMeasurable;

                if ((product.StockQuantity + productDto.IncomingQuantity) - orderProductItem.Quantity < 0)
                {
                    //errorMessage = $"Nem elérhető készleten!";
                    var errorText = $"((product.StockQuantity + productDto.IncomingQuantity) - item.Quantity < 0); productId: {product.Id}; (product.StockQuantity + productDto.IncomingQuantity) - item.Quantity: {(product.StockQuantity + productDto.IncomingQuantity) - orderProductItem.Quantity}";

                    _logger.Error($"{errorText}");
                    throw new Exception($"{errorText}");
                }

                //itt vajon elég ez a vizsgálat, vagy a priceCalculationService.GetFinalPriceAsync-al kéne lekérni a végső árat és azt összehasonlítani? - A.
                //ha kedvezménye is van, de manuálisan is le van csökkentve az ár, akkor a kedvezményt látja a rendszer, és azt kellene összevetni a bejövő árral... - A.
                if (orderProductItem.Price != product.Price)
                {
                    //manual price change
                    unitPricesIncludeDiscounts = false;
                }
                else
                {
                    unitPricesIncludeDiscounts = true;
                }

                //itt ha includeDiscounts van, akkor már a beírt ár megy be?
                var orderItem = await CreateOrderItem(product, order, orderProductItem, isMeasurable, unitPricesIncludeDiscounts, customer, store);

                _logger.Detail($"Adding order item: ProductId: {orderItem.ProductId}, Quantity: {orderItem.Quantity}, UnitPriceInclTax: {orderItem.UnitPriceInclTax}, UnitPriceExclTax: {orderItem.UnitPriceExclTax}, PriceInclTax: {orderItem.PriceInclTax}, PriceExclTax: {orderItem.PriceExclTax}");

                await _orderService.InsertOrderItemAsync(orderItem);
                await _productService.AdjustInventoryAsync(product, -orderItem.Quantity, orderItem.AttributesXml, string.Format(await _localizationService.GetResourceAsync("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id));

                // Use the order item values directly — these already reflect any manual
                // price override from CreateOrderItem, unlike a fresh GetFinalPriceAsync call.
                order.OrderSubtotalInclTax += orderItem.UnitPriceInclTax * orderItem.Quantity;
                order.OrderSubtotalExclTax += orderItem.UnitPriceExclTax * orderItem.Quantity;

                // Discount only applies when price was NOT manually overridden.
                if (unitPricesIncludeDiscounts)
                {
                    var priceCalculation = await _priceCalculationService.GetFinalPriceAsync(product, customer, store, includeDiscounts: true);
                    var appliedDiscounts = priceCalculation.appliedDiscountAmount;
                    order.OrderSubTotalDiscountInclTax += appliedDiscounts * orderItem.Quantity;
                    order.OrderSubTotalDiscountExclTax += appliedDiscounts * orderItem.Quantity;
                }

                // OrderTotal: add item price only. Shipping and payment fees are NOT added
                // per item — they are already in the order total from creation and should
                // not be multiplied by the number of items being added.
                order.OrderTotal += orderItem.PriceInclTax;
            }

            await _orderService.UpdateOrderAsync(order);

            await InsertOrderNoteAsync(order.Id, false, $"Products added {orderProductItems.Count} item to order by {admin.FirstName} {admin.LastName}, (CustomerId: {admin.Id})");
        }

        private async Task<OrderItem> CreateOrderItem<TOrderProductItem>(Product product, Order order, TOrderProductItem orderProductItem, bool isMeasurable, bool unitPricesIncludeDiscounts, Customer customer = null, Store store = null)
            where TOrderProductItem : IOrderProductItemBase
        {
            if (product.Id != orderProductItem.Id)
                throw new Exception($"CustomOrderController->CreateOrderItem; (product.Id != orderProductItem.Id)");

            store ??= await _storeContext.GetCurrentStoreAsync();
            customer ??= await _workContext.GetCurrentCustomerAsync();

            decimal unitPriceInclTaxValue = 0;

            if (unitPricesIncludeDiscounts)
            {
                var priceCalculation = await _priceCalculationService.GetFinalPriceAsync(product, customer, store, includeDiscounts: unitPricesIncludeDiscounts);
                unitPriceInclTaxValue = priceCalculation.finalPrice;
            }
            else
            {
                unitPriceInclTaxValue = orderProductItem.Price;
            }


            // Calculate tax
            //var (unitPriceInclTaxValue, _) = await _taxService.GetProductPriceAsync(product, unitPrice, true, customer);
            var (unitPriceExclTaxValue, _) = await _taxService.GetProductPriceAsync(product, unitPriceInclTaxValue, false, customer);

            return new OrderItem
            {
                OrderId = order.Id,
                ProductId = orderProductItem.Id,
                Quantity = orderProductItem.Quantity,

                OrderItemGuid = Guid.NewGuid(),

                UnitPriceInclTax = unitPriceInclTaxValue,
                UnitPriceExclTax = unitPriceExclTaxValue,

                PriceInclTax = isMeasurable ? 0 : unitPriceInclTaxValue * orderProductItem.Quantity,
                PriceExclTax = isMeasurable ? 0 : unitPriceExclTaxValue * orderProductItem.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 * orderProductItem.Quantity,
                RentalStartDateUtc = null,
                RentalEndDateUtc = null
            };
        }

        // IOrderProductItemBase is defined in Models/Orders/IOrderProductItemBase.cs
        // and used as the shared contract across CustomOrderController and FruitBankOrderItemService.

        public class OrderProductItem : IOrderProductItemBase
        {
            /// <summary>
            /// ProductId
            /// </summary>
            public int Id { get; set; }
            public string Name { get; set; }
            public string Sku { get; set; }
            public int Quantity { get; set; }
            public decimal Price { get; set; }

            public override string ToString()
            {
                return $"{nameof(OrderProductItem)} [ProductId: {Id}; Name: {Name}; Sku: {Sku}; Quantity: {Quantity}; Price: {Price}]";
            }
        }

        public class AddProductModel : OrderProductItem
        {
            ///// <summary>
            ///// ProductId
            ///// </summary>
            //public int Id { get; set; }
            //public string Name { get; set; }
            //public string Sku { get; set; }
            //public int Quantity { get; set; }
            //public decimal Price { get; set; }
            public int StockQuantity { get; set; }
            public int IncomingQuantity { get; set; }
        }

        //private static OrderItem CreateOrderItem(ProductToAuctionMapping productToAuction, Order order, decimal orderTotal)
        //{
        //    return new OrderItem
        //    {
        //        ProductId = productToAuction.ProductId,
        //        OrderId = order.Id,
        //        OrderItemGuid = Guid.NewGuid(),
        //        PriceExclTax = orderTotal,
        //        PriceInclTax = orderTotal,
        //        UnitPriceExclTax = orderTotal,
        //        UnitPriceInclTax = orderTotal,
        //        Quantity = productToAuction.ProductAmount,
        //    };
        //}

        //private static Order CreateOrder(ProductToAuctionMapping productToAuction, decimal orderTotal, Customer customer, Address billingAddress, int storeId, Dictionary<string, object> customValues)
        //{
        //    return new Order
        //    {
        //        BillingAddressId = billingAddress.Id,
        //        CreatedOnUtc = DateTime.UtcNow,
        //        CurrencyRate = 1,
        //        CustomOrderNumber = productToAuction.AuctionId + "/" + productToAuction.SortIndex,
        //        CustomValuesXml = SerializeCustomValuesToXml(customValues),
        //        CustomerCurrencyCode = "HUF",
        //        CustomerId = productToAuction.WinnerCustomerId,
        //        CustomerLanguageId = 2,
        //        CustomerTaxDisplayType = TaxDisplayType.IncludingTax,
        //        OrderGuid = Guid.NewGuid(),
        //        OrderStatus = OrderStatus.Pending,
        //        OrderTotal = orderTotal,
        //        PaymentStatus = PaymentStatus.Pending,
        //        PaymentMethodSystemName = "Payments.CheckMoneyOrder",
        //        ShippingStatus = ShippingStatus.ShippingNotRequired,
        //        StoreId = storeId,
        //        VatNumber = customer.VatNumber,
        //        CustomerIp = customer.LastIpAddress,
        //        OrderSubtotalExclTax = orderTotal,
        //        OrderSubtotalInclTax = orderTotal
        //    };
        //}


        /// <summary>
        /// Add a note to an order that will be displayed in the external application
        /// </summary>
        [HttpPost]
        public async Task<IActionResult> AddOrderNote(int orderId, string note)
        {
            if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE))
                return Json(new { success = false, message = "Access denied" });

            if (orderId <= 0)
            {
                return Json(new { success = false, message = "Invalid order ID" });
            }

            if (string.IsNullOrWhiteSpace(note))
            {
                return Json(new { success = false, message = "Note text is required" });
            }

            try
            {
                // Create and insert the order note
                await InsertOrderNoteAsync(orderId, displayToCustomer: false, note);

                return Json(new
                {
                    success = true,
                    message = "Order note added successfully"
                });
            }
            catch (Exception ex)
            {
                return Json(new
                {
                    success = false,
                    message = $"Error adding order note: {ex.Message}"
                });
            }
        }


        private static OrderNote CreateOrderNote(int orderId, bool displayToCustomer, string note)
        {
            return new OrderNote
            {
                CreatedOnUtc = DateTime.UtcNow,//order.CreatedOnUtc,
                DisplayToCustomer = displayToCustomer,
                OrderId = orderId,
                Note = note
            };
        }

        public Task InsertOrderNoteAsync(int orderId, bool displayToCustomer, string note)
        {
            var orderNote = CreateOrderNote(orderId, displayToCustomer, note);
            return _orderService.InsertOrderNoteAsync(orderNote);
        }

        private static string SerializeCustomValuesToXml(Dictionary<string, object> sourceDictionary)
        {
            ArgumentNullException.ThrowIfNull(sourceDictionary);

            if (!sourceDictionary.Any())
                return null;

            var ds = new DictionarySerializer(sourceDictionary);
            var xs = new XmlSerializer(typeof(DictionarySerializer));

            using var textWriter = new StringWriter();
            using (var xmlWriter = XmlWriter.Create(textWriter))
            {
                xs.Serialize(xmlWriter, ds);
            }

            var result = textWriter.ToString();
            return result;
        }


        [HttpGet] // Change from [HttpPost] to [HttpGet]
        [CheckPermission(StandardPermission.Customers.CUSTOMERS_VIEW)]
        public virtual async Task<IActionResult> CustomerSearchAutoComplete(string term)
        {
            if (string.IsNullOrWhiteSpace(term) || term.Length < 3) return Json(new List<object>());

            const int maxResults = 15;

            // Search by email (contains)
            var customersByEmail = await _customerService.GetAllCustomersAsync(
                email: term,
                pageIndex: 0,
                pageSize: maxResults);

            // Search by first name (contains)
            var customersByFirstName = await _customerService.GetAllCustomersAsync(
                firstName: term,
                pageIndex: 0,
                pageSize: maxResults);

            // Search by last name (contains)
            var customersByLastName = await _customerService.GetAllCustomersAsync(
                lastName: term,
                pageIndex: 0,
                pageSize: maxResults);

            var customersByCompanyName = await _customerService.GetAllCustomersAsync(
                company: term,
                pageIndex: 0,
                pageSize: maxResults);

            // Combine and deduplicate results
            var allCustomers = customersByEmail
                .Union(customersByFirstName)
                .Union(customersByLastName)
                .Union(customersByCompanyName)
                .DistinctBy(c => c.Id)
                .Take(maxResults)
                .ToList();

            var result = new List<object>();
            foreach (var customer in allCustomers)
            {
                var fullName = await _customerService.GetCustomerFullNameAsync(customer);
                var company = customer.Company;

                if (string.IsNullOrEmpty(fullName))
                    fullName = "[No name]";
                if (string.IsNullOrEmpty(company))
                    company = "[No company]";

                string fullText = $"{company} ({fullName}), {customer.Email}";

                //var displayText = !string.IsNullOrEmpty(customer.Email)
                //    ? $"{customer.Email}, {customer.Company} ({fullName})"
                //    : fullName;

                result.Add(new
                {
                    label = fullText,
                    value = customer.Id
                });
            }

            return Json(result);
        }

        [HttpGet]
        [CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
        public virtual async Task<IActionResult> ProductSearchAutoComplete(string term)
        {
            if (string.IsNullOrWhiteSpace(term) || term.Length < 3) return Json(new List<object>());

            const int maxResults = 300;

            // Search products by name or SKU
            var products = await _productService.SearchProductsAsync(keywords: term, pageIndex: 0, pageSize: maxResults);

            var result = new List<object>();
            //var productDtosById = await _dbContext.ProductDtos.GetAllByIds(products.Select(p => p.Id)).Where(x => x.AvailableQuantity > 0).ToDictionaryAsync(k => k.Id, v => v);
            var productDtos = await _dbContext.ProductDtos.GetAllByIds(products.Select(p => p.Id)).ToArrayAsync();

            foreach (var productDto in productDtos.Where(x => x.AvailableQuantity > 0))
            {
                result.Add(new
                {
                    label = $"{productDto.Name}   [RENDELHETŐ: {productDto.AvailableQuantity} (R:{productDto.StockQuantity}/K:{productDto.IncomingQuantity})]  [ÁR: {productDto.Price}]",
                    value = productDto.Id,
                    sku = productDto.Sku,
                    price = productDto.Price,
                    stockQuantity = productDto.StockQuantity,
                    availableQuantity = productDto.AvailableQuantity,
                });
            }

            return Json(result);
        }

        [HttpGet]
        [CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
        public virtual async Task<IActionResult> PreOrderProductSearchAutoComplete(string term)
    {
        if (string.IsNullOrWhiteSpace(term) || term.Length < 3) return Json(new List<object>());

        const int maxResults = 30;
        var today   = DateTime.UtcNow.Date;
        var store   = await _storeContext.GetCurrentStoreAsync();

        // Load preorder window attributes in two batch queries
        var gaStart = await _dbContext.GenericAttributes.Table
            .Where(ga => ga.KeyGroup == nameof(Product)
                      && ga.Key     == FruitBankConst.PreOrderWindowStart
                      && ga.StoreId == store.Id)
            .ToListAsync();

        var gaEnd = await _dbContext.GenericAttributes.Table
            .Where(ga => ga.KeyGroup == nameof(Product)
                      && ga.Key     == FruitBankConst.PreOrderWindowEnd
                      && ga.StoreId == store.Id)
            .ToListAsync();

        var startById = gaStart.ToDictionary(g => g.EntityId, g => g.Value);
        var endById   = gaEnd.ToDictionary(g => g.EntityId, g => g.Value);

        // Product IDs currently in the preorder window
        var availableIds = startById.Keys
            .Intersect(endById.Keys)
            .Where(id =>
            {
                DateTime.TryParse(startById[id], out var ws);
                DateTime.TryParse(endById[id],   out var we);
                return ws.Date <= today && today <= we.Date;
            })
            .ToHashSet();

        if (!availableIds.Any())
            return Json(new List<object>());

        // Search within available products only
        var products = await _productService.SearchProductsAsync(
            keywords: term,
            pageIndex: 0,
            pageSize: maxResults);

        var inWindow = products.Where(p => availableIds.Contains(p.Id)).ToList();
        if (!inWindow.Any())
            return Json(new List<object>());

        var productDtosById = await _dbContext.ProductDtos
            .GetAllByIds(inWindow.Select(p => p.Id))
            .ToDictionaryAsync(k => k.Id, v => v);

        var result = new List<object>();
        foreach (var product in inWindow)
        {
            productDtosById.TryGetValue(product.Id, out var dto);
            result.Add(new
            {
                label           = $"{product.Name}   [KÉSZLET: {(product.StockQuantity + (dto?.IncomingQuantity ?? 0))}]  [ÁR: {product.Price}]",
                value           = product.Id,
                sku             = product.Sku,
                price           = product.Price,
                stockQuantity   = product.StockQuantity,
                incomingQuantity = dto?.IncomingQuantity ?? 0
            });
        }

        return Json(result);
    }

    [HttpGet]
        [CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
        public virtual async Task<IActionResult> ProductSearchUnfilteredAutoComplete(string term)
        {
            if (string.IsNullOrWhiteSpace(term) || term.Length < 3) return Json(new List<object>());

            const int maxResults = 30;

            var products = await _productService.SearchProductsAsync(
                keywords: term,
                pageIndex: 0,
                pageSize: maxResults);

            var result = new List<object>();
            var productDtosById = await _dbContext.ProductDtos.GetAllByIds(products.Select(p => p.Id)).ToDictionaryAsync(k => k.Id, v => v);

            foreach (var product in products)
            {
                var productDto = productDtosById[product.Id];
                if (productDto != null)
                {
                    result.Add(new
                    {
                        label           = $"{product.Name}   [RENDELHETŐ: {(product.StockQuantity + productDto.IncomingQuantity)}]  [ÁR: {product.Price}]",
                        value           = product.Id,
                        sku             = product.Sku,
                        price           = product.Price,
                        stockQuantity   = product.StockQuantity,
                        incomingQuantity = productDto.IncomingQuantity,
                    });
                }
            }

            return Json(result);
        }

        //public async Task<IActionResult> CreateInvoice(int orderId)
        //{
        //    try
        //    {
        //        var order = await _orderService.GetOrderByIdAsync(orderId);
        //        if (order == null)
        //            return Json(new { success = false, message = "Order not found" });

        //        var billingAddress = await _customerService.GetCustomerBillingAddressAsync(order.Customer);
        //        if (billingAddress == null)
        //            return Json(new { success = false, message = "Billing address not found" });

        //        var country = await _countryService.GetCountryByAddressAsync(billingAddress);
        //        var countryCode = country?.TwoLetterIsoCode ?? "HU";

        //        // Create invoice request
        //        var invoiceRequest = new InvoiceCreateRequest
        //        {
        //            VevoNev = $"{billingAddress.FirstName} {billingAddress.LastName}",
        //            VevoIrsz = billingAddress.ZipPostalCode ?? "",
        //            VevoTelep = billingAddress.City ?? "",
        //            VevoOrszag = countryCode,
        //            VevoUtcaHsz = $"{billingAddress.Address1} {billingAddress.Address2}".Trim(),
        //            SzamlatombID = 1, // Configure this based on your setup
        //            SzamlaKelte = DateTime.Now,
        //            TeljesitesKelte = DateTime.Now,
        //            Hatarido = DateTime.Now.AddDays(15), // 15 days payment term
        //            Devizanem = order.CustomerCurrencyCode,
        //            FizetesiMod = order.PaymentMethodSystemName,
        //            Felretett = false,
        //            Proforma = false,
        //            Email = billingAddress.Email,
        //            Telefon = billingAddress.PhoneNumber
        //        };

        //        // Add order items
        //        var orderItems = await _orderService.GetOrderItemsAsync(order.Id);
        //        foreach (var item in orderItems)
        //        {
        //            var product = await _productService.GetProductByIdAsync(item.ProductId);

        //            invoiceRequest.AddItem(new InvoiceItem
        //            {
        //                TetelNev = product?.Name ?? "Product",
        //                AfaSzoveg = "27%", // Configure VAT rate as needed
        //                Brutto = true,
        //                EgysegAr = item.UnitPriceInclTax,
        //                Mennyiseg = item.Quantity,
        //                MennyisegEgyseg = "db",
        //                CikkSzam = product?.Sku
        //            });
        //        }

        //        // Create invoice via API
        //        var response = await _innVoiceApiService.CreateInvoiceAsync(invoiceRequest);

        //        if (response.IsSuccess)
        //        {
        //            // TODO: Save invoice details to your database for future reference
        //            // You might want to create a custom table to store:
        //            // - OrderId
        //            // - InnVoice TableId
        //            // - Invoice Number
        //            // - PDF URL
        //            // - Created Date

        //            return Json(new
        //            {
        //                success = true,
        //                message = "Invoice created successfully",
        //                data = new
        //                {
        //                    tableId = response.TableId,
        //                    invoiceNumber = response.Sorszam,
        //                    sorszam = response.Sorszam,
        //                    printUrl = response.PrintUrl
        //                }
        //            });
        //        }
        //        else
        //        {
        //            return Json(new
        //            {
        //                success = false,
        //                message = $"InnVoice API Error: {response.Message}"
        //            });
        //        }
        //    }
        //    catch (Exception ex)
        //    {
        //        return Json(new
        //        {
        //            success = false,
        //            message = $"Error: {ex.Message}"
        //        });
        //    }
        //}

        //[HttpGet]
        //public async Task<IActionResult> GetInvoiceStatus(int orderId)
        //{
        //    try
        //    {
        //        // TODO: Retrieve invoice details from your database
        //        // This is a placeholder - you need to implement actual storage/retrieval

        //        // Example: var invoiceData = await _yourInvoiceService.GetByOrderIdAsync(orderId);
        //        // if (invoiceData != null)
        //        // {
        //        //     return Json(new
        //        //     {
        //        //         success = true,
        //        //         data = new
        //        //         {
        //        //             tableId = invoiceData.TableId,
        //        //             invoiceNumber = invoiceData.InvoiceNumber,
        //        //             sorszam = invoiceData.InvoiceNumber,
        //        //             printUrl = invoiceData.PrintUrl
        //        //         }
        //        //     });
        //        // }

        //        return Json(new
        //        {
        //            success = false,
        //            message = "No invoice found for this order"
        //        });
        //    }
        //    catch (Exception ex)
        //    {
        //        return Json(new
        //        {
        //            success = false,
        //            message = $"Error: {ex.Message}"
        //        });
        //    }
        //}


        //THE REST

        #region Export / Import

        [HttpPost, ActionName("ExportXml")]
        [FormValueRequired("exportxml-all")]
        [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)]
        public virtual async Task<IActionResult> ExportXmlAll(OrderSearchModelExtended model)
        {
            var startDateValue = model.StartDate == null
                ? null
                : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync());

            var endDateValue = model.EndDate == null
                ? null
                : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync()).AddDays(1);

            //a vendor should have access only to his products
            var currentVendor = await _workContext.GetCurrentVendorAsync();
            if (currentVendor != null)
            {
                model.VendorId = currentVendor.Id;
            }

            var orderStatusIds = model.OrderStatusIds != null && !model.OrderStatusIds.Contains(0)
                ? model.OrderStatusIds.ToList()
                : null;
            var paymentStatusIds = model.PaymentStatusIds != null && !model.PaymentStatusIds.Contains(0)
                ? model.PaymentStatusIds.ToList()
                : null;
            var shippingStatusIds = model.ShippingStatusIds != null && !model.ShippingStatusIds.Contains(0)
                ? model.ShippingStatusIds.ToList()
                : null;

            var filterByProductId = 0;
            var product = await _productService.GetProductByIdAsync(model.ProductId);
            if (product != null && (currentVendor == null || product.VendorId == currentVendor.Id))
                filterByProductId = model.ProductId;

            //load orders
            var orders = await _orderService.SearchOrdersAsync(storeId: model.StoreId,
                vendorId: model.VendorId,
                productId: filterByProductId,
                warehouseId: model.WarehouseId,
                paymentMethodSystemName: model.PaymentMethodSystemName,
                createdFromUtc: startDateValue,
                createdToUtc: endDateValue,
                osIds: orderStatusIds,
                psIds: paymentStatusIds,
                ssIds: shippingStatusIds,
                billingPhone: model.BillingPhone,
                billingEmail: model.BillingEmail,
                billingLastName: model.BillingLastName,
                billingCountryId: model.BillingCountryId,
                orderNotes: model.OrderNotes);

            //ensure that we at least one order selected
            if (!orders.Any())
            {
                _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Orders.NoOrders"));
                return RedirectToAction("List");
            }

            try
            {
                var xml = await _exportManager.ExportOrdersToXmlAsync(orders);
                return File(Encoding.UTF8.GetBytes(xml), MimeTypes.ApplicationXml, "orders.xml");
            }
            catch (Exception exc)
            {
                await _notificationService.ErrorNotificationAsync(exc);
                return RedirectToAction("List");
            }
        }

        [HttpPost]
        [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)]
        public virtual async Task<IActionResult> ExportXmlSelected(string selectedIds)
        {
            var orders = new List<Order>();
            if (selectedIds != null)
            {
                var ids = selectedIds
                    .Split(_separator, StringSplitOptions.RemoveEmptyEntries)
                    .Select(x => Convert.ToInt32(x))
                    .ToArray();
                orders.AddRange(await (await _orderService.GetOrdersByIdsAsync(ids))
                    .WhereAwait(HasAccessToOrderAsync).ToListAsync());
            }

            try
            {
                var xml = await _exportManager.ExportOrdersToXmlAsync(orders);
                return File(Encoding.UTF8.GetBytes(xml), MimeTypes.ApplicationXml, "orders.xml");
            }
            catch (Exception exc)
            {
                await _notificationService.ErrorNotificationAsync(exc);
                return RedirectToAction("List");
            }
        }

        [HttpPost, ActionName("ExportExcel")]
        [FormValueRequired("exportexcel-all")]
        [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)]
        public virtual async Task<IActionResult> ExportExcelAll(OrderSearchModelExtended model)
        {
            var startDateValue = model.StartDate == null
                ? null
                : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync());

            var endDateValue = model.EndDate == null
                ? null
                : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync()).AddDays(1);

            //a vendor should have access only to his products
            var currentVendor = await _workContext.GetCurrentVendorAsync();
            if (currentVendor != null)
            {
                model.VendorId = currentVendor.Id;
            }

            var orderStatusIds = model.OrderStatusIds != null && !model.OrderStatusIds.Contains(0)
                ? model.OrderStatusIds.ToList()
                : null;
            var paymentStatusIds = model.PaymentStatusIds != null && !model.PaymentStatusIds.Contains(0)
                ? model.PaymentStatusIds.ToList()
                : null;
            var shippingStatusIds = model.ShippingStatusIds != null && !model.ShippingStatusIds.Contains(0)
                ? model.ShippingStatusIds.ToList()
                : null;

            var filterByProductId = 0;
            var product = await _productService.GetProductByIdAsync(model.ProductId);
            if (product != null && (currentVendor == null || product.VendorId == currentVendor.Id))
                filterByProductId = model.ProductId;

            //load orders
            var orders = await _orderService.SearchOrdersAsync(storeId: model.StoreId,
                vendorId: model.VendorId,
                productId: filterByProductId,
                warehouseId: model.WarehouseId,
                paymentMethodSystemName: model.PaymentMethodSystemName,
                createdFromUtc: startDateValue,
                createdToUtc: endDateValue,
                osIds: orderStatusIds,
                psIds: paymentStatusIds,
                ssIds: shippingStatusIds,
                billingPhone: model.BillingPhone,
                billingEmail: model.BillingEmail,
                billingLastName: model.BillingLastName,
                billingCountryId: model.BillingCountryId,
                orderNotes: model.OrderNotes);

            //ensure that we at least one order selected
            if (!orders.Any())
            {
                _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Orders.NoOrders"));
                return RedirectToAction("List");
            }

            try
            {
                var bytes = await _exportManager.ExportOrdersToXlsxAsync(orders);
                return File(bytes, MimeTypes.TextXlsx, "orders.xlsx");
            }
            catch (Exception exc)
            {
                await _notificationService.ErrorNotificationAsync(exc);
                return RedirectToAction("List");
            }
        }

        [HttpPost]
        [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)]
        public virtual async Task<IActionResult> ExportExcelSelected(string selectedIds)
        {
            var orders = new List<Order>();
            if (selectedIds != null)
            {
                var ids = selectedIds
                    .Split(_separator, StringSplitOptions.RemoveEmptyEntries)
                    .Select(x => Convert.ToInt32(x))
                    .ToArray();
                orders.AddRange(await (await _orderService.GetOrdersByIdsAsync(ids)).WhereAwait(HasAccessToOrderAsync).ToListAsync());
            }

            try
            {
                var bytes = await _exportManager.ExportOrdersToXlsxAsync(orders);
                return File(bytes, MimeTypes.TextXlsx, "orders.xlsx");
            }
            catch (Exception exc)
            {
                await _notificationService.ErrorNotificationAsync(exc);
                return RedirectToAction("List");
            }
        }

        [HttpPost]
        [CheckPermission(StandardPermission.Orders.ORDERS_IMPORT_EXPORT)]
        public virtual async Task<IActionResult> ImportFromXlsx(IFormFile importexcelfile)
        {
            //a vendor cannot import orders
            if (await _workContext.GetCurrentVendorAsync() != null)
                return AccessDeniedView();

            try
            {
                if (importexcelfile != null && importexcelfile.Length > 0)
                {
                    await _importManager.ImportOrdersFromXlsxAsync(importexcelfile.OpenReadStream());
                }
                else
                {
                    _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Admin.Common.UploadFile"));
                    return RedirectToAction("List");
                }

                _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Orders.Imported"));

                return RedirectToAction("List");
            }
            catch (Exception exc)
            {
                await _notificationService.ErrorNotificationAsync(exc);
                return RedirectToAction("List");
            }
        }

        #endregion

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> SendOrderNotification(int orderId, string message)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(message))
                {
                    return Json(new { success = false, message = "Az üzenet nem lehet üres" });
                }

                var orderDto = await _dbContext.OrderDtos.GetByIdAsync(orderId, true);

                await _sendToClient.SendMeasuringNotification(message, orderDto);
                return Json(new { success = true, message = "Üzenet sikeresen elküldve" });
            }
            catch (Exception ex)
            {
                _logger.Error($"Error sending notification for order {orderId}", ex);

                return Json(new { success = false, message = $"Hiba történt: {ex.Message}" });
            }
        }


        [HttpPost]
    [ValidateAntiForgeryToken]
    [CheckPermission(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE)]
    public async Task<IActionResult> SendOrderEmailToCustomer(int orderId)
    {
        try
        {
            var order = await _orderService.GetOrderByIdAsync(orderId);
            if (order == null)
                return Json(new { success = false, message = "Rendelés nem található" });

            var sentIds = await _fruitBankNotificationService.SendOrderInfoEmailAsync(order);
            var sentCount = sentIds?.Count(id => id > 0) ?? 0;

            if (sentCount > 0)
                return Json(new { success = true, message = $"Email sikeresen elküldve ({sentCount} címzett)" });

            return Json(new { success = false, message = "Az email nem került elküldésre. Ellenőrizze az email sablont és az ügyfél email címét." });
        }
        catch (Exception ex)
        {
            _logger.Error($"SendOrderEmailToCustomer error – orderId={orderId}: {ex.Message}", ex);
            return Json(new { success = false, message = $"Hiba: {ex.Message}" });
        }
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    [CheckPermission(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE)]
    public async Task<IActionResult> FruitBankAddProductToOrder(int orderId, string productsJson)
        {
            try {
                _logger.Info($"AddProductToOrder - OrderId: {orderId}, ProductsJson: {productsJson}");
                if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE))
                    return Json(new { success = false, message = "Access denied" });

                if (string.IsNullOrEmpty(productsJson))
                    return Json(new { success = false, message = "No products data received" });

                var order = await _orderService.GetOrderByIdAsync(orderId);

                if (order == null || order.Deleted)
                    return Json(new { success = false, message = "Order not found" });

                // Deserialize products
                var products = productsJson.JsonTo<List<AddProductModel>>(); //JsonConvert.DeserializeObject<List<AddProductModel>>(productsJson);

                if (products == null || products.Count == 0)
                    return Json(new { success = false, message = "No products to add" });

                var customer = await _customerService.GetCustomerByIdAsync(order.CustomerId);
                var store = await _storeContext.GetCurrentStoreAsync();
                var admin = await _workContext.GetCurrentCustomerAsync();

                string errorMessage = "";

                var transactionSuccess = await _dbContext.TransactionSafeAsync(async _ =>
                {
                    await AddOrderItemsThenUpdateOrder(order, products, true, customer, store, admin);
                    return true;
                });

                if (transactionSuccess)
                {
                    _logger.Info($"Successfully added {products.Count} products to order {orderId}");

                    //var orderDto = await _dbContext.OrderDtos.GetByIdAsync(order.Id, true);
                    //await _sendToClient.SendMeasuringNotification("Módosult a rendelés, mérjétek újra!", orderDto);

                    return Json(new { success = true, message = "Products added successfully" });
                }
                else
                {
                    return Json(new { success = false, message = errorMessage });
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"Error adding products to order {orderId}, {ex.Message}");
                return Json(new { success = false, message = $"Error: {ex.Message}" });
            }
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [CheckPermission(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE)]
        public async Task<IActionResult> SplitOrder(int orderId, string mode = "audit", string orderItemIds = "")
        {
            try
            {
                _logger.Info($"SplitOrder - OrderId: {orderId}, Mode: {mode}, OrderItemIds: {orderItemIds} - STARTED");

                var order = await _orderService.GetOrderByIdAsync(orderId);
                if (order == null || order.Deleted)
                {
                    _logger.Warning($"SplitOrder - Order {orderId} not found or deleted");
                    return Json(new { success = false, message = "Rendelés nem található" });
                }

                _logger.Info($"SplitOrder - Order {orderId} found, checking access");

                // Check if user has access to this order
                if (!await HasAccessToOrderAsync(order))
                {
                    _logger.Warning($"SplitOrder - No access to order {orderId}");
                    return Json(new { success = false, message = "Nincs jogosultsága ehhez a rendeléshez" });
                }

                _logger.Info($"SplitOrder - Getting OrderDto for order {orderId}");

                var orderDto = await _dbContext.OrderDtos.GetByIdAsync(orderId, true);
                if (orderDto == null)
                {
                    _logger.Warning($"SplitOrder - OrderDto not found for order {orderId}");
                    return Json(new { success = false, message = "OrderDto nem található" });
                }

                // SAFETY CHECK: Don't allow splitting if order is complete/audited
                if (orderDto.MeasuringStatus == MeasuringStatus.Audited)
                {
                    _logger.Warning($"SplitOrder - Cannot split audited order {orderId}");
                    return Json(new
                    {
                        success = false,
                        message = "Ez a rendelés már auditált, nem választható szét!"
                    });
                }

                // REMOVED: NotStarted check - we allow splitting at any stage except Audited
                // Manual mode is always available, audit mode is controlled by the UI

                _logger.Info($"SplitOrder - OrderDto found, separating items. Total items: {orderDto.OrderItemDtos.Count}, MeasuringStatus: {orderDto.MeasuringStatus}");

                List<OrderItemDto> itemsToMove;

                if (mode == "manual")
                {
                    // Manual mode - use provided order item IDs
                    if (string.IsNullOrWhiteSpace(orderItemIds))
                    {
                        _logger.Warning($"SplitOrder - Manual mode selected but no order item IDs provided");
                        return Json(new { success = false, message = "Nem lettek termékek kiválasztva" });
                    }

                    var selectedIds = orderItemIds.Split(',')
                        .Where(id => !string.IsNullOrWhiteSpace(id))
                        .Select(id => int.Parse(id.Trim()))
                        .ToList();

                    if (selectedIds.Count == 0)
                    {
                        _logger.Warning($"SplitOrder - No valid order item IDs provided");
                        return Json(new { success = false, message = "Nem lettek érvényes termékek kiválasztva" });
                    }

                    if (selectedIds.Count == orderDto.OrderItemDtos.Count)
                    {
                        _logger.Warning($"SplitOrder - All items selected for move");
                        return Json(new { success = false, message = "Legalább egy terméknek maradnia kell az eredeti rendelésben" });
                    }

                    itemsToMove = orderDto.OrderItemDtos.Where(oi => selectedIds.Contains(oi.Id)).ToList();

                    _logger.Info($"SplitOrder - Manual mode: {itemsToMove.Count} items selected to move out of {orderDto.OrderItemDtos.Count}");
                }
                else
                {
                    // Audit mode - separate by measuring status (started vs not started)
                    // Items with MeasuringStatus > NotStarted stay in original order
                    // Items with MeasuringStatus = NotStarted move to new order
                    var startedItems = orderDto.OrderItemDtos.Where(oi => oi.MeasuringStatus > MeasuringStatus.NotStarted).ToList();
                    itemsToMove = orderDto.OrderItemDtos.Where(oi => oi.MeasuringStatus == MeasuringStatus.NotStarted).ToList();

                    _logger.Info($"SplitOrder - Audit mode: Started/Audited items: {startedItems.Count}, Not started items: {itemsToMove.Count}");

                    if (itemsToMove.Count == 0)
                    {
                        _logger.Warning($"SplitOrder - No not-started items in order {orderId}");
                        return Json(new
                        {
                            success = false,
                            message = "Nincs nem elindított termék a rendelésben. Szétválasztás nem szükséges."
                        });
                    }

                    if (startedItems.Count == 0)
                    {
                        _logger.Warning($"SplitOrder - All items are not-started in order {orderId}");
                        return Json(new
                        {
                            success = false,
                            message = "Minden termék még nem lett elindítva. Használja a kézi módot."
                        });
                    }
                }

                _logger.Info($"SplitOrder - Getting customer, store, and admin");

                var customer = await _customerService.GetCustomerByIdAsync(order.CustomerId);
                var store = await _storeContext.GetCurrentStoreAsync();
                var admin = await _workContext.GetCurrentCustomerAsync();

                _logger.Info($"SplitOrder - Customer: {customer?.Id}, Store: {store?.Id}, Admin: {admin?.Id}");
                _logger.Info($"SplitOrder - Creating new order");

                // Create new order for items to move
                var newOrder = new Order
                {
                    OrderGuid = Guid.NewGuid(),
                    CustomOrderNumber = "",
                    CustomerId = order.CustomerId,
                    CustomerLanguageId = order.CustomerLanguageId,
                    CustomerTaxDisplayType = order.CustomerTaxDisplayType,
                    CustomerIp = order.CustomerIp,
                    OrderStatus = OrderStatus.Pending,
                    PaymentStatus = PaymentStatus.Pending,
                    ShippingStatus = ShippingStatus.ShippingNotRequired,
                    CreatedOnUtc = DateTime.UtcNow,
                    BillingAddressId = order.BillingAddressId,
                    ShippingAddressId = order.ShippingAddressId,
                    PaymentMethodSystemName = order.PaymentMethodSystemName,
                    CustomerCurrencyCode = order.CustomerCurrencyCode,
                    OrderTotal = 0,
                    OrderSubtotalInclTax = 0,
                    OrderSubtotalExclTax = 0,
                    OrderSubTotalDiscountInclTax = 0,
                    OrderSubTotalDiscountExclTax = 0,
                };

                _logger.Info($"SplitOrder - Inserting new order");
                await _orderService.InsertOrderAsync(newOrder);

                _logger.Info($"SplitOrder - New order inserted with ID: {newOrder.Id}");
                newOrder.CustomOrderNumber = newOrder.Id.ToString();
                await _orderService.UpdateOrderAsync(newOrder);

                // Get original order items
                _logger.Info($"SplitOrder - Getting original order items");
                var originalOrderItems = await _orderService.GetOrderItemsAsync(orderId);
                _logger.Info($"SplitOrder - Found {originalOrderItems.Count} original order items");

                var orderItemsToMove = new List<OrderItem>();

                // Find order items to move based on itemsToMove DTOs
                foreach (var itemDto in itemsToMove)
                {
                    var orderItemToMove = originalOrderItems.FirstOrDefault(oi => oi.Id == itemDto.Id);
                    if (orderItemToMove != null)
                    {
                        orderItemsToMove.Add(orderItemToMove);
                    }
                }

                _logger.Info($"SplitOrder - Found {orderItemsToMove.Count} items to move");

                // Move items to new order
                foreach (var orderItem in orderItemsToMove)
                {
                    _logger.Info($"SplitOrder - Processing order item {orderItem.Id}");

                    var product = await _productService.GetProductByIdAsync(orderItem.ProductId);
                    if (product == null)
                    {
                        _logger.Warning($"Product with ID {orderItem.ProductId} not found during split");
                        continue;
                    }

                    // Create new order item for new order
                    var newOrderItem = new OrderItem
                    {
                        OrderId = newOrder.Id,
                        ProductId = orderItem.ProductId,
                        Quantity = orderItem.Quantity,
                        OrderItemGuid = Guid.NewGuid(),
                        UnitPriceInclTax = orderItem.UnitPriceInclTax,
                        UnitPriceExclTax = orderItem.UnitPriceExclTax,
                        PriceInclTax = orderItem.PriceInclTax,
                        PriceExclTax = orderItem.PriceExclTax,
                        OriginalProductCost = orderItem.OriginalProductCost,
                        AttributeDescription = orderItem.AttributeDescription,
                        AttributesXml = orderItem.AttributesXml,
                        DiscountAmountInclTax = orderItem.DiscountAmountInclTax,
                        DiscountAmountExclTax = orderItem.DiscountAmountExclTax,
                        DownloadCount = 0,
                        IsDownloadActivated = false,
                        LicenseDownloadId = 0,
                        ItemWeight = orderItem.ItemWeight,
                        RentalStartDateUtc = orderItem.RentalStartDateUtc,
                        RentalEndDateUtc = orderItem.RentalEndDateUtc
                    };

                    _logger.Info($"SplitOrder - Inserting new order item for product {orderItem.ProductId}");
                    await _orderService.InsertOrderItemAsync(newOrderItem);

                    // Update new order totals
                    newOrder.OrderSubtotalInclTax += newOrderItem.PriceInclTax;
                    newOrder.OrderSubtotalExclTax += newOrderItem.PriceExclTax;
                    newOrder.OrderTotal += newOrderItem.PriceInclTax;

                    _logger.Info($"SplitOrder - Adjusting inventory for product {orderItem.ProductId}");

                    // Return inventory to stock (from original order)
                    await _productService.AdjustInventoryAsync(
                        product,
                        orderItem.Quantity,
                        orderItem.AttributesXml,
                        $"Returned from split order #{order.Id}"
                    );

                    // Remove from stock (for new order)
                    await _productService.AdjustInventoryAsync(
                        product,
                        -orderItem.Quantity,
                        orderItem.AttributesXml,
                        $"Split to new order #{newOrder.Id}"
                    );

                    _logger.Info($"SplitOrder - Deleting order item {orderItem.Id} from original order");

                    // Delete from original order
                    await _orderService.DeleteOrderItemAsync(orderItem);

                    // Update original order totals
                    order.OrderSubtotalInclTax -= orderItem.PriceInclTax;
                    order.OrderSubtotalExclTax -= orderItem.PriceExclTax;
                    order.OrderTotal -= orderItem.PriceInclTax;
                }

                _logger.Info($"SplitOrder - Updating both orders");

                // Update both orders
                await _orderService.UpdateOrderAsync(newOrder);
                await _orderService.UpdateOrderAsync(order);

                _logger.Info($"SplitOrder - Adding order notes");

                var splitModeText = mode == "manual" ? "kézi kiválasztással" : "mérési státusz alapján";

                // Add notes to both orders
                await InsertOrderNoteAsync(
                    order.Id,
                    false,
                    $"* Rendelés szétválasztva ({splitModeText}). {orderItemsToMove.Count} termék átkerült a #{newOrder.Id} rendelésbe. Művelet végrehajtója: {admin.FirstName} {admin.LastName} (ID: {admin.Id})"
                );

                await InsertOrderNoteAsync(
                    newOrder.Id,
                    false,
                    $"* Új rendelés létrehozva a #{order.Id} rendelés szétválasztásával ({splitModeText}). {orderItemsToMove.Count} termék. Művelet végrehajtója: {admin.FirstName} {admin.LastName} (ID: {admin.Id})"
                );

                _logger.Info($"Order {orderId} split successfully using {mode} mode. New order created: {newOrder.Id}. Moved {orderItemsToMove.Count} items.");

                // Send notifications
                _logger.Info($"SplitOrder - Sending notifications");

                var originalOrderDto = await _dbContext.OrderDtos.GetByIdAsync(order.Id, true);
                var newOrderDto = await _dbContext.OrderDtos.GetByIdAsync(newOrder.Id, true);

                await _sendToClient.SendOrderChanged(originalOrderDto);
                await _sendToClient.SendOrderChanged(newOrderDto);

                _logger.Info($"SplitOrder - COMPLETED SUCCESSFULLY");

                return Json(new
                {
                    success = true,
                    message = "Rendelés sikeresen szétválasztva",
                    newOrderId = newOrder.Id,
                    originalOrderId = order.Id,
                    movedItemsCount = orderItemsToMove.Count
                });
            }
            catch (Exception ex)
            {
                _logger.Error($"Error splitting order {orderId}: {ex.Message}", ex);
                return Json(new { success = false, message = $"Hiba: {ex.Message}" });
            }
        }



        // ═══════════════════════════════════════════════════════════════════
        // FruitBank Order Grid  –  new server-side DataTables endpoint
        // ═══════════════════════════════════════════════════════════════════

        /// <summary>
        /// Returns the new FruitBank order list view (replaces the default NopCommerce grid).
        /// </summary>
        [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)]
        public async Task<IActionResult> NewList(
            List<int> orderStatuses = null,
            List<int> paymentStatuses = null,
            List<int> shippingStatuses = null)
        {
            var model = await _orderModelFactory.PrepareOrderSearchModelAsync(new OrderSearchModelExtended
            {
                OrderStatusIds = orderStatuses,
                PaymentStatusIds = paymentStatuses,
                ShippingStatusIds = shippingStatuses,
                Length = 50,
                AvailablePageSizes = "20,50,100,500",
                SortColumn = "Id",
                SortColumnDirection = "desc",
            });
            model.SetGridSort("Id", "desc");
            model.SetGridPageSize(50, "20,50,100,500");

            return View(
                "~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/FruitBankOrderList.cshtml",
                model);
        }

        /// <summary>
        /// DataTables server-side endpoint for the FruitBank order grid.
        /// Handles NopCommerce base filters + FruitBank-specific filters + per-column search + sort + pagination.
        /// </summary>
        [HttpPost]
        [CheckPermission(StandardPermission.Orders.ORDERS_VIEW)]
        public async Task<IActionResult> FruitBankOrderList()
        {
            var swTotal = System.Diagnostics.Stopwatch.StartNew();
            var sw = System.Diagnostics.Stopwatch.StartNew();

            // ── 1. Parse DataTables protocol params ────────────────────────
            _ = int.TryParse(Request.Form["draw"].FirstOrDefault(), out int draw); draw = Math.Max(draw, 1);
            _ = int.TryParse(Request.Form["start"].FirstOrDefault(), out int start); start = Math.Max(start, 0);
            _ = int.TryParse(Request.Form["length"].FirstOrDefault(), out int length); length = length < 1 ? 50 : Math.Min(length, 500);

            // Sort column
            _ = int.TryParse(Request.Form["order[0][column]"].FirstOrDefault(), out int sortColIdx);
            var sortDir = Request.Form["order[0][dir]"].FirstOrDefault() ?? "desc";
            var sortColName = Request.Form[$"columns[{sortColIdx}][data]"].FirstOrDefault() ?? "Id";

            // Per-column search values keyed by column data-field name
            var colSearch = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            for (int ci = 0; Request.Form.ContainsKey($"columns[{ci}][data]"); ci++)
            {
                var cData = Request.Form[$"columns[{ci}][data]"].FirstOrDefault();
                var cVal = Request.Form[$"columns[{ci}][search][value]"].FirstOrDefault();
                if (!string.IsNullOrWhiteSpace(cData) && !string.IsNullOrWhiteSpace(cVal))
                    colSearch[cData] = cVal.Trim();
            }

            // ── 2. Parse custom filter params ─────────────────────────────
            DateTime? startDate = null, endDate = null;
            if (DateTime.TryParse(Request.Form["StartDate"].FirstOrDefault(), out var sd)) startDate = sd;
            if (DateTime.TryParse(Request.Form["EndDate"].FirstOrDefault(), out var ed)) endDate = ed;

            var orderStatusIds = Request.Form["OrderStatusIds"]
                .Where(v => int.TryParse(v, out _)).Select(int.Parse).Where(id => id > 0).ToList();
            var paymentStatusIds = Request.Form["PaymentStatusIds"]
                .Where(v => int.TryParse(v, out _)).Select(int.Parse).Where(id => id > 0).ToList();
            var shippingStatusIds = Request.Form["ShippingStatusIds"]
                .Where(v => int.TryParse(v, out _)).Select(int.Parse).Where(id => id > 0).ToList();

            var billingCompany = Request.Form["BillingCompany"].FirstOrDefault();

            bool? isMeasurableFilter = null;
            var imStr = Request.Form["IsMeasurable"].FirstOrDefault();
            if (imStr == "true") isMeasurableFilter = true;
            if (imStr == "false") isMeasurableFilter = false;

            bool? hasInnvoiceFilter = null;
            var hiStr = Request.Form["HasInnvoiceTechId"].FirstOrDefault();
            if (hiStr == "true") hasInnvoiceFilter = true;
            if (hiStr == "false") hasInnvoiceFilter = false;

            _logger.Info($"[PERF] FruitBankOrderList – params parsed in {sw.ElapsedMilliseconds} ms");
            sw.Restart();

            // ── 3. Direct lean query – bypasses the factory N+1 problem ───────
            // OrderDtos already has all FruitBank fields + Customer + GenericAttributes.
            // LinqToDB LoadWith batches relations into 1 query each – 3 queries total
            // regardless of row count, vs the factory’s ~5 queries per row.
            int? filterCustomerId = int.TryParse(billingCompany, out var cid) && cid > 0 ? cid : null;

            // UTC conversion for date filters (same logic as base factory)
            var currentTz = await _dateTimeHelper.GetCurrentTimeZoneAsync();
            DateTime? startUtc = startDate.HasValue ? (DateTime?)_dateTimeHelper.ConvertToUtcTime(startDate.Value, currentTz) : null;
            DateTime? endUtc = endDate.HasValue ? (DateTime?)_dateTimeHelper.ConvertToUtcTime(endDate.Value, currentTz).AddDays(1) : null;

            var query = _dbContext.OrderDtos
                .GetAll(true)                          // loads GenericAttributes in 1 batch query                       
                .Where(o => !o.Deleted);

            if (startUtc.HasValue) query = query.Where(o => o.CreatedOnUtc >= startUtc.Value);
            if (endUtc.HasValue) query = query.Where(o => o.CreatedOnUtc <= endUtc.Value);
            if (filterCustomerId.HasValue) query = query.Where(o => o.CustomerId == filterCustomerId.Value);
            if (orderStatusIds.Any()) query = query.Where(o => orderStatusIds.Contains(o.OrderStatusId));
            if (paymentStatusIds.Any()) query = query.Where(o => paymentStatusIds.Contains(o.PaymentStatusId));
            if (shippingStatusIds.Any()) query = query.Where(o => shippingStatusIds.Contains(o.ShippingStatusId));

            // Apply sort at DB level
            bool asc = sortDir == "asc";
            query = sortColName.ToLowerInvariant() switch
            {
                "customordernumber" => asc ? query.OrderBy(o => o.CustomOrderNumber) : query.OrderByDescending(o => o.CustomOrderNumber),
                "createdon" => asc ? query.OrderBy(o => o.CreatedOnUtc) : query.OrderByDescending(o => o.CreatedOnUtc),
                "dateofreceipt" => asc ? query.OrderBy(o => o.DateOfReceipt) : query.OrderByDescending(o => o.DateOfReceipt),
                "orderstatusid" => asc ? query.OrderBy(o => o.OrderStatusId) : query.OrderByDescending(o => o.OrderStatusId),
                "measuringstatus" => asc ? query.OrderBy(o => o.MeasuringStatus) : query.OrderByDescending(o => o.MeasuringStatus),
                "customercompany" => asc ? query.OrderBy(o => o.CustomerId) : query.OrderByDescending(o => o.CustomerId),
                _ => query.OrderByDescending(o => o.Id)
            };

            // Per-column DB-mappable filters
            if (colSearch.TryGetValue("CustomOrderNumber", out var coNum) && !string.IsNullOrEmpty(coNum))
                query = query.Where(o => o.CustomOrderNumber.Contains(coNum));
            if (colSearch.TryGetValue("OrderStatusId", out var osColStr) && int.TryParse(osColStr, out var osColId))
                query = query.Where(o => o.OrderStatusId == osColId);
            if (colSearch.TryGetValue("MeasuringStatus", out var msColStr) && int.TryParse(msColStr, out var msColId))
                query = query.Where(o => (int)o.MeasuringStatus == msColId);
            // IsMeasurable: computed from OrderItemDtos – pre-query the OrderItem table
            // to get order IDs where any item belongs to a measurable product, then filter SQL
            var isMeasurableColVal = colSearch.TryGetValue("IsMeasurable", out var imcs) ? imcs : null;
            bool? effectiveIsMeasurable = isMeasurableFilter;
            if (isMeasurableColVal != null && bool.TryParse(isMeasurableColVal, out var imcb))
                effectiveIsMeasurable = imcb;

            if (effectiveIsMeasurable.HasValue)
            {
                // Get all order IDs where any item has a measurable product
                var measurableOrderIds = await _dbContext.OrderItemDtos
                    .GetAll(false)
                    .Where(oi => oi.ProductDto != null && oi.ProductDto.IsMeasurable)
                    .Select(oi => oi.OrderId)
                    .Distinct()
                    .ToListAsync();

                query = effectiveIsMeasurable.Value
                    ? query.Where(o => measurableOrderIds.Contains(o.Id))
                    : query.Where(o => !measurableOrderIds.Contains(o.Id));

                _logger.Info($"[PERF] FruitBankOrderList – IsMeasurable pre-query: {measurableOrderIds.Count} measurable order IDs");
            }

            // CustomerCompany column search: pre-query Customer table for matching IDs
            if (colSearch.TryGetValue("CustomerCompany", out var ccColVal) && !string.IsNullOrEmpty(ccColVal))
            {
                var matchingCustomerIds = await _dbContext.Customers.Table
                    .Where(c => c.Company.Contains(ccColVal) ||
                                (c.FirstName + " " + c.LastName).Contains(ccColVal))
                    .Select(c => c.Id)
                    .ToListAsync();

                query = query.Where(o => matchingCustomerIds.Contains(o.CustomerId));
                _logger.Info($"[PERF] FruitBankOrderList – CustomerCompany pre-query: {matchingCustomerIds.Count} matching customers");
            }

            // COUNT – runs as a simple SELECT COUNT(*) against the filtered set
            int total;
            try { total = await query.CountAsync(); }
            catch (Exception ex)
            {
                _logger.Error($"FruitBankOrderList – count error: {ex.Message}", ex);
                return Json(new { draw, recordsTotal = 0, recordsFiltered = 0, data = Array.Empty<object>() });
            }

            _logger.Info($"[PERF] FruitBankOrderList – COUNT {sw.ElapsedMilliseconds} ms  |  total: {total}");
            sw.Restart();

            // Step 1: get just the IDs for the current page (plain SQL, no relations)
            List<int> pageIds;
            try
            {
                pageIds = await query.Skip(start).Take(length).Select(o => o.Id).ToListAsync();
            }
            catch (Exception ex)
            {
                _logger.Error($"FruitBankOrderList – page IDs query error: {ex.Message}", ex);
                return Json(new { draw, recordsTotal = 0, recordsFiltered = 0, data = Array.Empty<object>() });
            }

            _logger.Info($"[PERF] FruitBankOrderList – page IDs (Skip+Take) {sw.ElapsedMilliseconds} ms  |  ids: {pageIds.Count}");
            sw.Restart();

            // Step 2: reload those ~50 rows with only the relations we need.
            // LoadWith works here because it’s applied to the base table query, not a filtered IQueryable.
            List<OrderDto> rows;
            try
            {
                // GetAllByIds(ids, false) uses GetAll(false) which has LoadWith(GenericAttributes) baked in.
                // LoadWith on a chained IQueryable is not supported by LinqToDB.
                rows = await _dbContext.OrderDtos
                    .GetAllByIds(pageIds, true)
                    .ToListAsync();

                // Re-sort to match the original query order (IN clause doesn’t guarantee order)
                rows = pageIds
                .Select(id => rows.FirstOrDefault(r => r.Id == id))
                .Where(r => r != null)
                .ToList();
            }
            catch (Exception ex)
            {
                _logger.Error($"FruitBankOrderList – relations query error: {ex.Message}", ex);
                return Json(new { draw, recordsTotal = 0, recordsFiltered = 0, data = Array.Empty<object>() });
            }

            _logger.Info($"[PERF] FruitBankOrderList – relations load (GetAll+LoadWith) {sw.ElapsedMilliseconds} ms  |  rows: {rows.Count}");
            sw.Restart();

            var userTz = currentTz;

            // ── 4. Map to lightweight DTO ──────────────────────────────────
            static string MeasuringStatusLabel(MeasuringStatus s) => s switch
            {
                MeasuringStatus.NotStarted => "Nincs elindítva",
                MeasuringStatus.Started => "Folyamatban",
                MeasuringStatus.Finnished => "Mérve",
                MeasuringStatus.Audited => "Lezárva",
                _ => s.ToString()
            };
            static string OrderStatusLabel(int id) => id switch
            {
                10 => "Függőben",
                20 => "Feldolgozás",
                30 => "Teljesítve",
                40 => "Törölve",
                _ => id.ToString()
            };
            static string PaymentStatusLabel(int id) => id switch
            {
                10 => "Fizetésre vár",
                20 => "Félig fizetve",
                30 => "Fizetve",
                35 => "Túlfizetve",
                40 => "Visszatérítve",
                _ => id.ToString()
            };
            static string ShippingStatusLabel(int id) => id switch
            {
                10 => "Szállítás nincs",
                20 => "Nincs kiszállítva",
                25 => "Részben kiszállítva",
                30 => "Kiszállítva",
                _ => id.ToString()
            };

            var dtos = rows.Select(o =>
            {
                var ga = o.GenericAttributes;
                var dateOfReceipt = ga?.FirstOrDefault(a => a.Key == "DateOfReceipt")?.Value is string dv && DateTime.TryParse(dv, out var dp) ? dp : (DateTime?)null;
                var innvoiceTechId = ga?.FirstOrDefault(a => a.Key == "InnVoiceOrderTechId")?.Value;
                var company = o.Customer != null
                    ? $"{o.Customer.Company} {o.Customer.FirstName}_{o.Customer.LastName}".Trim()
                    : string.Empty;

                return new Nop.Plugin.Misc.FruitBankPlugin.Models.Orders.FruitBankOrderRowDto
                {
                    Id = o.Id,
                    CustomOrderNumber = o.CustomOrderNumber,
                    CustomerCompany = company,
                    CustomerId = o.CustomerId,
                    InnvoiceTechId = innvoiceTechId,
                    IsAllOrderItemAvgWeightValid = o.IsAllOrderItemAvgWeightValid,
                    IsMeasurable = o.IsMeasurable,
                    MeasuringStatus = (int)o.MeasuringStatus,
                    MeasuringStatusString = MeasuringStatusLabel(o.MeasuringStatus),
                    DateOfReceipt = dateOfReceipt,
                    OrderStatusId = o.OrderStatusId,
                    OrderStatus = OrderStatusLabel(o.OrderStatusId),
                    PaymentStatusId = o.PaymentStatusId,
                    PaymentStatus = PaymentStatusLabel(o.PaymentStatusId),
                    ShippingStatusId = o.ShippingStatusId,
                    ShippingStatus = ShippingStatusLabel(o.ShippingStatusId),
                    StoreName = string.Empty,   // not needed in grid
                    CreatedOn = TimeZoneInfo.ConvertTimeFromUtc(o.CreatedOnUtc, userTz),
                    OrderTotal = !o.IsComplete && o.IsMeasurable
                                                       ? "kalkuláció alatt..."
                                                       : $"{o.OrderTotal:N0} Ft"
                };
            }).ToList();

            _logger.Info($"[PERF] FruitBankOrderList – DTO mapping {sw.ElapsedMilliseconds} ms");
            sw.Restart();

            // InnVoice filter is post-query (it’s in GenericAttributes, not a plain column)
            if (hasInnvoiceFilter.HasValue)
                dtos = hasInnvoiceFilter.Value
                    ? dtos.Where(o => !string.IsNullOrEmpty(o.InnvoiceTechId)).ToList()
                    : dtos.Where(o => string.IsNullOrEmpty(o.InnvoiceTechId)).ToList();

            // InnVoice column-header filter (post-query: stored in GenericAttributes)
            if (colSearch.TryGetValue("InnvoiceTechId", out var innColVal))
                dtos = innColVal == "has"  ? dtos.Where(o => !string.IsNullOrEmpty(o.InnvoiceTechId)).ToList()
                     : innColVal == "none" ? dtos.Where(o =>  string.IsNullOrEmpty(o.InnvoiceTechId)).ToList()
                     : dtos;

            var result = Json(new { draw, recordsTotal = total, recordsFiltered = total, data = dtos });

            _logger.Info($"[PERF] FruitBankOrderList – JSON serialize {sw.ElapsedMilliseconds} ms");
            _logger.Info($"[PERF] FruitBankOrderList – TOTAL {swTotal.ElapsedMilliseconds} ms  |  page: {dtos.Count}");

            return result;
        }

        

        /// <summary>
        /// Inline-edit save endpoint. Currently supports DateOfReceipt.
        /// </summary>
        [HttpPost]
        [CheckPermission(StandardPermission.Orders.ORDERS_CREATE_EDIT_DELETE)]
        public async Task<IActionResult> UpdateOrderField(int orderId, string field, string value)
        {
            try
            {
                var order = await _orderService.GetOrderByIdAsync(orderId);
                if (order == null)
                    return Json(new { success = false, error = "Rendelés nem található" });

                switch (field?.ToUpperInvariant())
                {
                    case "DATEOFRECEIPT":
                        var dateOdReceiptDateTime = DateTime.TryParse(value, out var dp);
                        if (string.IsNullOrWhiteSpace(value))
                        {
                            //await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, string>(order.Id, nameof(IOrderDto.DateOfReceipt), null, _storeContext.GetCurrentStore().Id);
                            await _fruitBankAttributeService.DeleteGenericAttributeAsync<Order>(order.Id, nameof(IOrderDto.DateOfReceipt));
                            //await _genericAttributeService.SaveAttributeAsync<DateTime?>(order, "DateOfReceipt", null);
                            return Json(new { success = true, displayValue = (string)null });
                        }
                        if (DateTime.TryParse(value, out var newDate))
                        {
                            // Store in the same format that NopCommerce's SaveAttributeAsync<DateTime?> uses (MM/dd/yyyy HH:mm:ss invariant)
                            // so OrderDto deserialization in the Blazor app doesn't break.
                            var formattedValue = newDate.ToString("MM/dd/yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
                            await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Order, string>(order.Id, nameof(IOrderDto.DateOfReceipt), formattedValue, _storeContext.GetCurrentStore().Id);
                            return Json(new { success = true, displayValue = newDate.ToString("yyyy. MM. dd. HH:mm") });
                        }
                        return Json(new { success = false, error = "Érvénytelen dátum formátum" });

                    default:
                        return Json(new { success = false, error = $"Ismeretlen mező: {field}" });
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"UpdateOrderField error – orderId={orderId} field={field}: {ex.Message}", ex);
                return Json(new { success = false, error = ex.Message });
            }
        }

    }
}


