using ExCSS;
using FruitBank.Common.Entities;
using FruitBank.Common.Enums;
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Common;
using Nop.Services.Configuration;
using Nop.Services.Localization;
using Nop.Services.Messages;
using Nop.Services.Security;
using Nop.Web.Areas.Admin.Controllers;
using Nop.Web.Areas.Admin.Factories;
using Nop.Web.Areas.Admin.Models.Common;
using Nop.Web.Areas.Admin.Models.Home;
using Nop.Web.Framework.Models.DataTables;

namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
{
    public partial class CustomDashboardController : BaseAdminController
    {
        #region Fields

        protected readonly AdminAreaSettings _adminAreaSettings;
        protected readonly ICommonModelFactory _commonModelFactory;
        protected readonly IHomeModelFactory _homeModelFactory;
        protected readonly ILocalizationService _localizationService;
        protected readonly INotificationService _notificationService;
        protected readonly IPermissionService _permissionService;
        protected readonly ISettingService _settingService;
        protected readonly IGenericAttributeService _genericAttributeService;
        protected readonly IWorkContext _workContext;
        protected readonly AICalculationService _aiCalculationService;
        protected readonly FruitBankDbContext _fruitBankDbContext;
        protected readonly PreOrderDbContext _preorderDbContext;

        #endregion

        #region Ctor

        public CustomDashboardController(
            AdminAreaSettings adminAreaSettings,
            ICommonModelFactory commonModelFactory,
            IHomeModelFactory homeModelFactory,
            ILocalizationService localizationService,
            INotificationService notificationService,
            IPermissionService permissionService,
            ISettingService settingService,
            IGenericAttributeService genericAttributeService,
            IWorkContext workContext,
            AICalculationService aiCalculationService,
            FruitBankDbContext fruitBankDbContext,
            PreOrderDbContext preorderDbContext)
        {
            _adminAreaSettings = adminAreaSettings;
            _commonModelFactory = commonModelFactory;
            _homeModelFactory = homeModelFactory;
            _localizationService = localizationService;
            _notificationService = notificationService;
            _permissionService = permissionService;
            _settingService = settingService;
            _workContext = workContext;
            _genericAttributeService = genericAttributeService;
            _aiCalculationService = aiCalculationService;
            _fruitBankDbContext = fruitBankDbContext;
            _preorderDbContext = preorderDbContext;
        }

        #endregion

        #region Methods

        public virtual async Task<IActionResult> Index()
        {
            var customer = await _workContext.GetCurrentCustomerAsync();
            var hideCard = await _genericAttributeService.GetAttributeAsync<bool>(customer, NopCustomerDefaults.HideConfigurationStepsAttribute);
            var closeCard = await _genericAttributeService.GetAttributeAsync<bool>(customer, NopCustomerDefaults.CloseConfigurationStepsAttribute);

            if ((hideCard || closeCard) && await _permissionService.AuthorizeAsync(StandardPermission.System.MANAGE_MAINTENANCE))
            {
                var warnings = await _commonModelFactory.PrepareSystemWarningModelsAsync();
                if (warnings.Any(warning => warning.Level == SystemWarningLevel.Fail || warning.Level == SystemWarningLevel.Warning))
                {
                    var locale = await _localizationService.GetResourceAsync("Admin.System.Warnings.Errors");
                    _notificationService.WarningNotification(string.Format(locale, Url.Action("Warnings", "Common")), false);
                }
            }

            var currentLanguage = await _workContext.GetWorkingLanguageAsync();
            var progress = await _genericAttributeService.GetAttributeAsync<string>(currentLanguage, NopCommonDefaults.LanguagePackProgressAttribute);
            if (!string.IsNullOrEmpty(progress))
            {
                var locale = await _localizationService.GetResourceAsync("Admin.Configuration.LanguagePackProgressMessage");
                _notificationService.SuccessNotification(string.Format(locale, progress, NopLinksDefaults.OfficialSite.Translations), false);
                await _genericAttributeService.SaveAttributeAsync(currentLanguage, NopCommonDefaults.LanguagePackProgressAttribute, string.Empty);
            }

            var model = await _homeModelFactory.PrepareDashboardModelAsync(new DashboardModel());
            return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Index.cshtml", model);
        }

        [HttpPost]
        public virtual async Task<IActionResult> NopCommerceNewsHideAdv()
        {
            _adminAreaSettings.HideAdvertisementsOnAdminArea = !_adminAreaSettings.HideAdvertisementsOnAdminArea;
            await _settingService.SaveSettingAsync(_adminAreaSettings);
            return Content("Setting changed");
        }

        public virtual async Task<IActionResult> GetPopularSearchTerm()
        {
            var model = new DataTablesModel();
            model = await _homeModelFactory.PreparePopularSearchTermReportModelAsync(model);
            return PartialView("Table", model);
        }

        public virtual async Task<IActionResult> GetBestsellersBriefReportByAmount()
        {
            var model = new DataTablesModel();
            model = await _homeModelFactory.PrepareBestsellersBriefReportByAmountModelAsync(model);
            return PartialView("Table", model);
        }

        public virtual async Task<IActionResult> GetBestsellersBriefReportByQuantity()
        {
            var model = new DataTablesModel();
            model = await _homeModelFactory.PrepareBestsellersBriefReportByQuantityModelAsync(model);
            return PartialView("Table", model);
        }

        public virtual async Task<IActionResult> GetLatestOrders()
        {
            var model = new DataTablesModel();
            model = await _homeModelFactory.PrepareLatestOrdersModelAsync(model);
            return PartialView("Table", model);
        }

        public virtual async Task<IActionResult> GetOrderIncomplete()
        {
            var model = new DataTablesModel();
            model = await _homeModelFactory.PrepareOrderIncompleteModelAsync(model);
            return PartialView("Table", model);
        }

        public virtual async Task<IActionResult> GetOrderAverage()
        {
            var model = new DataTablesModel();
            model = await _homeModelFactory.PrepareOrderAverageModelAsync(model);
            return PartialView("Table", model);
        }

        [HttpGet]
        public virtual async Task<IActionResult> GetWelcomeMessage()
        {
            try
            {
                var customer = await _workContext.GetCurrentCustomerAsync();
                var message = await _aiCalculationService.GetWelcomeMessageAsync(customer);

                if (string.IsNullOrWhiteSpace(message))
                    return Content("<p class=\"lead text-muted\">Nincs kapcsolat az AI szerverrel...</p>");

                return Content($"<p class=\"lead\">{System.Net.WebUtility.HtmlEncode(message)}</p>");
            }
            catch (Exception ex)
            {
                return Content($"<p class=\"lead text-muted\"><i class=\"fas fa-exclamation-circle mr-2\"></i>{System.Net.WebUtility.HtmlEncode(ex.Message)}</p>");
            }
        }

        /// <summary>
        /// Returns all FruitBank dashboard data as a single JSON payload.
        /// Called via AJAX after page load — never blocks the dashboard render.
        /// </summary>
        [HttpGet]
        public virtual async Task<IActionResult> GetDashboardData()
        {
            if (!await _permissionService.AuthorizeAsync(StandardPermission.Orders.ORDERS_VIEW))
                return Forbid();

            var today = DateTime.Now.Date;

            // ── Batch 1: parallel queries ─────────────────────────────────────
            // NOTE: PreOrderItems is not a LinqToDB [Association] — never use LoadWith on it.
            // PreOrders and items are loaded separately and joined in memory below.
            var allOrdersTask       = _fruitBankDbContext.OrderDtos.GetAll(true).ToListAsync();
            var allCreditsTask      = _fruitBankDbContext.CustomerCredits.GetAll().ToListAsync();
            var allPreOrdersTask    = _preorderDbContext.PreOrders.GetAll().ToListAsync();
            var unprocessedDocsTask = _fruitBankDbContext.ShippingDocuments.GetAllNotMeasured(true).ToListAsync();

            await Task.WhenAll(allOrdersTask, allCreditsTask, allPreOrdersTask, unprocessedDocsTask);

            var allOrders       = await allOrdersTask;
            var credits         = await allCreditsTask;
            var unprocessedDocs = await unprocessedDocsTask;

            // Filter pending preorders in memory — LinqToDB cannot translate enum comparisons to SQL
            var pendingStatuses  = new[] { PreOrderStatus.Pending, PreOrderStatus.PartiallyFulfilled };
            var pendingPreOrders = (await allPreOrdersTask)
                .Where(p => pendingStatuses.Contains(p.Status))
                .ToList();

            // ── Batch 1b: preorder items for pending preorders only ────────────
            var pendingPreOrderIds = pendingPreOrders.Select(p => p.Id).ToList();
            var pendingItems = pendingPreOrderIds.Any()
                ? await _preorderDbContext.PreOrderItems.GetAll()
                    .Where(i => pendingPreOrderIds.Contains(i.PreOrderId))
                    .ToListAsync()
                : new List<PreOrderItem>();

            // ── Today's orders (in-memory filter, same pattern as AICalculationService)
            var todaysOrders = allOrders
                .Where(o => o.DateOfReceiptOrCreated.Date == today)
                .OrderByDescending(o => o.DateOfReceiptOrCreated)
                .ToList();

            // ── Batch 2: unpaid balances for credit customers ──────────────────
            var creditCustomerIds = credits.Select(c => c.CustomerId).ToList();
            var unpaidByCustomer  = new Dictionary<int, decimal>();

            if (creditCustomerIds.Any())
            {
                var unpaid = await _fruitBankDbContext.OrderDtos.GetAll(false)
                    .Where(o => creditCustomerIds.Contains(o.CustomerId)
                             && o.OrderStatusId  != (int)OrderStatus.Cancelled
                             && o.PaymentStatusId <  (int)PaymentStatus.Paid)
                    .Select(o => new { o.CustomerId, o.OrderTotal })
                    .ToListAsync();

                unpaidByCustomer = unpaid
                    .GroupBy(o => o.CustomerId)
                    .ToDictionary(g => g.Key, g => g.Sum(o => o.OrderTotal));
            }

            // ── Customer name helper: orders cache first, then ID fallback ─────
            string CustomerName(int customerId)
            {
                var fromOrder = allOrders.FirstOrDefault(o => o.CustomerId == customerId)?.Customer;
                if (fromOrder != null)
                {
                    if (!string.IsNullOrEmpty(fromOrder.Company)) return fromOrder.Company;
                    if (!string.IsNullOrEmpty(fromOrder.Email))   return fromOrder.Email;
                }
                return $"#{customerId}";
            }

            // ── PreOrder customer names (may not appear in allOrders) ──────────
            var preorderCustomerIds = pendingPreOrders
                .Select(p => p.CustomerId).Distinct()
                .Where(id => allOrders.All(o => o.CustomerId != id))
                .ToList();

            var preorderCustomerLookup = new Dictionary<int, string>();
            if (preorderCustomerIds.Any())
            {
                var customers = await _preorderDbContext.Customers.Table
                    .Where(c => preorderCustomerIds.Contains(c.Id))
                    .Select(c => new { c.Id, c.Company, c.Email })
                    .ToListAsync();

                foreach (var c in customers)
                    preorderCustomerLookup[c.Id] = !string.IsNullOrEmpty(c.Company) ? c.Company : c.Email;
            }

            string PreOrderCustomerName(int customerId)
                => preorderCustomerLookup.TryGetValue(customerId, out var name)
                    ? name
                    : CustomerName(customerId);

            // ─────────────────────────────────────────────────────────────────
            // Section 1: Today's pipeline
            // ─────────────────────────────────────────────────────────────────
            var todayRows = todaysOrders.Take(20).Select(o =>
            {
                var hasInnVoice = o.GenericAttributes.Any(a => a.Key == "InnVoiceOrderTableId");
                return new
                {
                    id              = o.Id,
                    orderNumber     = o.CustomOrderNumber,
                    company         = CustomerName(o.CustomerId),
                    total           = o.OrderTotal,
                    measuringStatus = o.MeasuringStatus.ToString(),
                    audited         = o.IsAllOrderItemAudited,
                    hasInnVoice,
                    orderStatus     = o.OrderStatus.ToString(),
                    dateOfReceipt   = o.DateOfReceiptOrCreated.ToString("yyyy.MM.dd HH:mm")
                };
            }).ToList();

            var pipeline = new
            {
                total           = todaysOrders.Count,
                measuring       = todaysOrders.Count(o => o.MeasuringStatus == MeasuringStatus.Started),
                audited         = todaysOrders.Count(o => o.IsAllOrderItemAudited),
                missingInnVoice = todaysOrders.Count(o =>
                    o.IsAllOrderItemAudited &&
                    !o.GenericAttributes.Any(a => a.Key == "InnVoiceOrderTableId")),
                completed       = todaysOrders.Count(o => o.OrderStatus == OrderStatus.Complete),
                rows            = todayRows
            };

            // ─────────────────────────────────────────────────────────────────
            // Section 2: Alerts
            // ─────────────────────────────────────────────────────────────────
            var alerts = new List<object>();

            // Audited today but InnVoice sync missing and not yet completed
            foreach (var o in todaysOrders.Where(o =>
                o.IsAllOrderItemAudited &&
                o.OrderStatus != OrderStatus.Complete &&
                !o.GenericAttributes.Any(a => a.Key == "InnVoiceOrderTableId")))
            {
                alerts.Add(new
                {
                    type        = "missing_innvoice",
                    orderId     = o.Id,
                    orderNumber = o.CustomOrderNumber,
                    company     = CustomerName(o.CustomerId),
                    message     = "Auditálva, InnVoice szinkron hiányzik"
                });
            }

            // Measuring started but DateOfReceipt is past (stale, stuck in progress)
            foreach (var o in allOrders.Where(o =>
                o.MeasuringStatus == MeasuringStatus.Started &&
                o.DateOfReceiptOrCreated.Date < today))
            {
                alerts.Add(new
                {
                    type          = "stale_measuring",
                    orderId       = o.Id,
                    orderNumber   = o.CustomOrderNumber,
                    company       = CustomerName(o.CustomerId),
                    dateOfReceipt = o.DateOfReceiptOrCreated.ToString("yyyy.MM.dd"),
                    message       = "Régi, befejezetlen mérés"
                });
            }

            // Partners over their credit limit
            foreach (var credit in credits)
            {
                var outstanding = unpaidByCustomer.GetValueOrDefault(credit.CustomerId, 0m);
                if (outstanding > credit.CreditLimit)
                {
                    alerts.Add(new
                    {
                        type        = "credit_exceeded",
                        customerId  = credit.CustomerId,
                        company     = CustomerName(credit.CustomerId),
                        outstanding,
                        creditLimit = credit.CreditLimit,
                        message     = "Hitelkeret túllépve"
                    });
                }
            }

            // Pending preorders older than 7 days
            var sevenDaysAgo = DateTime.UtcNow.AddDays(-7);
            foreach (var p in pendingPreOrders.Where(p =>
                p.Status == PreOrderStatus.Pending &&
                p.CreatedOnUtc < sevenDaysAgo))
            {
                alerts.Add(new
                {
                    type       = "old_preorder",
                    preorderId = p.Id,
                    company    = PreOrderCustomerName(p.CustomerId),
                    createdAt  = p.CreatedOnUtc.ToLocalTime().ToString("yyyy.MM.dd"),
                    message    = "Régi, nyitott előrendelés"
                });
            }

            // ─────────────────────────────────────────────────────────────────
            // Section 3: Credit status (only partners with a limit set)
            // ─────────────────────────────────────────────────────────────────
            var creditRows = credits
                .Select(credit =>
                {
                    var outstanding = unpaidByCustomer.GetValueOrDefault(credit.CustomerId, 0m);
                    var remaining   = credit.CreditLimit - outstanding;
                    var status      = outstanding > credit.CreditLimit
                                        ? "exceeded"
                                    : credit.CreditLimit > 0 && outstanding > credit.CreditLimit * 0.8m
                                        ? "warning"
                                        : "ok";
                    return new
                    {
                        customerId  = credit.CustomerId,
                        company     = CustomerName(credit.CustomerId),
                        creditLimit = credit.CreditLimit,
                        outstanding,
                        remaining,
                        status
                    };
                })
                .OrderByDescending(c => c.outstanding)
                .ToList();

            // ─────────────────────────────────────────────────────────────────
            // Section 4: Pending preorders (items joined in memory)
            // ─────────────────────────────────────────────────────────────────
            var preorderRows = pendingPreOrders
                .OrderBy(p => p.CreatedOnUtc)
                .Select(p =>
                {
                    var items = pendingItems.Where(i => i.PreOrderId == p.Id).ToList();
                    return new
                    {
                        id             = p.Id,
                        customerId     = p.CustomerId,
                        company        = PreOrderCustomerName(p.CustomerId),
                        status         = p.Status.ToString(),
                        createdAt      = p.CreatedOnUtc.ToLocalTime().ToString("yyyy.MM.dd"),
                        itemCount      = items.Count,
                        fulfilledCount = items.Count(i => i.Status == PreOrderItemStatus.Fulfilled)
                    };
                })
                .ToList();

            // ─────────────────────────────────────────────────────────────────
            // Section 5: Unprocessed shipping documents
            // ─────────────────────────────────────────────────────────────────
            var docRows = unprocessedDocs
                .OrderBy(d => d.ShippingDate)
                .Select(doc => new
                {
                    id            = doc.Id,
                    partnerName   = doc.Partner?.Name ?? "–",
                    shippingDate  = doc.ShippingDate.ToString("yyyy.MM.dd"),
                    totalItems    = doc.ShippingItems?.Count ?? 0,
                    measuredItems = doc.ShippingItems?.Count(si => si.IsMeasured) ?? 0
                })
                .ToList();

            // ─────────────────────────────────────────────────────────────────
            return Json(new
            {
                pipeline,
                alerts,
                creditStatus     = creditRows,
                pendingPreOrders = preorderRows,
                unprocessedDocs  = docRows
            });
        }

        #endregion
    }
}
