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

namespace CashLineERP.Domain.Finance;

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

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

    public long CustomerId { get; private set; }

    public DateOnly ReceiptDate { get; private set; }

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

    public string CurrencyCode { get; private set; } = "EGP";

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

    public decimal AmountAmount { get; private set; }

    public decimal UnappliedAmount { get; private set; }

    public string? ReferenceNumber { get; private set; }

    public string? Notes { 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 DateOnly? ReversalDate { get; private set; }

    public DateTime? ReversedAtUtc { get; private set; }

    public long? ReversedByUserId { get; private set; }

    public string? ReversalReasonCode { get; private set; }

    public string? ReversalNotes { get; private set; }

    public long? ReversalGeneralLedgerBatchId { get; private set; }

    public long? ReallocatedFromReceiptId { get; private set; }

    public long? ReallocationReceiptId { get; private set; }

    public long? GeneralLedgerBatchId { get; private set; }

    public List<AccountsReceivableReceiptAllocation> Allocations { get; private set; } = [];

    public AccountsReceivableReceipt(
        string receiptNumber,
        long customerId,
        DateOnly receiptDate,
        string receiptMethod,
        string currencyCode,
        decimal amountAmount,
        string? referenceNumber,
        string? notes)
    {
        ReceiptNumber = receiptNumber;
        CustomerId = customerId;
        ReceiptDate = receiptDate;
        ReceiptMethod = receiptMethod;
        CurrencyCode = currencyCode;
        AmountAmount = amountAmount;
        UnappliedAmount = amountAmount;
        ReferenceNumber = referenceNumber;
        Notes = notes;
    }

    private AccountsReceivableReceipt()
    {
    }

    public void AddAllocation(long accountsReceivableInvoiceId, decimal allocationAmount, DateOnly allocationDate)
    {
        if (allocationAmount <= 0m)
        {
            throw new InvalidOperationException("AR receipt allocation amount must be greater than zero.");
        }

        Allocations.Add(new AccountsReceivableReceiptAllocation(
            accountsReceivableReceiptId: Id,
            accountsReceivableInvoiceId: accountsReceivableInvoiceId,
            allocationAmount: allocationAmount,
            allocationDate: allocationDate));

        UnappliedAmount = AmountAmount - Allocations.Sum(entry => entry.AllocationAmount);
        if (UnappliedAmount < 0m)
        {
            throw new InvalidOperationException("AR receipt allocations cannot exceed receipt amount.");
        }
    }

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

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

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

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

        if (SubmittedByUserId == actorUserId)
        {
            throw new InvalidOperationException("AR receipt 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("AR receipt must be approved before posting.");
        }

        StatusId = WorkflowStatusSeedIds.Posted;
        GeneralLedgerBatchId = generalLedgerBatchId;
        PostedAtUtc = DateTime.UtcNow;
        PostedByUserId = actorUserId;
        UnappliedAmount = AmountAmount - Allocations.Sum(entry => entry.AllocationAmount);
        Touch(actorUserId);
    }

    public void Reverse(long actorUserId, DateOnly reversalDate, string reasonCode, string? notes, long reversalGeneralLedgerBatchId)
    {
        if (StatusId != WorkflowStatusSeedIds.Posted)
        {
            throw new InvalidOperationException("Only posted AR receipts can be reversed.");
        }

        StatusId = WorkflowStatusSeedIds.Reversed;
        ReversalDate = reversalDate;
        ReversedAtUtc = DateTime.UtcNow;
        ReversedByUserId = actorUserId;
        ReversalReasonCode = reasonCode;
        ReversalNotes = notes;
        ReversalGeneralLedgerBatchId = reversalGeneralLedgerBatchId;
        Touch(actorUserId);
    }

    public void MarkAsReallocation(long sourceReceiptId, long actorUserId)
    {
        ReallocatedFromReceiptId = sourceReceiptId;
        Touch(actorUserId);
    }

    public void LinkReallocationReceipt(long replacementReceiptId, long actorUserId)
    {
        if (StatusId != WorkflowStatusSeedIds.Reversed)
        {
            throw new InvalidOperationException("Only reversed AR receipts can link to a reallocation receipt.");
        }

        if (ReallocationReceiptId.HasValue)
        {
            throw new InvalidOperationException("AR receipt already has a linked reallocation receipt.");
        }

        ReallocationReceiptId = replacementReceiptId;
        Touch(actorUserId);
    }
}
