using AyCode.Core.Loggers;
using FruitBank.Common.Entities;
using FruitBank.Common.Enums;
using LinqToDB;
using Mango.Nop.Core.Loggers;
using Mango.Nop.Data.Repositories;
using Nop.Core.Caching;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Events;
using Nop.Data;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer.Interfaces;
using Nop.Plugin.Misc.FruitBankPlugin.Services;

namespace Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;

public class PreOrderDbContext :
    IPreOrderDbSet<PreOrderDbTable>,
    IPreOrderItemDbSet<PreOrderItemDbTable>
{
    private readonly ILogger _logger;

    public PreOrderDbTable PreOrders { get; set; }
    public PreOrderItemDbTable PreOrderItems { get; set; }

    // Read-only access to related NopCommerce repositories needed during conversion
    public IRepository<Customer> Customers { get; set; }
    public IRepository<Product> Products { get; set; }
    public IRepository<Order> Orders { get; set; }
    public IRepository<OrderItem> OrderItems { get; set; }

    public PreOrderDbContext(
        PreOrderDbTable preorderDbTable,
        PreOrderItemDbTable preorderItemDbTable,
        IRepository<Customer> customerRepository,
        IRepository<Product> productRepository,
        IRepository<Order> orderRepository,
        IRepository<OrderItem> orderItemRepository,
        IEnumerable<IAcLogWriterBase> logWriters)
    {
        PreOrders = preorderDbTable;
        PreOrderItems = preorderItemDbTable;
        Customers = customerRepository;
        Products = productRepository;
        Orders = orderRepository;
        OrderItems = orderItemRepository;
        _logger = new Logger<PreOrderDbContext>(logWriters.ToArray());
    }

    /// <summary>
    /// Insert a complete preorder with all its items in one operation.
    /// Returns the saved preorder (with Id populated).
    /// </summary>
    public async Task<PreOrder> InsertPreOrderAsync(PreOrder preorder, IList<PreOrderItem> items)
    {
        preorder.CreatedOnUtc = DateTime.UtcNow;
        preorder.UpdatedOnUtc = DateTime.UtcNow;
        preorder.Status = PreOrderStatus.Pending;

        await PreOrders.InsertAsync(preorder);

        foreach (var item in items)
        {
            item.PreOrderId = preorder.Id;
            item.FulfilledQuantity = 0;
            item.Status = PreOrderItemStatus.Pending;
            await PreOrderItems.InsertAsync(item);
        }

        _logger.Info($"PreOrderDbContext: inserted PreOrder #{preorder.Id} with {items.Count} items for customer #{preorder.CustomerId}");
        return preorder;
    }

    /// <summary>
    /// Returns all pending preorder items for a set of productIds, ordered by PreOrderId (FCFS).
    /// Used by PreOrderConversionService after IncomingQuantity is written.
    /// </summary>
    public async Task<List<PreOrderItem>> GetPendingItemsForProductsAsync(IList<int> productIds)
    {
        // Fetch all items for these products first, then filter by status in memory
        // LinqToDB cannot translate enum comparisons to SQL in this codebase
        var all = await PreOrderItems.Table
            .Where(i => productIds.Contains(i.ProductId))
            .OrderBy(i => i.PreOrderId)
            .ToListAsync();

        return all.Where(i =>
            i.Status == PreOrderItemStatus.Pending ||
            i.Status == PreOrderItemStatus.PartiallyFulfilled)
            .ToList();
    }

    /// <summary>
    /// After conversion: check if all items in a preorder are resolved and update the preorder's status.
    /// </summary>
    public async Task RefreshPreOrderStatusAsync(int preorderId)
    {
        var preorder = await PreOrders.GetByIdAsync(preorderId);
        if (preorder == null) return;

        var items = await PreOrderItems.GetAllByPreOrderIdAsync(preorderId).ToListAsync();

        var hasDropped    = items.Any(i => i.Status == PreOrderItemStatus.Dropped);
        var hasPartial    = items.Any(i => i.Status == PreOrderItemStatus.PartiallyFulfilled);
        var hasPending    = items.Any(i => i.Status == PreOrderItemStatus.Pending);
        var allFulfilled  = items.All(i => i.Status == PreOrderItemStatus.Fulfilled);

        preorder.Status = (hasDropped || hasPartial) && !hasPending ? PreOrderStatus.PartiallyFulfilled
                        : allFulfilled                               ? PreOrderStatus.Confirmed
                        : PreOrderStatus.Pending;

        preorder.UpdatedOnUtc = DateTime.UtcNow;
        await PreOrders.UpdateAsync(preorder);

        _logger.Info($"PreOrderDbContext: PreOrder #{preorderId} status → {preorder.Status}");
    }

    /// <summary>
    /// Mark a preorder as cancelled (customer or admin action).
    /// </summary>
    public async Task CancelPreOrderAsync(int preorderId)
    {
        var preorder = await PreOrders.GetByIdAsync(preorderId);
        if (preorder == null) return;

        preorder.Status = PreOrderStatus.Cancelled;
        preorder.UpdatedOnUtc = DateTime.UtcNow;
        await PreOrders.UpdateAsync(preorder);

        var items = await PreOrderItems.GetAllByPreOrderIdAsync(preorderId).ToListAsync();
        var cancellableStatuses = new[] { PreOrderItemStatus.Pending, PreOrderItemStatus.PartiallyFulfilled };
        foreach (var item in items.Where(i => cancellableStatuses.Contains(i.Status)))
        {
            item.Status = PreOrderItemStatus.Dropped;
            await PreOrderItems.UpdateAsync(item);
        }

        _logger.Info($"PreOrderDbContext: PreOrder #{preorderId} cancelled");
    }
}
