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

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class WarehouseTransferRepository : IWarehouseTransferRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<WarehouseTransferDetailView> GetWarehouseTransfers() =>
        _dbContext.Set<WarehouseTransfer>()
            .AsNoTracking()
            .Include(transfer => transfer.Lines)
            .OrderByDescending(transfer => transfer.TransferDate)
            .ThenByDescending(transfer => transfer.Id)
            .ToArray()
            .Select(Map)
            .ToArray();

    public WarehouseTransferDetailView? GetWarehouseTransfer(long warehouseTransferId)
    {
        var transfer = _dbContext.Set<WarehouseTransfer>()
            .AsNoTracking()
            .Include(existingTransfer => existingTransfer.Lines)
            .SingleOrDefault(existingTransfer => existingTransfer.Id == warehouseTransferId);

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

    public WarehouseTransferDetailView Create(CreateWarehouseTransferCommand command)
    {
        ValidateCreateCommand(command);
        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, command.TransferDate, "Warehouse transfer");

        var nextSequence = (_dbContext.Set<WarehouseTransfer>().Max(transfer => (long?)transfer.Id) ?? 0L) + 1L;
        var transfer = new WarehouseTransfer(
            transferNumber: $"INVTRF-{nextSequence:D6}",
            branchId: command.BranchId,
            fromWarehouseId: command.FromWarehouseId,
            toWarehouseId: command.ToWarehouseId,
            transferDate: command.TransferDate,
            reasonCode: command.ReasonCode,
            notes: command.Notes);

        var lineNumber = 1;
        foreach (var line in command.Lines)
        {
            transfer.AddLine(
                lineNumber++,
                line.ItemId,
                line.FromWarehouseLocationId,
                line.ToWarehouseLocationId,
                line.QuantityTransferred,
                line.UnitOfMeasureCode,
                line.UnitCost,
                line.StockStatus,
                line.BatchNumber,
                line.SerialNumber,
                line.Notes);
        }

        _dbContext.Add(transfer);
        _dbContext.SaveChanges();
        return GetWarehouseTransfer(transfer.Id)!;
    }

    public WarehouseTransferDetailView Submit(long warehouseTransferId, long actorUserId)
    {
        var transfer = _dbContext.Set<WarehouseTransfer>()
            .Include(existingTransfer => existingTransfer.Lines)
            .Single(existingTransfer => existingTransfer.Id == warehouseTransferId);

        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, transfer.TransferDate, "Warehouse transfer");
        transfer.Submit(actorUserId);

        var workflowInstance = new WorkflowInstance(
            moduleCode: "INVENTORY",
            documentType: "INV_TRANSFER",
            documentId: transfer.Id,
            makerUserId: actorUserId,
            workflowRuleId: 1L);
        workflowInstance.Submit(actorUserId, "Warehouse transfer submitted for checker approval.");

        _dbContext.WorkflowInstances.Add(workflowInstance);
        _dbContext.SaveChanges();
        return GetWarehouseTransfer(transfer.Id)!;
    }

    public WarehouseTransferDetailView Approve(long warehouseTransferId, long actorUserId)
    {
        var transfer = _dbContext.Set<WarehouseTransfer>()
            .Include(existingTransfer => existingTransfer.Lines)
            .Single(existingTransfer => existingTransfer.Id == warehouseTransferId);

        transfer.Approve(actorUserId);

        var workflowInstance = _dbContext.WorkflowInstances
            .Include(instance => instance.Actions)
            .SingleOrDefault(instance =>
                instance.DocumentType == "INV_TRANSFER" &&
                instance.DocumentId == transfer.Id);

        workflowInstance?.Approve(actorUserId, "Warehouse transfer approved by checker.");

        _dbContext.SaveChanges();
        return GetWarehouseTransfer(transfer.Id)!;
    }

    public WarehouseTransferDetailView Post(long warehouseTransferId, long actorUserId)
    {
        var transfer = _dbContext.Set<WarehouseTransfer>()
            .Include(existingTransfer => existingTransfer.Lines)
            .Single(existingTransfer => existingTransfer.Id == warehouseTransferId);

        if (transfer.StatusId == WorkflowStatusSeedIds.Posted)
        {
            return GetWarehouseTransfer(transfer.Id)!;
        }

        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, transfer.TransferDate, "Warehouse transfer");
        transfer.Post(actorUserId);

        var nextMovementSequence = (_dbContext.InventoryMovements.Max(movement => (long?)movement.Id) ?? 0L) + 1L;
        foreach (var line in transfer.Lines.OrderBy(line => line.LineNumber))
        {
            var movement = new InventoryMovement(
                movementNumber: $"IM-{nextMovementSequence + line.LineNumber - 1:D6}",
                movementType: "WAREHOUSE_TRANSFER",
                branchId: transfer.BranchId,
                itemId: line.ItemId,
                fromWarehouseId: transfer.FromWarehouseId,
                fromWarehouseLocationId: line.FromWarehouseLocationId,
                toWarehouseId: transfer.ToWarehouseId,
                toWarehouseLocationId: line.ToWarehouseLocationId,
                batchNumber: line.BatchNumber,
                serialNumber: line.SerialNumber,
                stockStatus: line.StockStatus,
                quantityMoved: line.QuantityTransferred,
                unitCost: line.UnitCost,
                movedAtUtc: transfer.TransferDate.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc));

            _dbContext.InventoryMovements.Add(movement);
        }

        _dbContext.SaveChanges();
        return GetWarehouseTransfer(transfer.Id)!;
    }

    private static WarehouseTransferDetailView Map(WarehouseTransfer transfer) =>
        new(
            transfer.Id,
            transfer.TransferNumber,
            transfer.BranchId,
            transfer.FromWarehouseId,
            transfer.ToWarehouseId,
            transfer.TransferDate,
            transfer.ReasonCode,
            transfer.Notes,
            WorkflowStatusSeedIds.ToCode(transfer.StatusId),
            transfer.TotalTransferQuantity,
            transfer.SubmittedAtUtc,
            transfer.SubmittedByUserId,
            transfer.ApprovedAtUtc,
            transfer.ApprovedByUserId,
            transfer.PostedAtUtc,
            transfer.PostedByUserId,
            transfer.Lines.OrderBy(line => line.LineNumber)
                .Select(line => new WarehouseTransferLineView(
                    line.Id,
                    line.LineNumber,
                    line.ItemId,
                    line.FromWarehouseLocationId,
                    line.ToWarehouseLocationId,
                    line.QuantityTransferred,
                    line.UnitOfMeasureCode,
                    line.UnitCost,
                    line.StockStatus,
                    line.BatchNumber,
                    line.SerialNumber,
                    line.Notes))
                .ToArray());

    private static void ValidateCreateCommand(CreateWarehouseTransferCommand command)
    {
        if (command.BranchId <= 0)
        {
            throw new InvalidOperationException("Warehouse transfer requires a valid branch.");
        }

        if (command.FromWarehouseId <= 0 || command.ToWarehouseId <= 0)
        {
            throw new InvalidOperationException("Warehouse transfer requires valid source and destination warehouses.");
        }

        if (command.FromWarehouseId == command.ToWarehouseId)
        {
            throw new InvalidOperationException("Warehouse transfer requires different source and destination warehouses.");
        }

        if (string.IsNullOrWhiteSpace(command.ReasonCode))
        {
            throw new InvalidOperationException("Warehouse transfer requires a reason code.");
        }

        if (command.Lines.Count == 0)
        {
            throw new InvalidOperationException("Warehouse transfer requires at least one line.");
        }
    }
}
