using ERPCore.Domain.Entities;
using ERPCore.Domain.Enums;
using ERPCore.Dtos.Stock;

namespace ERPCore.Services.Interfaces;

/// <summary>
/// FIFO cost-layer and ledger domain service (00-CORE §4, docs/10 Part A.2).
/// Invoked by stock services **inside** a UoW transaction — never from a
/// controller/repository. This turn covers inbound layer creation, ledger posting
/// and valuation; oldest-first consumption (issues/transfers/adjustments) arrives
/// with §5 and must row-lock the layers it consumes (NFR-02).
/// </summary>
public interface IFifoCostingService
{
    /// <summary>Create an inbound FIFO layer (qty and unit cost in the item's base UOM).</summary>
    Task<StockLayer> CreateInboundLayerAsync(
        int itemId, int warehouseId, int? batchId, int? serialId, int? grnLineId,
        decimal qtyBase, decimal unitCost, DateTime receiptDate, CancellationToken ct = default);

    /// <summary>
    /// Consume <paramref name="qtyBase"/> from open layers oldest-first, row-locking
    /// the affected layers for the transaction (NFR-02). Skips on-hold and expired
    /// stock. Throws <c>STOCK_NEGATIVE_BLOCKED</c> if issuable stock is insufficient,
    /// <c>EXPIRED_BATCH_BLOCKED</c>/<c>ONHOLD_NOT_ISSUABLE</c> for an explicit batch
    /// that is expired/held. Returns the consumed segments (for cost-preserving moves
    /// and ledger costing). Must run inside a UoW transaction.
    /// </summary>
    Task<IReadOnlyList<ConsumedSegment>> ConsumeAsync(
        int itemId, int warehouseId, int? batchId, decimal qtyBase, CancellationToken ct = default);

    /// <summary>
    /// Append an immutable ledger entry. Value defaults to <c>round(qtyBase × unitCost, 4)</c>.
    /// </summary>
    /// <param name="valueOverride">
    /// Posts this exact value instead of deriving it from qty × unit cost. Needed when the
    /// authoritative figure is a total rather than a rate: a production receipt must carry the
    /// run's cost pool exactly, but <c>unitCost = pool / goodQty</c> rounds to 6 dp, and at
    /// 100+ units that rounding error exceeds the ledger's 4 dp tick — so the derived value
    /// would drift from the pool (FR-MFG-13). Also used by leftover and cancel returns, whose
    /// value is the exact consumed residual. Omit for every rate-driven movement.
    /// </param>
    Task<StockLedger> PostLedgerAsync(
        int itemId, int warehouseId, int? binId, int? batchId, int? serialId, int userId,
        Direction direction, decimal qtyBase, decimal unitCost, decimal runningBalance,
        string sourceDocType, int sourceDocId, DateTime createdAt, CancellationToken ct = default,
        decimal? valueOverride = null);

    /// <summary>Current on-hand (Σ open-layer qtyRemaining) for an item at a warehouse.</summary>
    Task<decimal> GetOnHandAsync(int itemId, int warehouseId, CancellationToken ct = default);

    /// <summary>Valuation over open layers: Σ(qtyRemaining × unitCost) (FR-STK-04).</summary>
    Task<StockValuationDto> GetValuationAsync(int itemId, int warehouseId, CancellationToken ct = default);
}

/// <summary>A quantity consumed from one FIFO layer at that layer's unit cost.</summary>
public sealed record ConsumedSegment(int LayerId, decimal Qty, decimal UnitCost);
