using FruitBank.Common.Entities;
using FruitBank.Common.Server;
using LinqToDB;
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Catalog;
using Nop.Services.Customers;
using Nop.Services.Localization;
using Nop.Web.Framework.Controllers;
using static Nop.Plugin.Misc.FruitBankPlugin.Controllers.OrderController;

namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers;

[AutoValidateAntiforgeryToken]
public class PreOrderController : BasePluginController
{
    private readonly IWorkContext _workContext;
    private readonly IStoreContext _storeContext;
    private readonly ICustomerService _customerService;
    private readonly ILocalizationService _localizationService;
    private readonly FruitBankDbContext _dbContext;
    private readonly PreOrderDbContext _preorderDbContext;
    private readonly FruitBankAttributeService _fruitBankAttributeService;
    private readonly CustomPriceCalculationService _customPriceCalculationService;

    private const string PendingDeliveryDateTimeKey = "PreOrderPendingDeliveryDateTime";
    private const string Prefix = "Plugins.Misc.FruitBankPlugin.PreOrder.";

    public PreOrderController(
        IWorkContext workContext,
        IStoreContext storeContext,
        ICustomerService customerService,
        ILocalizationService localizationService,
        FruitBankDbContext dbContext,
        PreOrderDbContext preorderDbContext,
        FruitBankAttributeService fruitBankAttributeService,
        IPriceCalculationService priceCalculationService)
    {
        _workContext = workContext;
        _storeContext = storeContext;
        _customerService = customerService;
        _localizationService = localizationService;
        _dbContext = dbContext;
        _preorderDbContext = preorderDbContext;
        _fruitBankAttributeService = fruitBankAttributeService;
        _customPriceCalculationService = priceCalculationService as CustomPriceCalculationService;
    }

    private Task<string> L(string keySuffix)
        => _localizationService.GetResourceAsync(Prefix + keySuffix);

    // ── INDEX ─────────────────────────────────────────────────────────────────

    [HttpGet]
    public async Task<IActionResult> Index()
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        if (await _customerService.IsGuestAsync(customer))
            return Challenge();

        return View("~/Plugins/Misc.FruitBankPlugin/Views/PreOrder/Index.cshtml");
    }

    // ── GET SAVED DELIVERY DATETIME (page restore) ────────────────────────────

    [HttpGet]
    public async Task<IActionResult> GetDeliveryDateTime()
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        if (await _customerService.IsGuestAsync(customer))
            return Json(new { success = false });

        var store = await _storeContext.GetCurrentStoreAsync();
        var saved = await _fruitBankAttributeService
            .GetGenericAttributeValueAsync<Nop.Core.Domain.Customers.Customer, DateTime?>(
                customer.Id, PendingDeliveryDateTimeKey, store.Id);

        if (!saved.HasValue)
            return Json(new { success = true, hasValue = false });

        return Json(new
        {
            success  = true,
            hasValue = true,
            date     = saved.Value.ToString("yyyy-MM-dd"),
            time     = saved.Value.ToString("HH:mm"),
            iso      = saved.Value.ToString("yyyy-MM-ddTHH:mm")
        });
    }

    // ── SET DELIVERY DATETIME ─────────────────────────────────────────────────

    [HttpPost]
    public async Task<IActionResult> SetDeliveryDateTime(string deliveryDateTime)
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        if (await _customerService.IsGuestAsync(customer))
            return Json(new { success = false, message = await L("NotLoggedIn") });

        if (string.IsNullOrWhiteSpace(deliveryDateTime))
            return Json(new { success = false, message = await L("NoDeliveryDateTimeProvided") });

        if (!DateTime.TryParse(deliveryDateTime, out var parsed))
            return Json(new { success = false, message = await L("InvalidDeliveryDateTime") });

        var store = await _storeContext.GetCurrentStoreAsync();
        await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Nop.Core.Domain.Customers.Customer, DateTime>(
            customer.Id, PendingDeliveryDateTimeKey, parsed, store.Id);

        return Json(new { success = true });
    }

    // ── GET AVAILABLE PRODUCTS (filtered by preorder window) ──────────────────

    [HttpGet]
    public async Task<IActionResult> GetAvailableProducts()
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        if (await _customerService.IsGuestAsync(customer))
            return Json(new { success = false, message = await L("NotLoggedIn") });

        try
        {
            var store   = await _storeContext.GetCurrentStoreAsync();
            var today   = DateTime.UtcNow.Date;

            // Load preorder window generic attributes — two queries, no N+1
            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 that are available today for preorder
            var availableProductIds = 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 (!availableProductIds.Any())
                return Json(new { success = true, products = Array.Empty<object>() });

            // Load product DTOs for those IDs
            var productDtos = await _dbContext.ProductDtos
                .GetAll(true)
                .Where(p => availableProductIds.Contains(p.Id))
                .ToListAsync();

            var result = new List<object>();

            foreach (var dto in productDtos.OrderBy(p => p.Name))
            {
                decimal? unitPrice = null;
                if (!dto.IsMeasurable && _customPriceCalculationService != null)
                {
                    var product = await _dbContext.Products.GetByIdAsync(dto.Id);
                    if (product != null)
                    {
                        var priceResult = await _customPriceCalculationService.GetFinalPriceAsync(
                            product, customer, store, null, 0, true, 1, null, null);
                        unitPrice = priceResult.finalPrice;
                    }
                }

                result.Add(new
                {
                    id           = dto.Id,
                    name         = dto.Name,
                    isMeasurable = dto.IsMeasurable,
                    unitPrice,
                    stockQuantity = dto.AvailableQuantity
                });
            }

            return Json(new { success = true, products = result });
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[PreOrder] GetAvailableProducts error: {ex.Message}");
            return Json(new { success = false, message = $"Hiba: {ex.Message}" });
        }
    }

    // ── PLACE PREORDER ────────────────────────────────────────────────────────

    [HttpPost]
    public async Task<IActionResult> PlacePreOrder([FromBody] PlacePreOrderRequest request)
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        if (await _customerService.IsGuestAsync(customer))
            return Json(new { success = false, message = await L("NotLoggedIn") });

        if (request?.Items == null || !request.Items.Any())
            return Json(new { success = false, message = await L("NoItemsSelected") });

        if (!DateTime.TryParse(request.DeliveryDateTime, out var deliveryDateTime))
            return Json(new { success = false, message = await L("InvalidDeliveryDateTime") });

        try
        {
            var store = await _storeContext.GetCurrentStoreAsync();

            var preorder = new PreOrder
            {
                CustomerId    = customer.Id,
                StoreId       = store.Id,
                DateOfReceipt = deliveryDateTime,
                CustomerNote  = request.CustomerNote?.Trim()
            };

            var items = new List<PreOrderItem>();
            foreach (var req in request.Items.Where(i => i.Quantity > 0))
            {
                var product = await _dbContext.Products.GetByIdAsync(req.ProductId);
                if (product == null || product.Deleted || !product.Published)
                    continue;

                decimal unitPrice = 0;
                if (_customPriceCalculationService != null)
                {
                    var pr = await _customPriceCalculationService.GetFinalPriceAsync(
                        product, customer, store, null, 0, true, req.Quantity, null, null);
                    unitPrice = pr.finalPrice;
                }

                items.Add(new PreOrderItem
                {
                    ProductId       = req.ProductId,
                    RequestedQuantity = req.Quantity,
                    UnitPriceInclTax = unitPrice
                });
            }

            if (!items.Any())
                return Json(new { success = false, message = await L("NoValidItems") });

            var saved = await _preorderDbContext.InsertPreOrderAsync(preorder, items);

            // Clean up the pending delivery datetime attribute
            await _fruitBankAttributeService
                .DeleteGenericAttributeAsync<Nop.Core.Domain.Customers.Customer>(
                    customer.Id, PendingDeliveryDateTimeKey, store.Id);

            Console.WriteLine($"[PreOrder] Placed #{saved.Id} — customer #{customer.Id}, {items.Count} items, delivery {deliveryDateTime:u}");

            return Json(new
            {
                success    = true,
                preorderId = saved.Id,
                message    = await L("PlacedSuccessfully")
            });
        }
        catch (Exception ex)
        {
            Console.WriteLine($"[PreOrder] PlacePreOrder error: {ex.Message}");
            return Json(new { success = false, message = $"Hiba: {ex.Message}" });
        }
    }

    // ── INNER MODELS ──────────────────────────────────────────────────────────

    public class PlacePreOrderRequest
    {
        public string? DeliveryDateTime { get; set; }
        public string? CustomerNote     { get; set; }
        public List<PreOrderItemRequest> Items { get; set; } = new();
    }

    public class PreOrderItemRequest
    {
        public int ProductId { get; set; }
        public int Quantity  { get; set; }
    }
}
