using CashLineERP.Domain.Common;
using CashLineERP.SharedKernel.Workflow;

namespace CashLineERP.Domain.Inventory;

public sealed class InventoryAdjustment : Entity
{
    public long Id { get; private set; }

    public string AdjustmentNumber { get; private set; } = string.Empty;

    public long BranchId { get; private set; }

    public long WarehouseId { get; private set; }

    public DateOnly AdjustmentDate { get; private set; }

    public string ReasonCode { get; private set; } = string.Empty;

    public string? Notes { get; private set; }

    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;

    public decimal TotalAdjustmentAmount { get; private set; }

    public DateTime? SubmittedAtUtc { get; private set; }

    public long? SubmittedByUserId { get; private set; }

    public DateTime? ApprovedAtUtc { get; private set; }

    public long? ApprovedByUserId { get; private set; }

    public DateTime? PostedAtUtc { get; private set; }

    public long? PostedByUserId { get; private set; }

    public long? GeneralLedgerBatchId { get; private set; }

    public List<InventoryAdjustmentLine> Lines { get; private set; } = [];

    public InventoryAdjustment(
        string adjustmentNumber,
        long branchId,
        long warehouseId,
        DateOnly adjustmentDate,
        string reasonCode,
        string? notes)
    {
        AdjustmentNumber = adjustmentNumber;
        BranchId = branchId;
        WarehouseId = warehouseId;
        AdjustmentDate = adjustmentDate;
        ReasonCode = reasonCode;
        Notes = notes;
    }

    private InventoryAdjustment()
    {
    }

    public void AddLine(
        int lineNumber,
        long itemId,
        long? warehouseLocationId,
        decimal quantityDelta,
        string unitOfMeasureCode,
        decimal unitCost,
        string stockStatus,
        string? batchNumber,
        string? serialNumber,
        string? notes)
    {
        if (quantityDelta == 0m)
        {
            throw new InvalidOperationException("Inventory adjustment line quantity delta cannot be zero.");
        }

        if (unitCost < 0m)
        {
            throw new InvalidOperationException("Inventory adjustment line unit cost cannot be negative.");
        }

        Lines.Add(new InventoryAdjustmentLine(
            inventoryAdjustmentId: Id,
            lineNumber: lineNumber,
            itemId: itemId,
            warehouseLocationId: warehouseLocationId,
            quantityDelta: quantityDelta,
            unitOfMeasureCode: unitOfMeasureCode,
            unitCost: unitCost,
            stockStatus: stockStatus,
            batchNumber: batchNumber,
            serialNumber: serialNumber,
            notes: notes));

        RecalculateTotals();
    }

    public void Submit(long actorUserId)
    {
        if (StatusId != WorkflowStatusSeedIds.Draft && StatusId != WorkflowStatusSeedIds.Returned)
        {
            throw new InvalidOperationException("Only draft or returned inventory adjustments can be submitted.");
        }

        if (Lines.Count == 0)
        {
            throw new InvalidOperationException("Inventory adjustment must contain at least one line before submission.");
        }

        StatusId = WorkflowStatusSeedIds.Submitted;
        SubmittedAtUtc = DateTime.UtcNow;
        SubmittedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void Approve(long actorUserId)
    {
        if (StatusId == WorkflowStatusSeedIds.Approved)
        {
            return;
        }

        if (StatusId != WorkflowStatusSeedIds.Submitted)
        {
            throw new InvalidOperationException("Only submitted inventory adjustments can be approved.");
        }

        if (SubmittedByUserId == actorUserId)
        {
            throw new InvalidOperationException("Inventory adjustment maker-checker control prohibits self-approval.");
        }

        StatusId = WorkflowStatusSeedIds.Approved;
        ApprovedAtUtc = DateTime.UtcNow;
        ApprovedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void Post(long actorUserId, long generalLedgerBatchId)
    {
        if (StatusId == WorkflowStatusSeedIds.Posted)
        {
            GeneralLedgerBatchId ??= generalLedgerBatchId;
            return;
        }

        if (StatusId != WorkflowStatusSeedIds.Approved)
        {
            throw new InvalidOperationException("Inventory adjustment must be approved before posting.");
        }

        StatusId = WorkflowStatusSeedIds.Posted;
        GeneralLedgerBatchId = generalLedgerBatchId;
        PostedAtUtc = DateTime.UtcNow;
        PostedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void MarkSystemGeneratedPosted(long actorUserId, long generalLedgerBatchId)
    {
        StatusId = WorkflowStatusSeedIds.Posted;
        GeneralLedgerBatchId = generalLedgerBatchId;
        SubmittedAtUtc ??= DateTime.UtcNow;
        SubmittedByUserId ??= actorUserId;
        ApprovedAtUtc ??= DateTime.UtcNow;
        ApprovedByUserId ??= actorUserId;
        PostedAtUtc = DateTime.UtcNow;
        PostedByUserId = actorUserId;
        Touch(actorUserId);
    }

    private void RecalculateTotals()
    {
        TotalAdjustmentAmount = Lines.Sum(line => line.LineAmount);
    }
}
