using CashLineERP.Domain.Common;

namespace CashLineERP.Domain.Finance;

public sealed class PaymentGatewayExecution : Entity
{
    public long Id { get; private set; }
    public string ExecutionNumber { get; private set; } = string.Empty;
    public long PaymentChannelConfigurationId { get; private set; }
    public long IntegrationEndpointId { get; private set; }
    public string SourceDocumentType { get; private set; } = string.Empty;
    public long SourceDocumentId { get; private set; }
    public string SourceDocumentNumber { get; private set; } = string.Empty;
    public long? PartyId { get; private set; }
    public string CurrencyCode { get; private set; } = string.Empty;
    public decimal AmountAmount { get; private set; }
    public string CorrelationKey { get; private set; } = string.Empty;
    public string StatusCode { get; private set; } = "QUEUED";
    public string? ProviderReference { get; private set; }
    public string? ProviderStatusCode { get; private set; }
    public string? ProviderResponseText { get; private set; }
    public int AttemptNumber { get; private set; }
    public DateTime? DispatchedAtUtc { get; private set; }
    public string? DispatchedByProcess { get; private set; }
    public DateTime? CompletedAtUtc { get; private set; }
    public string? LastErrorText { get; private set; }

    public PaymentGatewayExecution(
        string executionNumber,
        long paymentChannelConfigurationId,
        long integrationEndpointId,
        string sourceDocumentType,
        long sourceDocumentId,
        string sourceDocumentNumber,
        long? partyId,
        string currencyCode,
        decimal amountAmount,
        string correlationKey)
    {
        if (string.IsNullOrWhiteSpace(executionNumber))
        {
            throw new InvalidOperationException("Payment gateway execution number is required.");
        }

        if (paymentChannelConfigurationId <= 0 || integrationEndpointId <= 0)
        {
            throw new InvalidOperationException("Payment gateway execution requires valid channel and endpoint identifiers.");
        }

        if (string.IsNullOrWhiteSpace(sourceDocumentType) || string.IsNullOrWhiteSpace(sourceDocumentNumber))
        {
            throw new InvalidOperationException("Payment gateway execution requires source document metadata.");
        }

        if (sourceDocumentId <= 0)
        {
            throw new InvalidOperationException("Payment gateway execution requires a valid source document.");
        }

        if (string.IsNullOrWhiteSpace(currencyCode))
        {
            throw new InvalidOperationException("Payment gateway execution currency is required.");
        }

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

        if (string.IsNullOrWhiteSpace(correlationKey))
        {
            throw new InvalidOperationException("Payment gateway execution correlation key is required.");
        }

        ExecutionNumber = executionNumber.Trim().ToUpperInvariant();
        PaymentChannelConfigurationId = paymentChannelConfigurationId;
        IntegrationEndpointId = integrationEndpointId;
        SourceDocumentType = sourceDocumentType.Trim().ToUpperInvariant();
        SourceDocumentId = sourceDocumentId;
        SourceDocumentNumber = sourceDocumentNumber.Trim().ToUpperInvariant();
        PartyId = partyId;
        CurrencyCode = currencyCode.Trim().ToUpperInvariant();
        AmountAmount = decimal.Round(amountAmount, 2, MidpointRounding.AwayFromZero);
        CorrelationKey = correlationKey.Trim();
    }

    private PaymentGatewayExecution()
    {
    }

    public void Dispatch(string processorCode, string? providerReference, string? providerResponseText)
    {
        if (StatusCode != "QUEUED" && StatusCode != "RETRY")
        {
            throw new InvalidOperationException("Only queued or retry payment gateway executions can be dispatched.");
        }

        if (string.IsNullOrWhiteSpace(processorCode))
        {
            throw new InvalidOperationException("Payment gateway execution dispatch processor code is required.");
        }

        StatusCode = "DISPATCHED";
        AttemptNumber += 1;
        ProviderReference = string.IsNullOrWhiteSpace(providerReference) ? ProviderReference : providerReference.Trim();
        ProviderResponseText = string.IsNullOrWhiteSpace(providerResponseText) ? ProviderResponseText : providerResponseText.Trim();
        DispatchedAtUtc = DateTime.UtcNow;
        DispatchedByProcess = processorCode.Trim().ToUpperInvariant();
        LastErrorText = null;
        Touch(null);
    }

    public void Acknowledge(string providerStatusCode, string? providerReference, string? providerResponseText)
    {
        ApplyProviderUpdate("ACKNOWLEDGED", providerStatusCode, providerReference, providerResponseText, completed: false);
    }

    public void MarkSettled(string providerStatusCode, string? providerReference, string? providerResponseText)
    {
        ApplyProviderUpdate("SETTLED", providerStatusCode, providerReference, providerResponseText, completed: true);
    }

    public void MarkFailed(string errorText, string? providerStatusCode, string? providerResponseText)
    {
        if (string.IsNullOrWhiteSpace(errorText))
        {
            throw new InvalidOperationException("Payment gateway execution failure reason is required.");
        }

        StatusCode = "FAILED";
        ProviderStatusCode = string.IsNullOrWhiteSpace(providerStatusCode) ? ProviderStatusCode : providerStatusCode.Trim().ToUpperInvariant();
        ProviderResponseText = string.IsNullOrWhiteSpace(providerResponseText) ? ProviderResponseText : providerResponseText.Trim();
        CompletedAtUtc = DateTime.UtcNow;
        LastErrorText = errorText.Trim();
        Touch(null);
    }

    public void ScheduleRetry(string errorText, DateTime availableAtUtc)
    {
        if (string.IsNullOrWhiteSpace(errorText))
        {
            throw new InvalidOperationException("Payment gateway retry reason is required.");
        }

        StatusCode = "RETRY";
        LastErrorText = errorText.Trim();
        CompletedAtUtc = null;
        Touch(null);
    }

    private void ApplyProviderUpdate(string targetStatusCode, string providerStatusCode, string? providerReference, string? providerResponseText, bool completed)
    {
        if (string.IsNullOrWhiteSpace(providerStatusCode))
        {
            throw new InvalidOperationException("Provider status code is required.");
        }

        StatusCode = targetStatusCode;
        ProviderStatusCode = providerStatusCode.Trim().ToUpperInvariant();
        ProviderReference = string.IsNullOrWhiteSpace(providerReference) ? ProviderReference : providerReference.Trim();
        ProviderResponseText = string.IsNullOrWhiteSpace(providerResponseText) ? ProviderResponseText : providerResponseText.Trim();
        LastErrorText = null;
        CompletedAtUtc = completed ? DateTime.UtcNow : null;
        Touch(null);
    }
}
