using CashLineERP.Application.Procurement;
using CashLineERP.Domain.Inventory;
using CashLineERP.Domain.Procurement;
using CashLineERP.SharedKernel.Workflow;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class GoodsReceiptRepository : IGoodsReceiptRepository
{
    private readonly CashLineErpDbContext _dbContext;

    public GoodsReceiptRepository(CashLineErpDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IReadOnlyCollection<GoodsReceiptDetailView> GetGoodsReceipts() =>
        _dbContext.GoodsReceipts
            .AsNoTracking()
            .Include(goodsReceipt => goodsReceipt.Lines)
            .OrderByDescending(goodsReceipt => goodsReceipt.ReceivedAtUtc)
            .ThenByDescending(goodsReceipt => goodsReceipt.Id)
            .ToArray()
            .Select(Map)
            .ToArray();

    public GoodsReceiptDetailView? GetGoodsReceipt(long goodsReceiptId)
    {
        var goodsReceipt = _dbContext.GoodsReceipts
            .AsNoTracking()
            .Include(existingGoodsReceipt => existingGoodsReceipt.Lines)
            .SingleOrDefault(existingGoodsReceipt => existingGoodsReceipt.Id == goodsReceiptId);

        return goodsReceipt is null ? null : Map(goodsReceipt);
    }

    public GoodsReceiptDetailView Create(CreateGoodsReceiptCommand command)
    {
        var nextSequence = (_dbContext.GoodsReceipts.Max(goodsReceipt => (long?)goodsReceipt.Id) ?? 0L) + 1L;
        var goodsReceipt = new GoodsReceipt(
            goodsReceiptNumber: $"GRN-{nextSequence:D6}",
            branchId: command.BranchId,
            purchaseOrderId: command.PurchaseOrderId,
            supplierId: command.SupplierId,
            receivedAtUtc: command.ReceivedAtUtc,
            receivedByUserId: command.ReceivedByUserId,
            inspectionStatus: command.InspectionStatus);

        var lineNumber = 1;
        foreach (var line in command.Lines)
        {
            goodsReceipt.AddLine(
                lineNumber++,
                line.PurchaseOrderLineId,
                line.ItemId,
                line.WarehouseId,
                line.WarehouseLocationId,
                line.QuantityReceived,
                line.QuantityAccepted,
                line.QuantityRejected,
                line.BatchNumber,
                line.SerialNumber);
        }

        _dbContext.GoodsReceipts.Add(goodsReceipt);
        _dbContext.SaveChanges();

        return GetGoodsReceipt(goodsReceipt.Id)!;
    }

    public GoodsReceiptDetailView Post(long goodsReceiptId, long actorUserId)
    {
        var goodsReceipt = _dbContext.GoodsReceipts
            .Include(existingGoodsReceipt => existingGoodsReceipt.Lines)
            .Single(existingGoodsReceipt => existingGoodsReceipt.Id == goodsReceiptId);

        goodsReceipt.Post(actorUserId);

        var nextSequence = (_dbContext.InventoryMovements.Max(movement => (long?)movement.Id) ?? 0L) + 1L;
        foreach (var line in goodsReceipt.Lines.OrderBy(line => line.LineNumber))
        {
            var purchaseOrderLine = _dbContext.PurchaseOrderLines.SingleOrDefault(existingLine => existingLine.Id == line.PurchaseOrderLineId);
            purchaseOrderLine?.RegisterReceipt(line.QuantityAccepted);

            if (line.QuantityAccepted <= 0m)
            {
                continue;
            }

            var movement = new InventoryMovement(
                movementNumber: $"IM-{nextSequence + line.LineNumber - 1:D6}",
                movementType: "GRN_RECEIPT",
                branchId: goodsReceipt.BranchId,
                itemId: line.ItemId,
                fromWarehouseId: null,
                fromWarehouseLocationId: null,
                toWarehouseId: line.WarehouseId,
                toWarehouseLocationId: line.WarehouseLocationId,
                batchNumber: line.BatchNumber,
                serialNumber: line.SerialNumber,
                stockStatus: "AVAILABLE",
                quantityMoved: line.QuantityAccepted,
                unitCost: null,
                movedAtUtc: goodsReceipt.ReceivedAtUtc);

            _dbContext.InventoryMovements.Add(movement);
        }

        _dbContext.SaveChanges();
        return GetGoodsReceipt(goodsReceipt.Id)!;
    }

    private static GoodsReceiptDetailView Map(GoodsReceipt goodsReceipt) =>
        new(
            goodsReceipt.Id,
            goodsReceipt.GoodsReceiptNumber,
            goodsReceipt.BranchId,
            goodsReceipt.PurchaseOrderId,
            goodsReceipt.SupplierId,
            goodsReceipt.ReceivedAtUtc,
            goodsReceipt.ReceivedByUserId,
            WorkflowStatusSeedIds.ToCode(goodsReceipt.StatusId),
            goodsReceipt.InspectionStatus,
            goodsReceipt.Lines.OrderBy(line => line.LineNumber).Select(line => new GoodsReceiptLineView(
                line.Id,
                line.LineNumber,
                line.PurchaseOrderLineId,
                line.ItemId,
                line.WarehouseId,
                line.WarehouseLocationId,
                line.QuantityReceived,
                line.QuantityAccepted,
                line.QuantityRejected,
                line.BatchNumber,
                line.SerialNumber)).ToArray());
}
