<?php

namespace App\Support;

use Throwable;

/**
 * What to tell someone when an AI provider refuses the work.
 *
 * A quota window and a busy server fail the same way from the outside and need
 * opposite advice. "Try again" is right for the second and actively wrong for
 * the first: the send fails identically until the window resets, so the user
 * retries three or four times and gives up, having been told to.
 *
 * The chat learned this first. The creative studio was still flattening every
 * failure, rate limits included, into "Generation failed. Please try again.",
 * which is how a Gemini 429 on dev on 23 Sep read as a broken product. The
 * wording lives here so the two cannot drift apart again.
 *
 * None of this returns the provider's own message: those carry endpoints, model
 * names and occasionally keys, and none of it means anything to a buyer.
 */
class ProviderFailure
{
    /** A quota window, which is not the same thing as a busy server. */
    public static function isRateLimit(Throwable $e): bool
    {
        $message = strtolower($e->getMessage());

        return str_contains($message, 'rate limit')
            || str_contains($message, 'rate-limit')
            || str_contains($message, 'quota')
            || str_contains($message, 'resource_exhausted')
            || str_contains($e->getMessage(), '429');
    }

    /**
     * The sentence, starting lower case so it can be embedded after a colon.
     *
     * Callers wanting it standalone should ucfirst() it.
     */
    public static function rateLimited(Throwable $e): string
    {
        $seconds = self::retryAfter($e);

        if ($seconds === null) {
            return 'we have hit the AI provider\'s rate limit. It clears on its own, but sending '
                .'this again before it does will fail the same way.';
        }

        $wait = $seconds < 90
            ? $seconds.' seconds'
            : (int) ceil($seconds / 60).' minutes';

        return sprintf(
            'we have hit the AI provider\'s rate limit. It clears in about %s, and sending this '
            .'again before then will fail the same way.',
            $wait,
        );
    }

    /** How long the provider asked us to wait, when it says so at all. */
    public static function retryAfter(Throwable $e): ?int
    {
        if (method_exists($e, 'response')) {
            $header = $e->response()?->header('Retry-After');

            if (is_numeric($header)) {
                return (int) $header;
            }
        }

        // "try again in 27s", "retry after 27 seconds", "please wait 1m30s".
        if (preg_match('/(?:retry after|try again in|wait)\D{0,12}?(\d+)\s*(s|sec|second|m|min|minute)/i', $e->getMessage(), $found)) {
            $value = (int) $found[1];

            return str_starts_with(strtolower($found[2]), 'm') ? $value * 60 : $value;
        }

        return null;
    }
}
