using CashLineERP.Domain.Common;

namespace CashLineERP.Domain.Integration;

public sealed class OutboxEvent : Entity
{
    public long Id { get; private set; }
    public string EventNumber { get; private set; } = string.Empty;
    public string SourceModuleCode { get; private set; } = string.Empty;
    public string AggregateType { get; private set; } = string.Empty;
    public long AggregateId { get; private set; }
    public string EventType { get; private set; } = string.Empty;
    public string? CorrelationKey { get; private set; }
    public string PayloadText { get; private set; } = string.Empty;
    public string StatusCode { get; private set; } = "PENDING";
    public int AttemptNumber { get; private set; }
    public DateTime AvailableAtUtc { get; private set; }
    public DateTime? PublishedAtUtc { get; private set; }
    public string? PublishedByProcess { get; private set; }
    public string? LastErrorText { get; private set; }

    public OutboxEvent(
        string eventNumber,
        string sourceModuleCode,
        string aggregateType,
        long aggregateId,
        string eventType,
        string? correlationKey,
        string payloadText,
        DateTime availableAtUtc)
    {
        if (string.IsNullOrWhiteSpace(eventNumber))
        {
            throw new InvalidOperationException("Outbox event number is required.");
        }

        if (string.IsNullOrWhiteSpace(sourceModuleCode))
        {
            throw new InvalidOperationException("Outbox event source module code is required.");
        }

        if (string.IsNullOrWhiteSpace(aggregateType))
        {
            throw new InvalidOperationException("Outbox event aggregate type is required.");
        }

        if (aggregateId <= 0)
        {
            throw new InvalidOperationException("Outbox event aggregate ID must be greater than zero.");
        }

        if (string.IsNullOrWhiteSpace(eventType))
        {
            throw new InvalidOperationException("Outbox event type is required.");
        }

        if (string.IsNullOrWhiteSpace(payloadText))
        {
            throw new InvalidOperationException("Outbox event payload is required.");
        }

        EventNumber = eventNumber;
        SourceModuleCode = sourceModuleCode;
        AggregateType = aggregateType;
        AggregateId = aggregateId;
        EventType = eventType;
        CorrelationKey = string.IsNullOrWhiteSpace(correlationKey) ? null : correlationKey.Trim();
        PayloadText = payloadText;
        AvailableAtUtc = availableAtUtc;
    }

    private OutboxEvent()
    {
    }

    public void MarkPublished(string publisherCode)
    {
        if (string.IsNullOrWhiteSpace(publisherCode))
        {
            throw new InvalidOperationException("Outbox publisher code is required.");
        }

        StatusCode = "PUBLISHED";
        AttemptNumber += 1;
        PublishedAtUtc = DateTime.UtcNow;
        PublishedByProcess = publisherCode.Trim();
        LastErrorText = null;
        Touch(null);
    }

    public void ScheduleRetry(string errorText, DateTime nextAttemptAtUtc)
    {
        StatusCode = "RETRY";
        AttemptNumber += 1;
        LastErrorText = errorText;
        AvailableAtUtc = nextAttemptAtUtc;
        Touch(null);
    }

    public void MarkFailed(string errorText)
    {
        StatusCode = "FAILED";
        AttemptNumber += 1;
        LastErrorText = errorText;
        Touch(null);
    }

    public void MoveToDeadLetter(string reasonText)
    {
        if (string.IsNullOrWhiteSpace(reasonText))
        {
            throw new InvalidOperationException("Dead-letter reason is required.");
        }

        if (StatusCode != "PENDING" && StatusCode != "RETRY" && StatusCode != "FAILED")
        {
            throw new InvalidOperationException("Only pending, retry, or failed outbox events can be moved to dead-letter.");
        }

        StatusCode = "DEAD_LETTER";
        LastErrorText = reasonText.Trim();
        Touch(null);
    }

    public void Replay(string replayReasonText, DateTime nextAttemptAtUtc)
    {
        if (StatusCode != "DEAD_LETTER")
        {
            throw new InvalidOperationException("Only dead-letter outbox events can be replayed.");
        }

        StatusCode = "RETRY";
        AvailableAtUtc = nextAttemptAtUtc;
        LastErrorText = string.IsNullOrWhiteSpace(replayReasonText)
            ? null
            : $"REPLAY REQUESTED: {replayReasonText.Trim()}";
        Touch(null);
    }
}
