using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Services.SignalRs;
using DevExpress.Blazor;
using FruitBank.Common.Dtos;
using FruitBank.Common.Entities;
using FruitBank.Common.Enums;
using FruitBank.Common.Models;
using FruitBank.Common.SignalRs;
using FruitBankHybrid.Shared.Extensions;
using FruitBankHybrid.Shared.Models;
using FruitBankHybrid.Shared.Services;
using FruitBankHybrid.Shared.Services.Loggers;
using FruitBankHybrid.Shared.Services.SignalRs;
using Mango.Nop.Core.Extensions;
using Microsoft.AspNetCore.Components;
using Nop.Core.Domain.Orders;

namespace FruitBankHybrid.Shared.Pages
{
    public partial class MeasuringOut : ComponentBase, IDisposable
    {
        private readonly Lock _lock = new Lock();

        [Inject] public required IEnumerable<IAcLogWriterClientBase> LogWriters { get; set; }
        [Inject] public required FruitBankSignalRClient FruitBankSignalRClient { get; set; }
        [Inject] public required NavigationManager NavManager{ get; set; }
        [Inject] private IDialogService DialogService { get; set; } = null!;
        [Inject] public required LoggedInModel LoggedInModel { get; set; }

        private LoggerClient _logger = null!;
        private string _errorText;

        private enum MeasuringTab
        {
            DailyTasks = 0,
            Measuring = 1
        }

        private bool _enablePalletItems = true;
        private int _lastDaysCount = 1;
        private MeasuringTab _activeTab = MeasuringTab.DailyTasks;
        private IEnumerable<MeasuringStatus> _statusFilter = [MeasuringStatus.NotStarted, MeasuringStatus.Started, MeasuringStatus.Finnished];
        public bool HasMeasuringAccess;
        public bool LoadingPanelVisible { get; set; } = true;
        public bool IsAllOrderItemPalletAudited => SelectedOrder?.IsAllOrderItemAudited ?? false;
        private DateTime SelectedDate;
        private List<OrderDto> SelectedDayOrders { get; set; } = null!;
        private OrderDto? SelectedOrder { get; set; }
        //private OrderItemDto? SelectedOrderItem { get; set; }

        private List<MeasuringDateSelectorModel> _measuringDates = null!;

        private IReadOnlyList<OrderDto> FilteredOrders => SelectedDayOrders is null
            ? []
            : _statusFilter.Any()
                ? SelectedDayOrders.Where(o => _statusFilter.Contains(o.MeasuringStatus)).ToList()
                : SelectedDayOrders;

        protected override async Task OnInitializedAsync()
        {
            LoadingPanelVisible = true;
            _logger = new LoggerClient<MeasuringOut>(LogWriters.ToArray());
            _logger.Info("OnInitializedAsync");

            FruitBankSignalRClient.OnMessageReceived += SignalRClientOnMessageReceived;

            await RefreshOrdersFromDb(DateTime.Now, _lastDaysCount);
            await base.OnInitializedAsync();
        }

        private async Task SignalRClientOnMessageReceived(int messageTag, SignalResponseDataMessage? responseDataMessage)
        {
            if (!LoggedInModel.IsLoggedIn) return;

            if (messageTag != SignalRTags.SendOrderItemDeleted && messageTag != SignalRTags.SendOrderChanged && messageTag != SignalRTags.SendOrderItemChanged &&
                messageTag != SignalRTags.SendOrderItemPalletChanged && messageTag != SignalRTags.SendProductChanged) return;

            _logger.DebugConditional($"MeasuringOut.SignalRClientOnMessageReceived received. {responseDataMessage}");

            try
            {
                OrderDto? orderDto;
                OrderItem? orderItem;
                OrderItemDto? orderItemDto;

                switch (messageTag)
                {
                    case SignalRTags.SendOrderItemDeleted:
                        orderItem = responseDataMessage?.GetResponseData<OrderItem>();
                        if (orderItem == null) break;

                        lock (_lock)
                        {
                            orderDto = SelectedDayOrders.FirstOrDefault(o => o.OrderItemDtos.Any(oi => oi.Id == orderItem.Id));
                            orderDto?.OrderItemDtos.RemoveAll(oi => oi.Id == orderItem.Id);
                        }

                        await InvokeAsync(StateHasChanged);
                        return;
                    case SignalRTags.SendOrderChanged:
                        orderDto = responseDataMessage?.GetResponseData<OrderDto>();
                        if (orderDto == null) break;

                        if (orderDto.DateOfReceipt == null) return;

                        lock (_lock)
                        {
                            if (_measuringDates.All(x => x.DateTime.Date != orderDto.DateOfReceipt.Value.Date))
                                _measuringDates.Add(new MeasuringDateSelectorModel(orderDto.Id, orderDto.DateOfReceipt.Value.Date, orderDto.IsMeasured));

                            if (SelectedDate != orderDto.DateOfReceipt.Value.Date) return;

                            //Elég lenne ez is, csak a CopyTo a Collection - ökben lévő elemeket hozzáfűzi és duplikálva lesznek... -J.
                            if (SelectedOrder?.Id == orderDto.Id) orderDto.CopyTo(SelectedOrder);
                            else SelectedDayOrders.UpdateCollection(orderDto, false);

                            //var selectedOrderId = SelectedOrder?.Id;
                            //SelectedDayOrders.UpdateCollection(orderDto, false);

                            //if (selectedOrderId.GetValueOrDefault(-1) == orderDto.Id) SelectedOrder = orderDto;
                        }

                        await InvokeAsync(StateHasChanged);
                        return;
                    case SignalRTags.SendOrderItemChanged:
                        orderItemDto = responseDataMessage?.GetResponseData<OrderItemDto>();
                        if (orderItemDto == null) break;

                        lock (_lock)
                        {
                            var localOrderDto = SelectedDayOrders.FirstOrDefault(o => o.OrderItemDtos.Any(oi => oi.Id == orderItemDto.Id));
                            var localOrderItemDto = localOrderDto?.OrderItemDtos.FirstOrDefault(x => x.Id == orderItemDto.Id);

                            if (localOrderItemDto == null) return;

                            //orderItemDto.OrderDto = localOrderDto!;

                            localOrderItemDto.Quantity = orderItemDto.Quantity;
                            localOrderItemDto.GenericAttributes.UpdateBaseEntityCollection(orderItemDto.GenericAttributes, false);
                        }

                        await InvokeAsync(StateHasChanged);
                        return;
                    case SignalRTags.SendOrderItemPalletChanged:
                        var orderItemPallet = responseDataMessage?.GetResponseData<OrderItemPallet>();
                        if (orderItemPallet == null) break;

                        lock (_lock)
                        {
                            var orderItemDtos = SelectedDayOrders.FirstOrDefault(x => x.OrderItemDtos.Any(oi => oi.Id == orderItemPallet.OrderItemId))?.OrderItemDtos;

                            orderItemDto = orderItemDtos?.FirstOrDefault(oi => oi.Id == orderItemPallet.OrderItemId);
                            if (orderItemDto == null) return;

                            orderItemPallet.OrderItemDto = orderItemDto;
                            var orderItemPalletsCount = orderItemDto.OrderItemPallets.Count;

                            if (orderItemDto.OrderItemPallets[orderItemPalletsCount - 1].Id == 0) orderItemDto.OrderItemPallets.Insert(orderItemPalletsCount - 1, orderItemPallet);
                            else orderItemDto.OrderItemPallets.UpdateCollection(orderItemPallet, false);
                        }

                        await InvokeAsync(StateHasChanged);
                        return;
                    case SignalRTags.SendProductChanged:
                        var productDto = responseDataMessage?.GetResponseData<ProductDto>();
                        if (productDto == null) break;

                        await InvokeAsync(StateHasChanged);
                        return;
                }

                _logger.Error($"MeasuringOut.SignalRClientOnMessageReceived message == null; messageTag: {messageTag}");
            }
            catch (Exception ex)
            {
                _logger.Error($"MeasuringOut.SignalRClientOnMessageReceived ERROR; messageTag: {messageTag}", ex);
            }
        }

        private async Task RefreshOrdersFromDb(DateTime dateTime, int lastDaysCount)
        {
            LoadingPanelVisible = true;

            _lastDaysCount = lastDaysCount;
            SelectedDate = dateTime;
            
            var orders = (await FruitBankSignalRClient.GetPendingOrderDtosForMeasuring(_lastDaysCount) ?? []).Where(o => o.HasMeasuringAccess(LoggedInModel.CustomerDto?.Id, LoggedInModel.IsRevisor)).OrderBy(o => o.DateOfReceipt).ToList();

            lock (_lock)
            {
                _measuringDates = orders.Select(order => new MeasuringDateSelectorModel(order.Id, order.DateOfReceiptOrCreated, order.IsMeasured)).ToList();

                SelectedDayOrders = orders.Where(order => MeasurementService.DaysEqual(order.DateOfReceiptOrCreated, dateTime)).OrderBy(x => x.DateOfReceipt).ToList();

                foreach (var orderDto in SelectedDayOrders) PrepareOrderItemDtos(orderDto);

                SelectedOrder = LoggedInModel.IsRevisor
                    ? SelectedDayOrders.FirstOrDefault(o => o is { IsComplete: false, IsMeasured: true })
                    : SelectedDayOrders.FirstOrDefault(o => o.MeasurementOwnerId == 0 || (o.MeasurementOwnerId == (LoggedInModel.CustomerDto?.Id ?? 0) && !o.IsMeasured));

                SelectedOrder ??= SelectedDayOrders.FirstOrDefault();
            }

            LoadingPanelVisible = false;
        }

        private async Task OnMeasuringDateChanged(DateTime selectedDateTime) => await RefreshOrdersFromDb(selectedDateTime, _lastDaysCount);

        private void OnCustomDisabledMeasuringDate(CalendarCustomDisabledDateEventArgs args)
            => MeasurementService.OnCustomDisabledDate(args, _measuringDates);

        private string GetMeasuringDateCssClassNames(DateTime date)
            => MeasurementService.GetShippingDateCssClassNames(date, _measuringDates);

        private string GetOrderItemPalletsCssClassNames(string fieldName, OrderItemPallet orderItemPallet)
            => MeasurementService.GetCustomItemPalletsCssClassNames(fieldName, orderItemPallet, orderItemPallet.OrderItemDto!.IsMeasurable);

        private bool IsOrderItemPalletMeasuredAndValid(OrderItemPallet orderItemPallet)
            => MeasurementService.IsCustomItemPalletMeasuredAndValid(orderItemPallet, orderItemPallet.OrderItemDto!.IsMeasurable);

        private async Task OnSelectedOrderChanged(SelectedDataItemChangedEventArgs<OrderDto> eventArgs)
        {
            var orderDto = eventArgs.DataItem;
            if (orderDto != null && !LoadingPanelVisible)
                await RefreshOrderStatusFromDbAsync(orderDto);

            await ApplyMeasuringAccessAsync(orderDto);
        }

        private Task OnOrderItemPalletValueChanged(OrderItemPallet orderItemPallet, OrderItemDto selectedOrderItemDto)
        {
            StateHasChanged();
            return Task.CompletedTask;
        }

        private async Task OnOrderItemAudited(OrderItemPallet? orderItemPallet, OrderItemDto selectedOrderItemDto)
        {
            if (orderItemPallet == null) return;

            _enablePalletItems = false;

            await InvokeAsync(StateHasChanged);
            await OnOrderItemPalletSaved(orderItemPallet, selectedOrderItemDto);
        }

        private async Task OnOrderItemSaveOrAuditClick(OrderItemPallet? orderItemPallet, OrderItemDto selectedOrderItemDto)
        {
            if (orderItemPallet != null)
            {
                _enablePalletItems = false;
                await InvokeAsync(StateHasChanged);
            }
        }

        private async Task OnOrderItemPalletSaved(OrderItemPallet? orderItemPallet, OrderItemDto selectedOrderItemDto)
        {
            if (orderItemPallet != null)
            {
                orderItemPallet.OrderItemDto = selectedOrderItemDto;

                selectedOrderItemDto.OrderItemPallets.UpdateCollection(orderItemPallet, false);
                //MeasuringValuesHelper.SetShippingItemTotalMeasuringValues(SelectedShippingItem);
            }
            else await DialogService.ShowMessageBoxAsync("Hiba", "Adatok mentése sikertelen volt, ellenőrizze a mérés adatait!", MessageBoxRenderStyle.Danger);

            _enablePalletItems = true;
            await InvokeAsync(StateHasChanged);
        }

        private Task AddNewPalletItemClick(OrderItemDto selectedOrderItemDto)
        {
            if (selectedOrderItemDto.OrderItemPallets[^1].Id > 0)
            {
                selectedOrderItemDto.OrderItemPallets.Add(MeasurementService.CreateNewOrderItemPallet(selectedOrderItemDto, LoggedInModel.CustomerDto));
                StateHasChanged();
            }

            return Task.CompletedTask;
        }

        private Task LastPalletItemDeleteClick(OrderItemDto selectedOrderItemDto)
        {
            if (selectedOrderItemDto.OrderItemPallets.Count <= 1) return Task.CompletedTask;

            var lastPalletItem = selectedOrderItemDto.OrderItemPallets[^1];

            if (lastPalletItem.Id > 0) return Task.CompletedTask;
            selectedOrderItemDto.OrderItemPallets.Remove(lastPalletItem);

            StateHasChanged();
            return Task.CompletedTask;
        }

        private async Task OnOrdersRefreshClick()
        {
            await RefreshOrdersFromDb(SelectedDayOrders.FirstOrDefault()?.DateOfReceiptOrCreated ?? DateTime.Now, _lastDaysCount);
            StateHasChanged();
        }

        private async Task OnStartMeasuringClick()
        {
            if (SelectedOrder == null || !LoggedInModel.IsLoggedIn || SelectedOrder.MeasurementOwnerId != 0) return;
            
            var confirmed = !LoggedInModel.IsRevisor || await DialogService.ShowConfirmBoxAsync("Megerősítés", "Ha elindítja a mérést, senki más nem tudja befelyezni! Biztoan folytatja?", MessageBoxRenderStyle.Info);
            if (!confirmed) return;

            var responseOrderDto = await FruitBankSignalRClient.StartMeasuring(SelectedOrder.Id, LoggedInModel.CustomerDto!.Id);
            if (responseOrderDto != null)
            {
                PrepareOrderItemDtos(responseOrderDto);

                //SelectedOrder.GenericAttributes.UpdateBaseEntityCollection(responseOrderDto.GenericAttributes, false);
                SelectedDayOrders.UpdateCollection(responseOrderDto, false);
                SelectedOrder = responseOrderDto;
            }
            else await DialogService.ShowMessageBoxAsync("Hiba", "Nem sikerült a mérést elindítani!", MessageBoxRenderStyle.Danger);

            StateHasChanged();
        }

        private async Task OnMeasuringAuditorClick()
        {
            if (SelectedOrder != null && LoggedInModel.IsRevisor && SelectedOrder.IsMeasured && SelectedOrder.OrderStatus != OrderStatus.Complete)
            {
                if (await DialogService.ShowConfirmBoxAsync("Megerősítés", "Biztoan jóváhagyja a rendelést? Jóváhagyás után a mérések nem módosíthatóak!", MessageBoxRenderStyle.Info))
                {
                    var responseOrderDto = await FruitBankSignalRClient.SetOrderStatusToComplete(SelectedOrder.Id, LoggedInModel.CustomerDto!.Id);
                    if (responseOrderDto != null)
                    {
                        PrepareOrderItemDtos(responseOrderDto);

                        SelectedDayOrders.UpdateCollection(responseOrderDto, false);
                        SelectedOrder = responseOrderDto;

                        //await DialogService.ShowMessageBoxAsync("Információ", "Adatok mentése sikeres volt!", MessageBoxRenderStyle.Info);
                    }
                    else await DialogService.ShowMessageBoxAsync("Hiba", "Rendelés jóváhagyása sikertelen volt, ellenőrizze a mérés adatait!", MessageBoxRenderStyle.Danger);

                    StateHasChanged();
                }
            }
        }

        private void PrepareOrderItemDtos(OrderDto orderDto)
        {
            foreach (var orderItemDto in orderDto.OrderItemDtos)
            {
                orderItemDto.OrderDto = orderDto;

                if (orderItemDto.OrderItemPallets.Count == 0) orderItemDto.OrderItemPallets.Add(MeasurementService.CreateNewOrderItemPallet(orderItemDto, LoggedInModel.CustomerDto));
                else
                {
                    foreach (var orderItemPallet in orderItemDto.OrderItemPallets)
                        orderItemPallet.OrderItemDto = orderItemDto;
                }
            }
        }

        private static void RefreshOrderGenericAttributes(List<OrderDto> srcOrderDtos, List<OrderDto> destOrderDtos)
        {
            var orderAttributesByOrderId = srcOrderDtos.Where(o => o.GenericAttributes.Count > 0).ToDictionary(k => k.Id, v => v.GenericAttributes);
            foreach (var orderDto in destOrderDtos)
            {
                if (orderAttributesByOrderId.TryGetValue(orderDto.Id, out var genericAttributes))
                    orderDto.GenericAttributes.UpdateBaseEntityCollection(genericAttributes, false);
            }
        }

        /// <summary>
        /// Navigates from a card click (Napi feladatok tab) to the Mérés tab with the selected order.
        /// Loads the full order from DB and switches the active tab.
        /// </summary>
        private async Task NavigateToMeasuringTab(OrderDto order)
        {
            LoadingPanelVisible = true;
            SelectedOrder = order;

            await RefreshOrderStatusFromDbAsync(order);

            _activeTab = MeasuringTab.Measuring;

            await ApplyMeasuringAccessAsync(order);
        }

        /// <summary>
        /// Fetches the latest order data from DB and updates status and generic attributes.
        /// </summary>
        private async Task RefreshOrderStatusFromDbAsync(OrderDto order)
        {
            var orderFromDb = await FruitBankSignalRClient.GetOrderDtoById(order.Id);
            if (orderFromDb != null)
            {
                order.OrderStatus = orderFromDb.OrderStatus;
                order.GenericAttributes.UpdateBaseEntityCollection(orderFromDb.GenericAttributes, false);
            }
        }

        /// <summary>
        /// Sets measuring access, hides loading panel, refreshes UI, and shows info dialog if access is denied.
        /// </summary>
        private async Task ApplyMeasuringAccessAsync(OrderDto? order)
        {
            HasMeasuringAccess = order?.HasMeasuringAccess(LoggedInModel.CustomerDto?.Id, LoggedInModel.IsRevisor) ?? false;
            LoadingPanelVisible = false;

            StateHasChanged();

            if (!HasMeasuringAccess && order != null)
                await DialogService.ShowMessageBoxAsync("Információ", "A mérés már folyamatban, válasszon másik rendelést!", MessageBoxRenderStyle.Info);
        }

        public void Dispose()
        {
            FruitBankSignalRClient.OnMessageReceived -= SignalRClientOnMessageReceived;
        }
    }
}
