using FruitBank.Common.Entities;
using FruitBank.Common.Server;
using Microsoft.AspNetCore.Mvc;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Orders;
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.Services.Orders;
using Nop.Web.Framework.Controllers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Nop.Plugin.Misc.FruitBankPlugin.Controllers;

[AutoValidateAntiforgeryToken]
public class OrderController : 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 readonly IShoppingCartService _shoppingCartService;
    private readonly IProductService _productService;
    private readonly OpenAIApiService _aiApiService;
    private readonly CerebrasAPIService _cerebrasApiService;
    private readonly PreOrderConversionService _preorderConversionService;

    private const string PendingDeliveryKey = "OrderFlowPendingDeliveryDateTime";

    public OrderController(
        IWorkContext workContext,
        IStoreContext storeContext,
        ICustomerService customerService,
        ILocalizationService localizationService,
        FruitBankDbContext dbContext,
        PreOrderDbContext preorderDbContext,
        FruitBankAttributeService fruitBankAttributeService,
        IPriceCalculationService priceCalculationService,
        IShoppingCartService shoppingCartService,
        IProductService productService,
        OpenAIApiService aiApiService,
        CerebrasAPIService cerebrasApiService,
        PreOrderConversionService preorderConversionService)
    {
        _workContext = workContext;
        _storeContext = storeContext;
        _customerService = customerService;
        _localizationService = localizationService;
        _dbContext = dbContext;
        _preorderDbContext = preorderDbContext;
        _fruitBankAttributeService = fruitBankAttributeService;
        _customPriceCalculationService = priceCalculationService as CustomPriceCalculationService;
        _shoppingCartService = shoppingCartService;
        _productService = productService;
        _aiApiService = aiApiService;
        _cerebrasApiService = cerebrasApiService;
        _preorderConversionService = preorderConversionService;
    }

    // ── 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/Order/Index.cshtml");
    }

    // ── FLOW TYPE ─────────────────────────────────────────────────────────────

    /// <summary>
    /// Mon/Tue/Wed → preorder regardless of delivery date.
    /// Thu/Fri/Sat/Sun + delivery this week → quickorder.
    /// Thu/Fri/Sat/Sun + delivery next week or later → preorder.
    /// </summary>
    public static string ComputeFlowType(DateTime deliveryDate)
    {
        var today    = DateTime.Today;
        var todayDow = (int)today.DayOfWeek;   // 0=Sun 1=Mon … 6=Sat

        // This week's Thursday
        int daysSinceMon  = todayDow == 0 ? 6 : todayDow - 1;
        var weekStart     = today.AddDays(-daysSinceMon);   // Monday
        var thisThursday  = weekStart.AddDays(3);           // Thursday
        var weekEnd       = weekStart.AddDays(6);           // Sunday

        bool deliveryBeforeThursday = deliveryDate.Date < thisThursday;
        bool isLateWeek             = todayDow == 0 || todayDow >= 4;   // Thu-Sun
        bool deliveryThisWeek       = deliveryDate.Date >= weekStart && deliveryDate.Date <= weekEnd;

        // Quick Order: delivery needs current stock (before Thursday)
        //              OR goods already arrived (Thu-Sun) and delivery still this week
        // PreOrder: delivery is Thursday+ but today is still Mon/Tue/Wed (goods not yet here)
        return (deliveryBeforeThursday || (isLateWeek && deliveryThisWeek))
            ? "quickorder"
            : "preorder";
    }

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

    [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, PendingDeliveryKey, store.Id);

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

        var flowType = ComputeFlowType(saved.Value);

        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"),
            flowType
        });
    }

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

        if (string.IsNullOrWhiteSpace(deliveryDateTime) ||
            !DateTime.TryParse(deliveryDateTime, out var parsed))
            return Json(new { success = false, message = "Érvénytelen dátum/idő formátum" });

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

        var flowType = ComputeFlowType(parsed);

        Console.WriteLine($"[OrderFlow] SetDeliveryDateTime — customer #{customer.Id}, {parsed:u}, flowType={flowType}");
        return Json(new { success = true, flowType });
    }

    // ── PRODUCTS — Quick Order flow (all available stock) ─────────────────────

    [HttpGet]
    public async Task<IActionResult> GetAllProducts(string deliveryDate = null, string deliveryTime = null)
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        if (await _customerService.IsGuestAsync(customer))
            return Json(new { success = false, message = "Nincs bejelentkezve" });

        try
        {
            var store = await _storeContext.GetCurrentStoreAsync();
            var allProductDtos = (await _dbContext.ProductDtos.GetAll(true).ToListAsync())
                .Where(pd => pd.AvailableQuantity > 0);

            var result = new List<object>();
            foreach (var product in allProductDtos)
            {
                var availableQty = product.StockQuantity + product.IncomingQuantity;
                if (availableQty <= 0) continue;

                decimal? unitPrice = null;
                if (!product.IsMeasurable && _customPriceCalculationService != null)
                {
                    var tproduct = await _productService.GetProductByIdAsync(product.Id);
                    if (tproduct != null)
                    {
                        var pr = await _customPriceCalculationService.GetFinalPriceAsync(
                            tproduct, customer, store, null, 0, true, 1, null, null);
                        unitPrice = pr.finalPrice;
                    }
                }

                result.Add(new
                {
                    id            = product.Id,
                    name          = product.Name,
                    quantity      = 1,
                    unitPrice,
                    stockQuantity = availableQty,
                    searchTerm    = (string)null,
                    isQuantityReduced = false,
                    isMeasurable  = product.IsMeasurable
                });
            }

            return Json(new { success = true, products = result });
        }
        catch (Exception ex)
        {
            return Json(new { success = false, message = $"Hiba: {ex.Message}" });
        }
    }

    // ── PRODUCTS — PreOrder flow (curated window list) ────────────────────────

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

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

            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);

            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 { success = true, products = Array.Empty<object>() });

            var productDtos = await _dbContext.ProductDtos
                .GetAll(true)
                .Where(p => availableIds.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 pr = await _customPriceCalculationService.GetFinalPriceAsync(
                            product, customer, store, null, 0, true, 1, null, null);
                        unitPrice = pr.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)
        {
            return Json(new { success = false, message = $"Hiba: {ex.Message}" });
        }
    }

    // ── SEARCH (Quick Order flow) ─────────────────────────────────────────────

    [HttpPost]
    public async Task<IActionResult> SearchProducts(string text, string deliveryDate = null, string deliveryTime = null)
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        if (await _customerService.IsGuestAsync(customer))
            return Json(new { success = false, message = "Nincs bejelentkezve" });

        if (string.IsNullOrWhiteSpace(text))
            return Json(new { success = false, message = "Nincs szöveg megadva" });

        try
        {
            var parsedProducts = await ParseProductsFromText(text);
            if (parsedProducts == null || !parsedProducts.Any())
                return Json(new { success = false, message = "Nem sikerült termékeket azonosítani", transcription = text });

            var store = await _storeContext.GetCurrentStoreAsync();
            var enriched = await EnrichProductData(parsedProducts, customer, store);
            return Json(new { success = true, transcription = text, products = enriched });
        }
        catch (Exception ex)
        {
            return Json(new { success = false, message = $"Hiba: {ex.Message}" });
        }
    }

    // ── VOICE (Quick Order flow) ──────────────────────────────────────────────

    [HttpPost]
    public async Task<IActionResult> TranscribeAndSearch(
        Microsoft.AspNetCore.Http.IFormFile audioFile,
        string deliveryDate = null, string deliveryTime = null)
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        if (await _customerService.IsGuestAsync(customer))
            return Json(new { success = false, message = "Nincs bejelentkezve" });

        if (audioFile == null || audioFile.Length == 0)
            return Json(new { success = false, message = "Nem érkezett hangfájl" });

        try
        {
            var text = await TranscribeAudioFile(audioFile, "hu");
            if (string.IsNullOrEmpty(text))
                return Json(new { success = false, message = "Nem sikerült a hangfelismerés" });

            var parsedProducts = await ParseProductsFromText(text);
            if (parsedProducts == null || !parsedProducts.Any())
                return Json(new { success = false, message = "Nem sikerült termékeket azonosítani", transcription = text });

            var store = await _storeContext.GetCurrentStoreAsync();
            var enriched = await EnrichProductData(parsedProducts, customer, store);
            return Json(new { success = true, transcription = text, products = enriched });
        }
        catch (Exception ex)
        {
            return Json(new { success = false, message = $"Hiba: {ex.Message}" });
        }
    }

    // ── ADD TO CART (Quick Order flow) ────────────────────────────────────────

    [HttpPost]
    public async Task<IActionResult> AddToCart(int productId, int quantity)
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        if (await _customerService.IsGuestAsync(customer))
            return Json(new { success = false, message = "Nincs bejelentkezve" });

        if (productId <= 0 || quantity <= 0)
            return Json(new { success = false, message = "Érvénytelen termék vagy mennyiség" });

        try
        {
            var product = await _productService.GetProductByIdAsync(productId);
            if (product == null || product.Deleted || !product.Published)
                return Json(new { success = false, message = "A termék nem elérhető" });

            var store = await _storeContext.GetCurrentStoreAsync();
            var warnings = await _shoppingCartService.AddToCartAsync(
                customer, product, ShoppingCartType.ShoppingCart, store.Id, quantity: quantity);

            if (warnings.Any())
                return Json(new { success = false, message = string.Join("; ", warnings) });

            var cartItems = await GetCartItemsJson(customer, store);
            return Json(new { success = true, cartItems });
        }
        catch (Exception ex)
        {
            return Json(new { success = false, message = $"Hiba: {ex.Message}" });
        }
    }

    // ── GET CART (Quick Order flow) ───────────────────────────────────────────

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

        var store = await _storeContext.GetCurrentStoreAsync();
        return Json(new { success = true, cartItems = await GetCartItemsJson(customer, store) });
    }

    // ── PLACE PREORDER (PreOrder flow) ────────────────────────────────────────

    [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 = "Nincs bejelentkezve" });

        if (request?.Items == null || !request.Items.Any())
            return Json(new { success = false, message = "Nincs kiválasztott termék" });

        if (!DateTime.TryParse(request.DeliveryDateTime, out var deliveryDateTime))
            return Json(new { success = false, message = "Érvénytelen szállítási dátum/idő" });

        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 = "Nincs érvényes termék az előrendelésben" });

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

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

            // Immediately check if any items can be fulfilled from current available stock.
            // Awaited inline (not fire-and-forget) so we can return the order ID if one is created.
            // shippingDocumentId = 0 signals this was triggered at preorder placement, not by a document.
            var productIds = items.Select(i => i.ProductId).Distinct().ToList();
            await _preorderConversionService.ConvertPreOrdersForProductsAsync(productIds, 0);

            // Re-read to pick up OrderId if conversion created a real order
            var refreshed = await _preorderDbContext.PreOrders.GetByIdAsync(saved.Id);

            Console.WriteLine($"[OrderFlow] PlacePreOrder #{saved.Id} — orderId={refreshed?.OrderId}");

            return Json(new
            {
                success    = true,
                preorderId = saved.Id,
                orderId    = refreshed?.OrderId
            });
        }
        catch (Exception ex)
        {
            return Json(new { success = false, message = $"Hiba: {ex.Message}" });
        }
    }

    // ── PRIVATE HELPERS ───────────────────────────────────────────────────────

    private async Task<string> TranscribeAudioFile(Microsoft.AspNetCore.Http.IFormFile audioFile, string language)
    {
        var fileName      = $"order_{DateTime.Now:yyyyMMdd_HHmmss}.webm";
        var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "voice");
        if (!Directory.Exists(uploadsFolder)) Directory.CreateDirectory(uploadsFolder);

        var filePath = Path.Combine(uploadsFolder, fileName);
        using (var stream = new FileStream(filePath, FileMode.Create))
            await audioFile.CopyToAsync(stream);

        string text;
        using (var audioStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            text = await _aiApiService.TranscribeAudioAsync(audioStream, fileName, language, null);

        if (!string.IsNullOrEmpty(text) && (text.EndsWith(".") || text.EndsWith("!") || text.EndsWith("?")))
            text = text[..^1];

        try { System.IO.File.Delete(filePath); } catch { }
        return text;
    }

    private async Task<List<ParsedProduct>> ParseProductsFromText(string text)
    {
        var systemPrompt = @"You are a product parser for a Hungarian fruit and vegetable wholesale company.
Parse the product names and quantities from the user's input.
CRITICAL RULES:
1. Normalize product names to singular, lowercase
2. Handle Hungarian number words
3. Fix common transcription/typing errors
4. Return ONLY valid JSON array
OUTPUT FORMAT: [{""product"": ""narancs"", ""quantity"": 100}]";

        var aiResponse = await _cerebrasApiService.GetSimpleResponseAsync(systemPrompt, $"Parse this: {text}");
        var jsonMatch  = Regex.Match(aiResponse, @"\[.*\]", RegexOptions.Singleline);
        if (!jsonMatch.Success) return new List<ParsedProduct>();
        try
        {
            return System.Text.Json.JsonSerializer.Deserialize<List<ParsedProduct>>(jsonMatch.Value)
                   ?? new List<ParsedProduct>();
        }
        catch { return new List<ParsedProduct>(); }
    }

    private async Task<List<object>> EnrichProductData(
        List<ParsedProduct> parsedProducts,
        Nop.Core.Domain.Customers.Customer customer,
        Nop.Core.Domain.Stores.Store store)
    {
        var enriched     = new List<object>();
        var allDtos      = await _dbContext.ProductDtos.GetAll(true).ToListAsync();

        foreach (var parsed in parsedProducts)
        {
            var dbProducts = await _productService.SearchProductsAsync(
                keywords: parsed.Product, pageIndex: 0, pageSize: 20);

            foreach (var product in dbProducts)
            {
                var dto = allDtos.FirstOrDefault(x => x.Id == product.Id);
                if (dto == null) continue;
                var available = product.StockQuantity + dto.IncomingQuantity;
                if (available <= 0) continue;

                var finalQty   = Math.Min(parsed.Quantity, available);
                var isReduced  = finalQty < parsed.Quantity;
                decimal? price = null;
                if (!dto.IsMeasurable && _customPriceCalculationService != null)
                {
                    var pr = await _customPriceCalculationService.GetFinalPriceAsync(
                        product, customer, store, null, 0, true, finalQty, null, null);
                    price = pr.finalPrice;
                }

                enriched.Add(new
                {
                    id                = product.Id,
                    name              = product.Name,
                    quantity          = finalQty,
                    requestedQuantity = parsed.Quantity,
                    unitPrice         = price,
                    stockQuantity     = available,
                    searchTerm        = parsed.Product,
                    isQuantityReduced = isReduced,
                    isMeasurable      = dto.IsMeasurable
                });
            }
        }
        return enriched;
    }

    private async Task<List<object>> GetCartItemsJson(
        Nop.Core.Domain.Customers.Customer customer,
        Nop.Core.Domain.Stores.Store store)
    {
        var cart    = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id);
        var allDtos = await _dbContext.ProductDtos.GetAll(true).ToListAsync();
        var result  = new List<object>();

        foreach (var item in cart)
        {
            var product = await _productService.GetProductByIdAsync(item.ProductId);
            if (product == null) continue;
            var dto          = allDtos.FirstOrDefault(x => x.Id == product.Id);
            var isMeasurable = dto?.IsMeasurable ?? false;
            decimal? price   = null;
            if (!isMeasurable && _customPriceCalculationService != null)
            {
                var pr = await _customPriceCalculationService.GetFinalPriceAsync(
                    product, customer, store, null, 0, true, item.Quantity, null, null);
                price = pr.finalPrice;
            }
            result.Add(new { id = item.Id, productId = item.ProductId, name = product.Name,
                             quantity = item.Quantity, unitPrice = price, isMeasurable });
        }
        return result;
    }

    // ── 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; }
    }

    private class ParsedProduct
    {
        [System.Text.Json.Serialization.JsonPropertyName("product")]
        public string Product { get; set; }
        [System.Text.Json.Serialization.JsonPropertyName("quantity")]
        public int Quantity { get; set; }
    }
}
