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 InventoryStatusControlRepository : IInventoryStatusControlRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<InventoryStatusControlDetailView> GetInventoryStatusControls() =>
        _dbContext.InventoryStatusControls
            .AsNoTracking()
            .Include(control => control.Lines)
            .OrderByDescending(control => control.ControlDate)
            .ThenByDescending(control => control.Id)
            .ToArray()
            .Select(Map)
            .ToArray();

    public InventoryStatusControlDetailView? GetInventoryStatusControl(long inventoryStatusControlId)
    {
        var control = _dbContext.InventoryStatusControls
            .AsNoTracking()
            .Include(existingControl => existingControl.Lines)
            .SingleOrDefault(existingControl => existingControl.Id == inventoryStatusControlId);

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

    public InventoryStatusControlDetailView Create(CreateInventoryStatusControlCommand command)
    {
        ValidateCreateCommand(command);
        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, command.ControlDate, "Inventory status control");

        var nextSequence = (_dbContext.InventoryStatusControls.Max(control => (long?)control.Id) ?? 0L) + 1L;
        var control = new InventoryStatusControl(
            controlNumber: $"INVSTAT-{nextSequence:D6}",
            branchId: command.BranchId,
            warehouseId: command.WarehouseId,
            controlDate: command.ControlDate,
            reasonCode: command.ReasonCode,
            notes: command.Notes);

        var lineNumber = 1;
        foreach (var line in command.Lines)
        {
            control.AddLine(
                lineNumber++,
                line.ItemId,
                line.WarehouseLocationId,
                line.QuantityAffected,
                line.UnitOfMeasureCode,
                line.UnitCost,
                line.CurrentStockStatus,
                line.TargetStockStatus,
                line.BatchNumber,
                line.SerialNumber,
                line.ExpiryDate,
                line.Notes);
        }

        _dbContext.InventoryStatusControls.Add(control);
        _dbContext.SaveChanges();
        return GetInventoryStatusControl(control.Id)!;
    }

    public InventoryStatusControlDetailView Submit(long inventoryStatusControlId, long actorUserId)
    {
        var control = _dbContext.InventoryStatusControls
            .Include(existingControl => existingControl.Lines)
            .Single(existingControl => existingControl.Id == inventoryStatusControlId);

        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, control.ControlDate, "Inventory status control");
        control.Submit(actorUserId);

        var workflowInstance = new WorkflowInstance(
            moduleCode: "INVENTORY",
            documentType: "STOCK_STATUS_CONTROL",
            documentId: control.Id,
            makerUserId: actorUserId,
            workflowRuleId: 1L);
        workflowInstance.Submit(actorUserId, "Stock-status control submitted for checker approval.");

        _dbContext.WorkflowInstances.Add(workflowInstance);
        _dbContext.SaveChanges();
        return GetInventoryStatusControl(control.Id)!;
    }

    public InventoryStatusControlDetailView Approve(long inventoryStatusControlId, long actorUserId)
    {
        var control = _dbContext.InventoryStatusControls
            .Include(existingControl => existingControl.Lines)
            .Single(existingControl => existingControl.Id == inventoryStatusControlId);

        control.Approve(actorUserId);

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

        workflowInstance?.Approve(actorUserId, "Stock-status control approved by checker.");

        _dbContext.SaveChanges();
        return GetInventoryStatusControl(control.Id)!;
    }

    public InventoryStatusControlDetailView Post(long inventoryStatusControlId, long actorUserId)
    {
        var control = _dbContext.InventoryStatusControls
            .Include(existingControl => existingControl.Lines)
            .Single(existingControl => existingControl.Id == inventoryStatusControlId);

        if (control.StatusId == WorkflowStatusSeedIds.Posted)
        {
            return GetInventoryStatusControl(control.Id)!;
        }

        AccountingPeriodLockGuard.RequireOpenPeriod(_dbContext, control.ControlDate, "Inventory status control");

        var nextMovementSequence = (_dbContext.InventoryMovements.Max(movement => (long?)movement.Id) ?? 0L) + 1L;
        foreach (var line in control.Lines.OrderBy(line => line.LineNumber))
        {
            var effectiveMovedAtUtc = control.ControlDate.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);

            var outMovement = new InventoryMovement(
                movementNumber: $"IM-{nextMovementSequence:D6}",
                movementType: "STATUS_RECLASS_OUT",
                branchId: control.BranchId,
                itemId: line.ItemId,
                fromWarehouseId: control.WarehouseId,
                fromWarehouseLocationId: line.WarehouseLocationId,
                toWarehouseId: null,
                toWarehouseLocationId: null,
                batchNumber: line.BatchNumber,
                serialNumber: line.SerialNumber,
                stockStatus: line.CurrentStockStatus,
                quantityMoved: line.QuantityAffected,
                unitCost: line.UnitCost,
                movedAtUtc: effectiveMovedAtUtc);

            var inMovement = new InventoryMovement(
                movementNumber: $"IM-{nextMovementSequence + 1L:D6}",
                movementType: "STATUS_RECLASS_IN",
                branchId: control.BranchId,
                itemId: line.ItemId,
                fromWarehouseId: null,
                fromWarehouseLocationId: null,
                toWarehouseId: control.WarehouseId,
                toWarehouseLocationId: line.WarehouseLocationId,
                batchNumber: line.BatchNumber,
                serialNumber: line.SerialNumber,
                stockStatus: line.TargetStockStatus,
                quantityMoved: line.QuantityAffected,
                unitCost: line.UnitCost,
                movedAtUtc: effectiveMovedAtUtc);

            _dbContext.InventoryMovements.Add(outMovement);
            _dbContext.InventoryMovements.Add(inMovement);
            nextMovementSequence += 2L;
        }

        control.Post(actorUserId);
        _dbContext.SaveChanges();
        return GetInventoryStatusControl(control.Id)!;
    }

    private static InventoryStatusControlDetailView Map(InventoryStatusControl control) =>
        new(
            control.Id,
            control.ControlNumber,
            control.BranchId,
            control.WarehouseId,
            control.ControlDate,
            control.ReasonCode,
            control.Notes,
            WorkflowStatusSeedIds.ToCode(control.StatusId),
            control.TotalQuantityAffected,
            control.SubmittedAtUtc,
            control.SubmittedByUserId,
            control.ApprovedAtUtc,
            control.ApprovedByUserId,
            control.PostedAtUtc,
            control.PostedByUserId,
            control.Lines
                .OrderBy(line => line.LineNumber)
                .Select(line => new InventoryStatusControlLineView(
                    line.Id,
                    line.LineNumber,
                    line.ItemId,
                    line.WarehouseLocationId,
                    line.QuantityAffected,
                    line.UnitOfMeasureCode,
                    line.UnitCost,
                    line.CurrentStockStatus,
                    line.TargetStockStatus,
                    line.BatchNumber,
                    line.SerialNumber,
                    line.ExpiryDate,
                    line.Notes))
                .ToArray());

    private static void ValidateCreateCommand(CreateInventoryStatusControlCommand command)
    {
        if (command.Lines.Count == 0)
        {
            throw new InvalidOperationException("Inventory status control must contain at least one line.");
        }

        if (string.IsNullOrWhiteSpace(command.ReasonCode))
        {
            throw new InvalidOperationException("Inventory status control reason code is required.");
        }
    }
}
