using System.Text.Json;
using CashLineERP.Application.Integration;
using CashLineERP.Contracts.Integration;
using CashLineERP.Contracts.Tax;

namespace CashLineERP.Application.Tax;

public sealed class EtaAuthorityCallbackService : IEtaAuthorityCallbackService
{
    private const long EtaCallbackSystemUserId = 1L;
    private readonly IEtaAuthorityAuthService _etaAuthorityAuthService;
    private readonly IEtaComplianceService _etaComplianceService;
    private readonly IInboxMessageRepository _inboxMessageRepository;
    private readonly IInboxMessageService _inboxMessageService;

    public EtaAuthorityCallbackService(
        IEtaAuthorityAuthService etaAuthorityAuthService,
        IEtaComplianceService etaComplianceService,
        IInboxMessageRepository inboxMessageRepository,
        IInboxMessageService inboxMessageService)
    {
        _etaAuthorityAuthService = etaAuthorityAuthService;
        _etaComplianceService = etaComplianceService;
        _inboxMessageRepository = inboxMessageRepository;
        _inboxMessageService = inboxMessageService;
    }

    public EtaDocumentNotificationReceiptResponse ReceiveDocumentNotification(string? authorizationHeader, string payloadText)
    {
        var endpoint = _etaAuthorityAuthService.GetDocumentNotificationEndpoint();
        if (endpoint is null)
        {
            return new EtaDocumentNotificationReceiptResponse(
                false,
                "ENDPOINT_NOT_CONFIGURED",
                "ETA document notification endpoint is not configured.",
                null,
                null,
                null,
                "ETA_DOCUMENT_NOTIFICATION",
                DateTime.UtcNow,
                false,
                null,
                null,
                null,
                null);
        }

        var expectedSecret = _etaAuthorityAuthService.ResolveDocumentNotificationSharedSecret();
        if (string.IsNullOrWhiteSpace(expectedSecret))
        {
            return new EtaDocumentNotificationReceiptResponse(
                false,
                "SECRET_NOT_RESOLVED",
                "ETA document notification secret is not resolvable by the current runtime.",
                null,
                null,
                null,
                "ETA_DOCUMENT_NOTIFICATION",
                DateTime.UtcNow,
                false,
                null,
                null,
                null,
                null);
        }

        var suppliedSecret = NormalizeAuthorizationSecret(authorizationHeader);
        if (!string.Equals(expectedSecret, suppliedSecret, StringComparison.Ordinal))
        {
            return new EtaDocumentNotificationReceiptResponse(
                false,
                "AUTHORIZATION_FAILED",
                "ETA document notification authorization header did not match the configured secret.",
                null,
                null,
                null,
                "ETA_DOCUMENT_NOTIFICATION",
                DateTime.UtcNow,
                false,
                null,
                null,
                null,
                null);
        }

        var metadata = ParseNotificationMetadata(payloadText);
        var existing = _inboxMessageRepository.FindExistingMessage(
            endpoint.EndpointId,
            metadata.ExternalMessageId,
            metadata.CorrelationKey);

        if (existing is not null)
        {
            var replayReconciliation = ReconcileNotification(metadata, payloadText);
            if (replayReconciliation.Reconciled && existing.StatusCode is "RECEIVED" or "RETRY")
            {
                _inboxMessageRepository.MarkProcessed(existing.InboxMessageId, "ETA_CALLBACK");
            }

            return new EtaDocumentNotificationReceiptResponse(
                true,
                "IDEMPOTENT_REPLAY",
                $"ETA document notification was already received through endpoint {endpoint.EndpointCode}.",
                existing.InboxMessageId,
                existing.ExternalMessageId,
                existing.CorrelationKey,
                existing.MessageType,
                DateTime.UtcNow,
                replayReconciliation.Reconciled,
                replayReconciliation.ReconciledTransmissionId,
                replayReconciliation.ReconciliationOutcome,
                replayReconciliation.ReconciliationErrorCode,
                replayReconciliation.ReconciliationErrorMessage);
        }

        var received = _inboxMessageService.ReceiveInboxMessage(
            EtaCallbackSystemUserId,
            new ReceiveInboxMessageRequest(
                endpoint.EndpointId,
                metadata.MessageType,
                metadata.ExternalMessageId,
                metadata.CorrelationKey,
                payloadText));

        var reconciliation = ReconcileNotification(metadata, payloadText);
        if (reconciliation.Reconciled)
        {
            _inboxMessageRepository.MarkProcessed(received.InboxMessageId, "ETA_CALLBACK");
        }

        return new EtaDocumentNotificationReceiptResponse(
            true,
            "ACCEPTED",
            $"ETA document notification accepted through endpoint {endpoint.EndpointCode}.",
            received.InboxMessageId,
            received.ExternalMessageId,
            received.CorrelationKey,
            received.MessageType,
            DateTime.UtcNow,
            reconciliation.Reconciled,
            reconciliation.ReconciledTransmissionId,
            reconciliation.ReconciliationOutcome,
            reconciliation.ReconciliationErrorCode,
            reconciliation.ReconciliationErrorMessage);
    }

    public ProcessEtaDocumentNotificationBatchResultResponse ProcessPendingNotifications(ProcessEtaDocumentNotificationBatchRequest request)
    {
        var processorCode = request.ProcessorCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(processorCode))
        {
            throw new InvalidOperationException("ETA notification processor code is required.");
        }

        if (request.BatchSize is < 1 or > 100)
        {
            throw new InvalidOperationException("ETA notification batch size must be between 1 and 100.");
        }

        if (request.RetryDelaySeconds is < 0 or > 86400)
        {
            throw new InvalidOperationException("ETA notification retry delay must be between 0 and 86400 seconds.");
        }

        var pendingMessages = _inboxMessageRepository.GetPendingMessages(request.BatchSize, "ETA");
        var processedIds = new List<long>();
        var reconciledTransmissionIds = new HashSet<long>();
        var reconciledCount = 0;
        var retryCount = 0;
        var failedCount = 0;

        foreach (var message in pendingMessages)
        {
            try
            {
                var metadata = ParseNotificationMetadata(message.PayloadText, message.ExternalMessageId, message.CorrelationKey);
                var receipt = ApplyNotificationProcessing(message, metadata, processorCode, request.RetryDelaySeconds);

                if (receipt.StatusCode == "PROCESSED")
                {
                    processedIds.Add(message.InboxMessageId);
                    reconciledCount += 1;
                    if (receipt.ReconciledTransmissionId.HasValue)
                    {
                        reconciledTransmissionIds.Add(receipt.ReconciledTransmissionId.Value);
                    }

                    continue;
                }

                if (receipt.StatusCode == "RETRY_SCHEDULED")
                {
                    retryCount += 1;
                    continue;
                }

                failedCount += 1;
            }
            catch (Exception ex)
            {
                if (message.AttemptNumber < message.MaxRetryCount)
                {
                    _inboxMessageRepository.ScheduleRetry(message.InboxMessageId, ex.Message, request.RetryDelaySeconds);
                    retryCount += 1;
                }
                else
                {
                    _inboxMessageRepository.MarkFailed(message.InboxMessageId, ex.Message);
                    failedCount += 1;
                }
            }
        }

        return new ProcessEtaDocumentNotificationBatchResultResponse(
            processorCode,
            request.BatchSize,
            pendingMessages.Count,
            reconciledCount,
            retryCount,
            failedCount,
            processedIds,
            reconciledTransmissionIds.Order().ToArray());
    }

    public EtaNotificationProcessingReceiptResponse ProcessNotification(long inboxMessageId, string processorCode, int retryDelaySeconds = 300)
    {
        var normalizedProcessorCode = processorCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(normalizedProcessorCode))
        {
            throw new InvalidOperationException("ETA notification processor code is required.");
        }

        if (retryDelaySeconds is < 0 or > 86400)
        {
            throw new InvalidOperationException("ETA notification retry delay must be between 0 and 86400 seconds.");
        }

        var message = _inboxMessageRepository.GetInboxMessage(inboxMessageId)
            ?? throw new InvalidOperationException("ETA inbox notification was not found.");
        EnsureEtaProviderMessage(message);

        var metadata = ParseNotificationMetadata(message.PayloadText, message.ExternalMessageId, message.CorrelationKey);
        return ApplyNotificationProcessing(message, metadata, normalizedProcessorCode, retryDelaySeconds);
    }

    private CallbackReconciliationResult ReconcileNotification(NotificationMetadata metadata, string payloadText)
    {
        if (string.IsNullOrWhiteSpace(metadata.CorrelationKey))
        {
            return CallbackReconciliationResult.NotReconciled();
        }

        var transmission = _etaComplianceService.FindPendingTransmissionByCorrelation(metadata.CorrelationKey);
        if (transmission is null)
        {
            return CallbackReconciliationResult.NotReconciled();
        }

        var normalizedOutcome = NormalizeAuthorityStatus(metadata.MessageType, metadata.StatusCode);
        if (normalizedOutcome == "ACCEPTED")
        {
            var etaUuid = metadata.CorrelationKey
                ?? metadata.DocumentUuid
                ?? transmission.TransmissionNumber;

            _etaComplianceService.MarkTransmissionAccepted(
                transmission.EtaTransmissionAttemptId,
                EtaCallbackSystemUserId,
                "ETA_CALLBACK",
                etaUuid,
                payloadText);

            return new CallbackReconciliationResult(
                true,
                transmission.EtaTransmissionAttemptId,
                "ACCEPTED",
                null,
                null);
        }

        if (normalizedOutcome == "REJECTED")
        {
            var rejectionMessage = metadata.ErrorMessage
                ?? "ETA authority rejected the submission from callback notification.";

            _etaComplianceService.MarkTransmissionRejected(
                transmission.EtaTransmissionAttemptId,
                EtaCallbackSystemUserId,
                "ETA_CALLBACK",
                rejectionMessage,
                payloadText,
                metadata.ErrorCode);

            return new CallbackReconciliationResult(
                true,
                transmission.EtaTransmissionAttemptId,
                "REJECTED",
                metadata.ErrorCode,
                rejectionMessage);
        }

        return CallbackReconciliationResult.NotReconciled();
    }

    private EtaNotificationProcessingReceiptResponse ApplyNotificationProcessing(
        InboxMessageDetailView message,
        NotificationMetadata metadata,
        string processorCode,
        int retryDelaySeconds)
    {
        var reconciliation = ReconcileNotification(metadata, message.PayloadText);
        var normalizedOutcome = NormalizeAuthorityStatus(metadata.MessageType, metadata.StatusCode);

        if (reconciliation.Reconciled)
        {
            var processed = _inboxMessageRepository.MarkProcessed(message.InboxMessageId, processorCode);
            return new EtaNotificationProcessingReceiptResponse(
                processed.InboxMessageId,
                processed.MessageNumber,
                "PROCESSED",
                true,
                reconciliation.ReconciledTransmissionId,
                reconciliation.ReconciliationOutcome,
                reconciliation.ReconciliationErrorCode,
                reconciliation.ReconciliationErrorMessage);
        }

        var retryReason = BuildRetryReason(metadata, normalizedOutcome);
        if (message.AttemptNumber < message.MaxRetryCount)
        {
            var retried = _inboxMessageRepository.ScheduleRetry(message.InboxMessageId, retryReason, retryDelaySeconds);
            return new EtaNotificationProcessingReceiptResponse(
                retried.InboxMessageId,
                retried.MessageNumber,
                "RETRY_SCHEDULED",
                false,
                null,
                null,
                null,
                retryReason);
        }

        var failed = _inboxMessageRepository.MarkFailed(message.InboxMessageId, retryReason);
        return new EtaNotificationProcessingReceiptResponse(
            failed.InboxMessageId,
            failed.MessageNumber,
            "FAILED",
            false,
            null,
            null,
            null,
            retryReason);
    }

    private static void EnsureEtaProviderMessage(InboxMessageDetailView message)
    {
        if (!string.Equals(message.IntegrationProviderCode, "ETA", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only ETA inbox notifications can be processed through ETA notification operations.");
        }
    }

    private static NotificationMetadata ParseNotificationMetadata(string payloadText, string? defaultExternalMessageId = null, string? defaultCorrelationKey = null)
    {
        using var document = JsonDocument.Parse(payloadText);
        var root = document.RootElement;

        var messageType = ReadString(root, "type")
            ?? ReadString(root, "notificationType")
            ?? "ETA_DOCUMENT_NOTIFICATION";
        var externalMessageId = ReadString(root, "notificationId")
            ?? ReadString(root, "deliveryId")
            ?? ReadString(root, "eventId")
            ?? defaultExternalMessageId;

        string? correlationKey = defaultCorrelationKey;
        if (root.TryGetProperty("notifications", out var notificationsElement)
            && notificationsElement.ValueKind == JsonValueKind.Array)
        {
            var first = notificationsElement.EnumerateArray().FirstOrDefault();
            if (first.ValueKind != JsonValueKind.Undefined)
            {
                correlationKey = ReadString(first, "submissionUUID")
                    ?? ReadString(first, "submissionUuid")
                    ?? ReadString(first, "uuid")
                    ?? ReadString(first, "documentUUID")
                    ?? ReadString(first, "documentUuid")
                    ?? ReadString(first, "internalId");

                var notificationStatus = ReadString(first, "status")
                    ?? ReadString(first, "documentStatus")
                    ?? ReadString(first, "statusCode");
                var documentUuid = ReadString(first, "documentUUID")
                    ?? ReadString(first, "documentUuid")
                    ?? ReadString(first, "uuid");
                var errorCode = ReadNestedString(first, "error", "code")
                    ?? ReadString(first, "errorCode")
                    ?? ReadFirstArrayString(first, "errors", "code");
                var errorMessage = ReadNestedString(first, "error", "message")
                    ?? ReadString(first, "errorMessage")
                    ?? ReadString(first, "rejectionReason")
                    ?? ReadString(first, "reason")
                    ?? ReadString(first, "message")
                    ?? ReadFirstArrayString(first, "errors", "message");

                return new NotificationMetadata(
                    externalMessageId,
                    correlationKey,
                    messageType.Trim().ToUpperInvariant(),
                    notificationStatus,
                    documentUuid,
                    errorCode,
                    errorMessage);
            }
        }

        correlationKey ??= ReadString(root, "submissionUUID")
            ?? ReadString(root, "submissionUuid")
            ?? ReadString(root, "uuid");
        var rootStatus = ReadString(root, "status")
            ?? ReadString(root, "documentStatus")
            ?? ReadString(root, "statusCode");
        var rootErrorCode = ReadNestedString(root, "error", "code")
            ?? ReadString(root, "errorCode")
            ?? ReadFirstArrayString(root, "errors", "code");
        var rootErrorMessage = ReadNestedString(root, "error", "message")
            ?? ReadString(root, "errorMessage")
            ?? ReadString(root, "message")
            ?? ReadFirstArrayString(root, "errors", "message");

        return new NotificationMetadata(
            externalMessageId,
            correlationKey,
            messageType.Trim().ToUpperInvariant(),
            rootStatus,
            ReadString(root, "documentUUID")
                ?? ReadString(root, "documentUuid")
                ?? ReadString(root, "uuid"),
            rootErrorCode,
            rootErrorMessage);
    }

    private static string BuildRetryReason(NotificationMetadata metadata, string normalizedOutcome)
    {
        if (string.IsNullOrWhiteSpace(metadata.CorrelationKey))
        {
            return "ETA notification could not resolve a submission correlation key.";
        }

        return normalizedOutcome switch
        {
            "PENDING" => "ETA notification did not yet provide a final authority status.",
            _ => "ETA notification did not match a pending dispatched transmission."
        };
    }

    private static string? NormalizeAuthorizationSecret(string? authorizationHeader)
    {
        if (string.IsNullOrWhiteSpace(authorizationHeader))
        {
            return null;
        }

        var normalized = authorizationHeader.Trim();
        const string prefix = "ApiKey ";
        return normalized.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
            ? normalized[prefix.Length..].Trim()
            : normalized;
    }

    private static string? ReadString(JsonElement element, string propertyName) =>
        element.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String
            ? property.GetString()
            : null;

    private static string? ReadNestedString(JsonElement element, string objectPropertyName, string nestedPropertyName)
    {
        if (!element.TryGetProperty(objectPropertyName, out var property)
            || property.ValueKind != JsonValueKind.Object)
        {
            return null;
        }

        return ReadString(property, nestedPropertyName);
    }

    private static string? ReadFirstArrayString(JsonElement element, string arrayPropertyName, string nestedPropertyName)
    {
        if (!element.TryGetProperty(arrayPropertyName, out var property)
            || property.ValueKind != JsonValueKind.Array)
        {
            return null;
        }

        foreach (var item in property.EnumerateArray())
        {
            var candidate = item.ValueKind == JsonValueKind.Object
                ? ReadString(item, nestedPropertyName)
                : null;

            if (!string.IsNullOrWhiteSpace(candidate))
            {
                return candidate;
            }
        }

        return null;
    }

    private static string NormalizeAuthorityStatus(string messageType, string? statusCode)
    {
        var normalized = statusCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(normalized))
        {
            var normalizedMessageType = messageType.Trim().ToUpperInvariant();
            return normalizedMessageType switch
            {
                "DOCUMENTRECEIVED" => "ACCEPTED",
                "DOCUMENTREJECTED" => "REJECTED",
                _ => "PENDING"
            };
        }

        return normalized switch
        {
            "VALID" or "ACCEPTED" or "RECEIVED" or "SUBMITTED" => "ACCEPTED",
            "INVALID" or "REJECTED" or "CANCELLED" => "REJECTED",
            _ => "PENDING"
        };
    }

    private sealed record NotificationMetadata(
        string? ExternalMessageId,
        string? CorrelationKey,
        string MessageType,
        string? StatusCode,
        string? DocumentUuid,
        string? ErrorCode,
        string? ErrorMessage);

    private sealed record CallbackReconciliationResult(
        bool Reconciled,
        long? ReconciledTransmissionId,
        string? ReconciliationOutcome,
        string? ReconciliationErrorCode,
        string? ReconciliationErrorMessage)
    {
        public static CallbackReconciliationResult NotReconciled() =>
            new(false, null, null, null, null);
    }
}
