using CashLineERP.Contracts.Inventory;

namespace CashLineERP.Application.Inventory;

public sealed class InventoryMovementService : IInventoryMovementService
{
    private readonly IInventoryMovementReadRepository _repository;

    public InventoryMovementService(IInventoryMovementReadRepository repository)
    {
        _repository = repository;
    }

    public IReadOnlyCollection<InventoryMovementResponse> GetInventoryMovements() =>
        _repository.GetInventoryMovements()
            .Select(MapMovement)
            .ToArray();

    public InventoryTraceabilityResponse? GetLotTraceability(string batchNumber)
        => BuildTraceability("LOT", batchNumber, movement => movement.BatchNumber);

    public InventoryTraceabilityResponse? GetSerialTraceability(string serialNumber)
        => BuildTraceability("SERIAL", serialNumber, movement => movement.SerialNumber);

    private InventoryTraceabilityResponse? BuildTraceability(
        string trackingType,
        string trackingCode,
        Func<InventoryMovementView, string?> selector)
    {
        if (string.IsNullOrWhiteSpace(trackingCode))
        {
            throw new InvalidOperationException($"{trackingType} traceability requires a tracking code.");
        }

        var normalizedTrackingCode = trackingCode.Trim();
        var movements = _repository.GetInventoryMovements()
            .Where(movement => string.Equals(selector(movement), normalizedTrackingCode, StringComparison.OrdinalIgnoreCase))
            .OrderBy(movement => movement.MovedAtUtc)
            .ThenBy(movement => movement.InventoryMovementId)
            .ToArray();

        if (movements.Length == 0)
        {
            return null;
        }

        var positions = movements
            .SelectMany(GetWarehouseContributions)
            .GroupBy(contribution => new { contribution.WarehouseId, contribution.StockStatus })
            .Select(group => new InventoryTraceabilityPositionResponse(
                group.Key.WarehouseId,
                group.Key.StockStatus,
                group.Sum(entry => entry.QuantityDelta)))
            .Where(position => position.NetQuantity != 0m)
            .OrderBy(position => position.WarehouseId)
            .ThenBy(position => position.StockStatus)
            .ToArray();

        var positivePositions = positions.Where(position => position.NetQuantity > 0m).ToArray();
        var netTrackedQuantity = positivePositions.Sum(position => position.NetQuantity);
        var hasLocationConflict = string.Equals(trackingType, "SERIAL", StringComparison.OrdinalIgnoreCase)
            && (positivePositions.Length > 1 || netTrackedQuantity > 1m);

        return new InventoryTraceabilityResponse(
            trackingType,
            normalizedTrackingCode,
            movements.Select(movement => movement.ItemId).Distinct().OrderBy(itemId => itemId).ToArray(),
            netTrackedQuantity,
            hasLocationConflict,
            positions,
            movements.Select(MapMovement).ToArray());
    }

    private static IEnumerable<(long WarehouseId, string StockStatus, decimal QuantityDelta)> GetWarehouseContributions(InventoryMovementView movement)
    {
        if (movement.FromWarehouseId.HasValue)
        {
            yield return (movement.FromWarehouseId.Value, movement.StockStatus, -movement.QuantityMoved);
        }

        if (movement.ToWarehouseId.HasValue)
        {
            yield return (movement.ToWarehouseId.Value, movement.StockStatus, movement.QuantityMoved);
        }
    }

    private static InventoryMovementResponse MapMovement(InventoryMovementView movement)
        => new(
            movement.InventoryMovementId,
            movement.MovementNumber,
            movement.MovementType,
            movement.BranchId,
            movement.ItemId,
            movement.FromWarehouseId,
            movement.FromWarehouseLocationId,
            movement.ToWarehouseId,
            movement.ToWarehouseLocationId,
            movement.StockStatus,
            movement.BatchNumber,
            movement.SerialNumber,
            movement.QuantityMoved,
            movement.UnitCost,
            movement.MovedAtUtc,
            movement.StatusCode);
}
