<?php
declare(strict_types=1);
namespace Autogedal\AwbRetry\Model\Queue;

use Autogedal\AwbRetry\Model\AwbErrorLogManager;
use Autogedal\AwbRetry\Model\Config;
use Autogedal\AwbRetry\Model\RetryProcessor;
use Autogedal\AwbRetry\Model\TransientFailureClassifier;
use Eadesigndev\Awb\Model\Sources\CarrierType;
use Psr\Log\LoggerInterface;

class RetryConsumer
{
    public function __construct(
        private readonly RetryProcessor $retryProcessor,
        private readonly AwbErrorLogManager $errorLogManager,
        private readonly RetryRequestPublisher $retryRequestPublisher,
        private readonly Config $config,
        private readonly TransientFailureClassifier $failureClassifier,
        private readonly LoggerInterface $logger
    ) {
    }

    public function process(string $errorLogId): void
    {
        $retryMode = RetryRequestPublisher::RETRY_MODE_MANUAL;
        $simulateFailure = false;
        $simulateFailureMessage = '';
        $decodedMessage = json_decode($errorLogId, true);
        if (is_array($decodedMessage)) {
            $errorLogId = (string) ($decodedMessage['error_log_id'] ?? $errorLogId);
            $retryMode = (string) ($decodedMessage['retry_mode'] ?? $retryMode);
            $simulateFailure = (bool) ($decodedMessage['simulate_failure'] ?? false);
            $simulateFailureMessage = trim((string) ($decodedMessage['simulate_message'] ?? ''));
        }

        try {
            if ($retryMode === RetryRequestPublisher::RETRY_MODE_AUTOMATIC) {
                $delayMs = $this->config->getRetryDelayMs();
                if ($delayMs > 0) {
                    usleep($delayMs * 1000);
                }
            }

            $errorLogIdInt = (int) $errorLogId;
            $log = $this->errorLogManager->loadById($errorLogIdInt);
            if ((string) $log->getData('status') === 'resolved') {
                return;
            }

            $this->errorLogManager->markProcessing($errorLogIdInt);

            if ($simulateFailure) {
                throw new \RuntimeException(
                    $simulateFailureMessage !== ''
                        ? $simulateFailureMessage
                        : 'Operation timed out after 30001 milliseconds with 0 bytes received'
                );
            }

            $this->retryProcessor->process($errorLogIdInt);
        } catch (\Throwable $e) {
            try {
                $log = $this->errorLogManager->loadById((int) $errorLogId);
                $awbId = (int) $log->getData('awb_id');
                $carrierId = (int) $log->getData('carrier_id');
                $carrierCode = (string) ($log->getData('carrier_code') ?: $this->resolveCarrierCode($carrierId));
                $log = $this->errorLogManager->upsertFailure(
                    $awbId,
                    $carrierId,
                    $carrierCode,
                    $e,
                    1,
                    [
                        'source' => 'queue_consumer',
                        'error_log_id' => (int) $errorLogId,
                        'retry_mode' => $retryMode,
                    ]
                );

                if (
                    $retryMode === RetryRequestPublisher::RETRY_MODE_AUTOMATIC
                    && $this->failureClassifier->isTransient($e)
                    && (int) $log->getData('attempt_count') < $this->config->getMaxAttempts()
                ) {
                    $extraPayload = [];
                    if ($simulateFailure) {
                        $extraPayload = [
                            'simulate_failure' => true,
                            'simulate_message' => $simulateFailureMessage !== ''
                                ? $simulateFailureMessage
                                : 'Operation timed out after 30001 milliseconds with 0 bytes received',
                        ];
                    }

                    $this->retryRequestPublisher->queue(
                        (int) $errorLogId,
                        RetryRequestPublisher::RETRY_MODE_AUTOMATIC,
                        $extraPayload
                    );
                }
            } catch (\Throwable $logException) {
                $this->logger->error(
                    (string) __('AWB retry failure could not be written to the table for log %1: %2', $errorLogId, $logException->getMessage()),
                    ['exception' => $logException]
                );
            }

            $this->logger->error(
                (string) __('AWB retry queue consumer failed for log %1: %2', $errorLogId, $e->getMessage()),
                ['exception' => $e]
            );
        }
    }

    private function resolveCarrierCode(int $carrierId): string
    {
        return match ($carrierId) {
            CarrierType::PACKETA_SAMEDAY,
            CarrierType::PACKETA_FAN_COURIER => 'packeta',
            default => CarrierType::$carriersSettingsPath[$carrierId] ?? '',
        };
    }
}
