<?php

declare(strict_types=1);

namespace SeoScope\Integrations;

use RuntimeException;

final class GoogleOAuth
{
    public const AUTH_ENDPOINT = 'https://accounts.google.com/o/oauth2/v2/auth';
    public const TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token';
    public const REVOKE_ENDPOINT = 'https://oauth2.googleapis.com/revoke';

    public static function isInvalidGrantError(string $message): bool
    {
        $m = strtolower($message);
        return str_contains($m, 'invalid_grant') || str_contains($m, 'token has been expired or revoked');
    }

    /**
     * @param list<string> $scopes
     */
    public static function buildAuthUrl(
        string $clientId,
        string $redirectUri,
        string $state,
        array $scopes
    ): string {
        $params = [
            'client_id' => $clientId,
            'redirect_uri' => $redirectUri,
            'response_type' => 'code',
            'scope' => implode(' ', $scopes),
            'state' => $state,
            'access_type' => 'offline',
            'prompt' => 'consent',
            'include_granted_scopes' => 'true',
        ];
        return self::AUTH_ENDPOINT . '?' . http_build_query($params);
    }

    /**
     * @return array<string,mixed>
     */
    public static function exchangeCodeForToken(
        string $code,
        string $clientId,
        string $clientSecret,
        string $redirectUri
    ): array {
        if (!extension_loaded('curl')) {
            throw new RuntimeException('cURL extension is required for OAuth token exchange.');
        }

        $post = http_build_query([
            'code' => $code,
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
            'redirect_uri' => $redirectUri,
            'grant_type' => 'authorization_code',
        ]);

        $ch = curl_init(self::TOKEN_ENDPOINT);
        if ($ch === false) {
            throw new RuntimeException('Failed to initialize cURL.');
        }
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $post,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/x-www-form-urlencoded',
            ],
            CURLOPT_TIMEOUT => 20,
        ]);
        $body = curl_exec($ch);
        $err = curl_error($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);

        if ($body === false) {
            throw new RuntimeException('Token request failed: ' . ($err !== '' ? $err : 'unknown error'));
        }

        $decoded = json_decode((string) $body, true);
        if (!is_array($decoded)) {
            throw new RuntimeException('Token response was not valid JSON (HTTP ' . $status . ').');
        }

        if ($status >= 400) {
            $msg = (string) ($decoded['error_description'] ?? $decoded['error'] ?? 'HTTP ' . $status);
            throw new RuntimeException('Token exchange failed: ' . $msg);
        }

        if (empty($decoded['access_token']) || !is_string($decoded['access_token'])) {
            throw new RuntimeException('Token exchange failed: missing access_token.');
        }

        return $decoded;
    }

    /**
     * @return array<string,mixed>
     */
    public static function refreshAccessToken(
        string $refreshToken,
        string $clientId,
        string $clientSecret
    ): array {
        if (!extension_loaded('curl')) {
            throw new RuntimeException('cURL extension is required for OAuth token refresh.');
        }
        if ($refreshToken === '') {
            throw new RuntimeException('Missing refresh_token.');
        }

        $post = http_build_query([
            'refresh_token' => $refreshToken,
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
            'grant_type' => 'refresh_token',
        ]);

        $ch = curl_init(self::TOKEN_ENDPOINT);
        if ($ch === false) {
            throw new RuntimeException('Failed to initialize cURL.');
        }
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $post,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/x-www-form-urlencoded',
            ],
            CURLOPT_TIMEOUT => 20,
        ]);
        $body = curl_exec($ch);
        $err = curl_error($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);

        if ($body === false) {
            throw new RuntimeException('Refresh request failed: ' . ($err !== '' ? $err : 'unknown error'));
        }

        $decoded = json_decode((string) $body, true);
        if (!is_array($decoded)) {
            throw new RuntimeException('Refresh response was not valid JSON (HTTP ' . $status . ').');
        }

        if ($status >= 400) {
            $msg = (string) ($decoded['error_description'] ?? $decoded['error'] ?? 'HTTP ' . $status);
            throw new RuntimeException('Refresh failed: ' . $msg);
        }

        if (empty($decoded['access_token']) || !is_string($decoded['access_token'])) {
            throw new RuntimeException('Refresh failed: missing access_token.');
        }

        return $decoded;
    }

    /**
     * Best-effort token revocation.
     * Returns true if Google accepted the revoke request (2xx), false otherwise.
     */
    public static function revokeToken(string $token): bool
    {
        if ($token === '') {
            return false;
        }
        if (!extension_loaded('curl')) {
            return false;
        }

        $post = http_build_query(['token' => $token]);
        $ch = curl_init(self::REVOKE_ENDPOINT);
        if ($ch === false) {
            return false;
        }
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $post,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/x-www-form-urlencoded',
            ],
            CURLOPT_TIMEOUT => 15,
        ]);
        curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);
        return $status >= 200 && $status < 300;
    }
}

