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

namespace CashLineERP.Domain.Finance;

public sealed class CustomerRefund : Entity
{
    public long Id { get; private set; }
    public string RefundNumber { get; private set; } = string.Empty;
    public long CustomerId { get; private set; }
    public long SourceAccountsReceivableReceiptId { get; private set; }
    public DateOnly RefundDate { get; private set; }
    public string RefundMethod { get; private set; } = string.Empty;
    public string CurrencyCode { get; private set; } = "EGP";
    public decimal AmountAmount { get; private set; }
    public long? PaymentChannelConfigurationId { get; private set; }
    public string? ReferenceNumber { get; private set; }
    public string? Notes { get; private set; }
    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;
    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? ReturnedAtUtc { get; private set; }
    public long? ReturnedByUserId { get; private set; }
    public string? ReturnNotes { get; private set; }
    public DateTime? RejectedAtUtc { get; private set; }
    public long? RejectedByUserId { get; private set; }
    public string? RejectionNotes { get; private set; }
    public DateTime? PostedAtUtc { get; private set; }
    public long? PostedByUserId { get; private set; }
    public long? GeneralLedgerBatchId { get; private set; }

    public CustomerRefund(
        string refundNumber,
        long customerId,
        long sourceAccountsReceivableReceiptId,
        DateOnly refundDate,
        string refundMethod,
        string currencyCode,
        decimal amountAmount,
        long? paymentChannelConfigurationId,
        string? referenceNumber,
        string? notes)
    {
        if (string.IsNullOrWhiteSpace(refundNumber))
        {
            throw new InvalidOperationException("Customer refund number is required.");
        }

        if (amountAmount <= 0m)
        {
            throw new InvalidOperationException("Customer refund amount must be greater than zero.");
        }

        RefundNumber = refundNumber;
        CustomerId = customerId;
        SourceAccountsReceivableReceiptId = sourceAccountsReceivableReceiptId;
        RefundDate = refundDate;
        RefundMethod = refundMethod;
        CurrencyCode = currencyCode;
        AmountAmount = amountAmount;
        PaymentChannelConfigurationId = paymentChannelConfigurationId;
        ReferenceNumber = referenceNumber;
        Notes = notes;
    }

    private CustomerRefund()
    {
    }

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

        StatusId = WorkflowStatusSeedIds.Submitted;
        SubmittedAtUtc = DateTime.UtcNow;
        SubmittedByUserId = actorUserId;
        ApprovedAtUtc = null;
        ApprovedByUserId = null;
        ReturnedAtUtc = null;
        ReturnedByUserId = null;
        ReturnNotes = null;
        RejectedAtUtc = null;
        RejectedByUserId = null;
        RejectionNotes = null;
        Touch(actorUserId);
    }

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

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

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

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

    public void Return(long actorUserId, string reviewNotes)
    {
        if (StatusId != WorkflowStatusSeedIds.Submitted)
        {
            throw new InvalidOperationException("Only submitted customer refunds can be returned.");
        }

        if (string.IsNullOrWhiteSpace(reviewNotes))
        {
            throw new InvalidOperationException("Customer refund return notes are required.");
        }

        StatusId = WorkflowStatusSeedIds.Returned;
        ReturnedAtUtc = DateTime.UtcNow;
        ReturnedByUserId = actorUserId;
        ReturnNotes = reviewNotes.Trim();
        Touch(actorUserId);
    }

    public void Reject(long actorUserId, string reviewNotes)
    {
        if (StatusId != WorkflowStatusSeedIds.Submitted)
        {
            throw new InvalidOperationException("Only submitted customer refunds can be rejected.");
        }

        if (string.IsNullOrWhiteSpace(reviewNotes))
        {
            throw new InvalidOperationException("Customer refund rejection notes are required.");
        }

        StatusId = WorkflowStatusSeedIds.Rejected;
        RejectedAtUtc = DateTime.UtcNow;
        RejectedByUserId = actorUserId;
        RejectionNotes = reviewNotes.Trim();
        Touch(actorUserId);
    }

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

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

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