using ERPCore.Domain;
using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
using ERPCore.Repositories.Interfaces;
using ERPCore.Services.Interfaces;
using ERPCore.Services.Stock;
using Microsoft.EntityFrameworkCore;

namespace ERPCore.Services;

public sealed class SalesPricingService : ISalesPricingService
{
    private readonly IRepository<Item> _items;
    private readonly IRepository<GrnLine> _grnLines;
    private readonly IFifoCostingService _fifo;

    public SalesPricingService(IRepository<Item> items, IRepository<GrnLine> grnLines, IFifoCostingService fifo)
    {
        _items = items;
        _grnLines = grnLines;
        _fifo = fifo;
    }

    public async Task<SalesPriceResolution> ResolveAsync(
        int itemId, int warehouseId, decimal? requestedUnitPrice, bool allowManualOverride, CancellationToken ct = default)
    {
        var item = await _items.Query().AsNoTracking().FirstOrDefaultAsync(x => x.ItemId == itemId, ct)
            ?? throw new InvalidOperationException($"Item {itemId} was not found.");

        if (requestedUnitPrice is not null)
        {
            if (!allowManualOverride)
            {
                if (item.SalePrice.HasValue)
                    return new SalesPriceResolution(item.SalePrice.Value, "SALE_PRICE", item.SalePrice.Value);

                var grnPrice = await GetWeightedGrnPriceAsync(itemId, warehouseId, ct);
                if (grnPrice is not null)
                    return new SalesPriceResolution(grnPrice.Value, "GRN_WEIGHTED_AVG", grnPrice.Value);

                return new SalesPriceResolution(await GetFifoFallbackPriceAsync(itemId, warehouseId, ct), "FIFO_AVG", 0m);
            }

            return new SalesPriceResolution(requestedUnitPrice.Value, "MANUAL", requestedUnitPrice.Value);
        }

        if (item.SalePrice.HasValue)
            return new SalesPriceResolution(item.SalePrice.Value, "SALE_PRICE", item.SalePrice.Value);

        var weighted = await GetWeightedGrnPriceAsync(itemId, warehouseId, ct);
        if (weighted is not null)
            return new SalesPriceResolution(weighted.Value, "GRN_WEIGHTED_AVG", weighted.Value);

        return new SalesPriceResolution(await GetFifoFallbackPriceAsync(itemId, warehouseId, ct), "FIFO_AVG", 0m);
    }

    private async Task<decimal?> GetWeightedGrnPriceAsync(int itemId, int warehouseId, CancellationToken ct)
    {
        var rows = await _grnLines.Query().AsNoTracking()
            .Where(l => l.ItemId == itemId
                        && l.Grn != null
                        && l.Grn.WarehouseId == warehouseId
                        && l.Grn.Status == GrnStatus.Confirmed)
            .Select(l => new { l.Qty, l.ReceivedValue })
            .ToListAsync(ct);

        var totalQty = rows.Sum(x => x.Qty);
        if (totalQty <= 0) return null;

        var totalValue = rows.Sum(x => x.ReceivedValue);
        return totalValue / totalQty;
    }

    private async Task<decimal> GetFifoFallbackPriceAsync(int itemId, int warehouseId, CancellationToken ct)
    {
        var valuation = await _fifo.GetValuationAsync(itemId, warehouseId, ct);
        return valuation.TotalQty > 0 ? valuation.TotalValue / valuation.TotalQty : 0m;
    }
}
