<?php

declare(strict_types=1);

namespace SeoScope\Integrations;

use RuntimeException;
use SeoScope\Repositories\SettingsRepository;
use Throwable;

final class GoogleTokenManager
{
    private const TOKENS_KEY = 'google.tokens';

    /**
     * Refresh if token is expired (or near-expired).
     * Returns the latest token array (may be unchanged).
     *
     * @return array<string,mixed>
     */
    public static function ensureFreshTokens(SettingsRepository $repo, string $clientId, string $clientSecret): array
    {
        $raw = (string) ($repo->getSecret(self::TOKENS_KEY) ?? '');
        if ($raw === '') {
            throw new RuntimeException('Missing stored tokens. Connect Google first.');
        }
        $tokens = json_decode($raw, true);
        if (!is_array($tokens)) {
            throw new RuntimeException('Stored tokens are not valid JSON.');
        }

        $refreshToken = (string) ($tokens['refresh_token'] ?? '');
        $expiresAt = (string) ($tokens['expires_at'] ?? '');
        $isExpired = self::isExpiredOrNear($expiresAt, 120);

        if (!$isExpired) {
            return $tokens;
        }

        try {
            $refreshed = GoogleOAuth::refreshAccessToken($refreshToken, $clientId, $clientSecret);
        } catch (Throwable $e) {
            $msg = $e->getMessage();
            $repo->set('google.reconnect_required', GoogleOAuth::isInvalidGrantError($msg) ? '1' : '0');
            $repo->set('google.token_last_error', mb_substr($msg, 0, 250));
            throw $e;
        }

        // Merge refreshed fields, preserving refresh_token.
        $tokens['access_token'] = (string) $refreshed['access_token'];
        $tokens['token_type'] = (string) ($refreshed['token_type'] ?? ($tokens['token_type'] ?? 'Bearer'));
        $tokens['scope'] = (string) ($refreshed['scope'] ?? ($tokens['scope'] ?? ''));
        $tokens['expires_in'] = (int) ($refreshed['expires_in'] ?? 3600);
        $tokens['obtained_at'] = date('c');
        $tokens['expires_at'] = date('c', time() + max(60, (int) $tokens['expires_in']));
        if ($refreshToken !== '') {
            $tokens['refresh_token'] = $refreshToken;
        }

        $repo->setSecret(self::TOKENS_KEY, json_encode($tokens, JSON_UNESCAPED_SLASHES));
        $repo->set('google.reconnect_required', '0');
        $repo->set('google.token_last_error', null);

        return $tokens;
    }

    public static function computeExpiresAtFromExchange(array &$token): void
    {
        $expiresIn = (int) ($token['expires_in'] ?? 0);
        if ($expiresIn > 0) {
            $token['expires_at'] = date('c', time() + max(60, $expiresIn));
        }
    }

    private static function isExpiredOrNear(string $expiresAtIso, int $skewSeconds): bool
    {
        if ($expiresAtIso === '') {
            return true;
        }
        $ts = strtotime($expiresAtIso);
        if ($ts === false) {
            return true;
        }
        return $ts <= (time() + $skewSeconds);
    }
}

