<?php

namespace app\service\LLM;

use Throwable;

class LLMRetryQueue
{
    public function run(callable $job, array $config = []): mixed
    {
        $enabled = (bool) ($config['enabled'] ?? true);
        $maxAttempts = max(1, (int) ($config['max_attempts'] ?? 3));
        $baseDelayMs = max(0, (int) ($config['base_delay_ms'] ?? 1500));
        $maxDelayMs = max($baseDelayMs, (int) ($config['max_delay_ms'] ?? 10000));
        $retryStatuses = array_map('intval', $config['retry_statuses'] ?? [429]);
        $retryErrorCodes = array_map('strval', $config['retry_error_codes'] ?? ['1302', '1303', '1304', '1305', '1306', '1307', '1308']);

        $attempt = 0;
        while (true) {
            $attempt++;

            try {
                return $job($attempt);
            } catch (Throwable $exception) {
                if (!$enabled || $attempt >= $maxAttempts || !$this->shouldRetry($exception, $retryStatuses, $retryErrorCodes)) {
                    throw $exception;
                }

                usleep($this->delayMs($attempt, $baseDelayMs, $maxDelayMs) * 1000);
            }
        }
    }

    private function shouldRetry(Throwable $exception, array $retryStatuses, array $retryErrorCodes): bool
    {
        if (!$exception instanceof LLMRequestException) {
            return false;
        }

        if ($exception->statusCode() !== null && in_array($exception->statusCode(), $retryStatuses, true)) {
            return true;
        }

        return $exception->providerCode() !== null && in_array($exception->providerCode(), $retryErrorCodes, true);
    }

    private function delayMs(int $attempt, int $baseDelayMs, int $maxDelayMs): int
    {
        $delay = min($maxDelayMs, $baseDelayMs * (2 ** max(0, $attempt - 1)));
        $jitter = $delay > 0 ? random_int(0, min(500, $delay)) : 0;

        return $delay + $jitter;
    }
}
