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

namespace CashLineERP.Domain.Finance;

public sealed class PaymentSettlementBatch : Entity
{
    public long Id { get; private set; }
    public string BatchNumber { get; private set; } = string.Empty;
    public long PaymentChannelConfigurationId { get; private set; }
    public DateOnly SettlementDate { get; private set; }
    public string CurrencyCode { get; private set; } = "EGP";
    public string? ExternalBatchReference { get; private set; }
    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;
    public int TotalTransactionCount { get; private set; }
    public decimal TotalInternalAmount { get; private set; }
    public decimal TotalSettledAmount { get; private set; }
    public decimal TotalFeeAmount { get; private set; }
    public decimal TotalNetAmount { get; private set; }
    public int OpenExceptionCount { get; private set; }
    public int ResolvedExceptionCount { 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 List<PaymentSettlementBatchLine> Lines { get; private set; } = [];
    public List<PaymentSettlementException> Exceptions { get; private set; } = [];

    public PaymentSettlementBatch(
        string batchNumber,
        long paymentChannelConfigurationId,
        DateOnly settlementDate,
        string currencyCode,
        string? externalBatchReference)
    {
        if (string.IsNullOrWhiteSpace(batchNumber))
        {
            throw new InvalidOperationException("Payment settlement batch number is required.");
        }

        if (paymentChannelConfigurationId <= 0)
        {
            throw new InvalidOperationException("Payment settlement batch requires a valid payment channel.");
        }

        if (string.IsNullOrWhiteSpace(currencyCode))
        {
            throw new InvalidOperationException("Payment settlement batch currency code is required.");
        }

        BatchNumber = batchNumber.Trim().ToUpperInvariant();
        PaymentChannelConfigurationId = paymentChannelConfigurationId;
        SettlementDate = settlementDate;
        CurrencyCode = currencyCode.Trim().ToUpperInvariant();
        ExternalBatchReference = string.IsNullOrWhiteSpace(externalBatchReference) ? null : externalBatchReference.Trim();
    }

    private PaymentSettlementBatch()
    {
    }

    public void AddLine(
        string sourceDocumentType,
        long sourceDocumentId,
        string sourceDocumentNumber,
        long? partyId,
        decimal internalAmount,
        decimal settledAmount,
        decimal feeAmount,
        decimal netAmount,
        string? providerReference,
        string? notes,
        bool hasException)
    {
        if (StatusId != WorkflowStatusSeedIds.Draft)
        {
            throw new InvalidOperationException("Payment settlement lines can only be added while the batch is draft.");
        }

        if (Lines.Any(line => line.SourceDocumentType == sourceDocumentType.Trim().ToUpperInvariant() && line.SourceDocumentId == sourceDocumentId))
        {
            throw new InvalidOperationException("Payment settlement batch cannot contain duplicate source documents.");
        }

        Lines.Add(new PaymentSettlementBatchLine(
            paymentSettlementBatchId: Id,
            sourceDocumentType: sourceDocumentType,
            sourceDocumentId: sourceDocumentId,
            sourceDocumentNumber: sourceDocumentNumber,
            partyId: partyId,
            internalAmount: internalAmount,
            settledAmount: settledAmount,
            feeAmount: feeAmount,
            netAmount: netAmount,
            providerReference: providerReference,
            notes: notes,
            settlementStatusCode: hasException ? "EXCEPTION" : "MATCHED"));

        RecalculateTotals();
    }

    public void RefreshExceptionSummary(int openExceptionCount, int resolvedExceptionCount)
    {
        if (openExceptionCount < 0 || resolvedExceptionCount < 0)
        {
            throw new InvalidOperationException("Payment settlement exception summary cannot be negative.");
        }

        OpenExceptionCount = openExceptionCount;
        ResolvedExceptionCount = resolvedExceptionCount;
    }

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

        if (Lines.Count == 0)
        {
            throw new InvalidOperationException("Payment settlement batch must contain at least one settlement line.");
        }

        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 payment settlement batches can be approved.");
        }

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

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

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

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

        if (OpenExceptionCount > 0)
        {
            throw new InvalidOperationException("Payment settlement batch cannot be posted while open settlement exceptions remain.");
        }

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

    public void RefreshTotals()
    {
        RecalculateTotals();
    }

    private void RecalculateTotals()
    {
        TotalTransactionCount = Lines.Count;
        TotalInternalAmount = Lines.Sum(line => line.InternalAmount);
        TotalSettledAmount = Lines.Sum(line => line.SettledAmount);
        TotalFeeAmount = Lines.Sum(line => line.FeeAmount);
        TotalNetAmount = Lines.Sum(line => line.NetAmount);
    }
}
