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

namespace ERPCore.Services.Stock;

public sealed class StockService : IStockService
{
    private readonly IFifoCostingService _fifo;
    private readonly IRepository<StockLayer> _layers;
    private readonly IRepository<StockLedger> _ledger;
    private readonly IRepository<StockTransferLine> _transferLines;

    public StockService(
        IFifoCostingService fifo, IRepository<StockLayer> layers,
        IRepository<StockLedger> ledger, IRepository<StockTransferLine> transferLines)
    {
        _fifo = fifo;
        _layers = layers;
        _ledger = ledger;
        _transferLines = transferLines;
    }

    public async Task<StockOnHandDto> GetOnHandAsync(int itemId, int warehouseId, CancellationToken ct = default)
    {
        var onHand = await _fifo.GetOnHandAsync(itemId, warehouseId, ct);

        // On-hold stock is on-hand but not issuable — sourced from GRN lines still OnHold.
        var onHold = await _layers.Query().AsNoTracking()
            .Where(l => l.ItemId == itemId && l.WarehouseId == warehouseId
                        && l.GrnLine != null && l.GrnLine.HoldStatus == HoldStatus.OnHold)
            .SumAsync(l => (decimal?)l.QtyRemaining, ct) ?? 0m;

        // Outbound in-transit: dispatched from this warehouse, not yet received at the
        // destination. Dispatch already consumed the source layers, so this stock has
        // left onHand — it is reported for visibility (AR-05) but is NOT re-subtracted
        // from available (that would double-count). reserved (sales) stays a stub.
        var inTransit = await _transferLines.Query().AsNoTracking()
            .Where(l => l.ItemId == itemId
                        && l.Transfer!.SrcWarehouseId == warehouseId
                        && l.Transfer.Status == TransferStatus.InTransit)
            .SumAsync(l => (decimal?)(l.Qty - l.QtyReceived), ct) ?? 0m;
        const decimal reserved = 0m;
        var available = onHand - onHold - reserved;

        return new StockOnHandDto(itemId, warehouseId, onHand, available, onHold, inTransit, reserved, DateTime.UtcNow);
    }

    public async Task<PagedResponse<StockLedgerRowDto>> GetLedgerAsync(
        int? itemId, int? warehouseId, DateOnly? from, DateOnly? to, PageQuery query, CancellationToken ct = default)
    {
        var q = _ledger.Query().AsNoTracking();
        if (itemId is not null) q = q.Where(l => l.ItemId == itemId);
        if (warehouseId is not null) q = q.Where(l => l.WarehouseId == warehouseId);
        if (from is not null) q = q.Where(l => l.CreatedAt >= from.Value.ToDateTime(TimeOnly.MinValue));
        if (to is not null) q = q.Where(l => l.CreatedAt < to.Value.AddDays(1).ToDateTime(TimeOnly.MinValue));

        var total = await q.CountAsync(ct);
        var rows = await q.OrderByDescending(l => l.LedgerId)
            .Skip(query.Skip).Take(query.PageSize)
            .Select(l => new StockLedgerRowDto(
                l.LedgerId, l.ItemId, l.WarehouseId, l.BinId, l.BatchId, l.SerialId,
                l.Direction, l.QtyBase, l.UnitCost, l.Value, l.RunningBalance,
                l.SourceDocType, l.SourceDocId, l.UserId, l.CreatedAt))
            .ToListAsync(ct);

        return PagedResponse<StockLedgerRowDto>.Create(rows, query.Page, query.PageSize, total);
    }

    public Task<StockValuationDto> GetValuationAsync(int itemId, int warehouseId, CancellationToken ct = default)
        => _fifo.GetValuationAsync(itemId, warehouseId, ct);
}
