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