<?php
namespace Crawl\Services;

use Crawl\Repositories\OAuthTokenRepository;

/**
 * Handles Google OAuth 2.0 flow and token management for GA4, GSC, and GBP.
 *
 * Each "service" (ga4 / gsc / gbp) maps to a specific Google API scope.
 * Tokens are stored per-project in project_oauth_tokens.
 */
class GoogleOAuthService
{
    private const SCOPES = [
        'ga4' => 'https://www.googleapis.com/auth/analytics.readonly',
        'gsc' => 'https://www.googleapis.com/auth/webmasters.readonly',
        'gbp' => 'https://www.googleapis.com/auth/business.manage',
    ];

    private const AUTH_URL  = 'https://accounts.google.com/o/oauth2/v2/auth';
    private const TOKEN_URL = 'https://oauth2.googleapis.com/token';
    private const USERINFO  = 'https://www.googleapis.com/oauth2/v2/userinfo';

    private string $clientId;
    private string $clientSecret;
    private string $redirectUri;
    private OAuthTokenRepository $repo;

    public function __construct()
    {
        $this->clientId     = $_ENV['GOOGLE_CLIENT_ID']     ?? '';
        $this->clientSecret = $_ENV['GOOGLE_CLIENT_SECRET'] ?? '';
        $this->redirectUri  = $_ENV['GOOGLE_REDIRECT_URI']  ?? '';
        $this->repo         = new OAuthTokenRepository();
    }

    public function isConfigured(): bool
    {
        return !empty($this->clientId) && !empty($this->clientSecret) && !empty($this->redirectUri);
    }

    // One-shot OAuth for ALL services (GA4 + GSC) under a single Google account.
    // Stores result as project_id = 0 (global fallback for all projects).
    public function buildBulkAuthUrl(): string
    {
        $scopes = implode(' ', [
            self::SCOPES['ga4'],
            self::SCOPES['gsc'],
            'https://www.googleapis.com/auth/userinfo.email',
        ]);
        $state = base64_encode(json_encode(['type' => 'bulk', 'project_id' => 0]));
        return self::AUTH_URL . '?' . http_build_query([
            'client_id'     => $this->clientId,
            'redirect_uri'  => $this->redirectUri,
            'response_type' => 'code',
            'scope'         => $scopes,
            'access_type'   => 'offline',
            'prompt'        => 'consent',
            'state'         => $state,
        ]);
    }

    public function handleBulkCallback(string $code): array
    {
        $tokens = $this->exchangeCode($code);
        $email  = $this->getUserEmail($tokens['access_token']);

        $tokenData = [
            'access_token'     => $tokens['access_token'],
            'refresh_token'    => $tokens['refresh_token'] ?? null,
            'token_expires_at' => date('Y-m-d H:i:s', time() + (int)($tokens['expires_in'] ?? 3600)),
            'account_email'    => $email,
        ];

        // Store as global token for both GA4 and GSC
        foreach (['ga4', 'gsc'] as $service) {
            $this->repo->upsert(0, $service, $tokenData);
        }

        return ['email' => $email, 'services' => ['ga4', 'gsc']];
    }

    // Build the URL to redirect the user to for authorization
    public function buildAuthUrl(string $service, int $projectId): string
    {
        $scope = self::SCOPES[$service] ?? '';
        if (!$scope) throw new \InvalidArgumentException("Unknown service: {$service}");

        $state = base64_encode(json_encode(['service' => $service, 'project_id' => $projectId]));

        return self::AUTH_URL . '?' . http_build_query([
            'client_id'     => $this->clientId,
            'redirect_uri'  => $this->redirectUri,
            'response_type' => 'code',
            'scope'         => $scope . ' ' . 'https://www.googleapis.com/auth/userinfo.email',
            'access_type'   => 'offline',
            'prompt'        => 'consent',
            'state'         => $state,
        ]);
    }

    // Exchange auth code for tokens and store them
    public function handleCallback(string $code, string $rawState): array
    {
        $state = json_decode(base64_decode($rawState), true);
        if (!$state || empty($state['service']) || empty($state['project_id'])) {
            throw new \RuntimeException('Invalid OAuth state parameter.');
        }

        $service   = $state['service'];
        $projectId = (int)$state['project_id'];

        // Exchange code for tokens
        $tokens = $this->exchangeCode($code);

        // Get account email
        $email = $this->getUserEmail($tokens['access_token']);

        // Store tokens
        $this->repo->upsert($projectId, $service, [
            'access_token'     => $tokens['access_token'],
            'refresh_token'    => $tokens['refresh_token'] ?? null,
            'token_expires_at' => date('Y-m-d H:i:s', time() + (int)($tokens['expires_in'] ?? 3600)),
            'account_email'    => $email,
        ]);

        return ['service' => $service, 'project_id' => $projectId, 'email' => $email];
    }

    // Get a valid access token, refreshing if expired.
    // Falls back to global token (project_id=0) if no project-specific token exists.
    public function getAccessToken(int $projectId, string $service): ?string
    {
        $row = $this->repo->find($projectId, $service);
        if (!$row) return null;

        // Use the actual project_id from the found row (may be 0 = global token)
        $effectiveProjectId = (int)($row['project_id'] ?? $projectId);

        $expiresAt = strtotime($row['token_expires_at'] ?? '2000-01-01');
        if ($expiresAt - time() < 300) {
            if (empty($row['refresh_token'])) return null;
            $tokens = $this->refreshToken($row['refresh_token']);
            $this->repo->upsert($effectiveProjectId, $service, [
                'access_token'     => $tokens['access_token'],
                'token_expires_at' => date('Y-m-d H:i:s', time() + (int)($tokens['expires_in'] ?? 3600)),
            ]);
            return $tokens['access_token'];
        }

        return $row['access_token'];
    }

    private function exchangeCode(string $code): array
    {
        return $this->postJson(self::TOKEN_URL, [
            'code'          => $code,
            'client_id'     => $this->clientId,
            'client_secret' => $this->clientSecret,
            'redirect_uri'  => $this->redirectUri,
            'grant_type'    => 'authorization_code',
        ]);
    }

    private function refreshToken(string $refreshToken): array
    {
        return $this->postJson(self::TOKEN_URL, [
            'refresh_token' => $refreshToken,
            'client_id'     => $this->clientId,
            'client_secret' => $this->clientSecret,
            'grant_type'    => 'refresh_token',
        ]);
    }

    private function getUserEmail(string $accessToken): string
    {
        $ctx = stream_context_create(['http' => [
            'header' => "Authorization: Bearer {$accessToken}\r\n",
        ]]);
        $body = @file_get_contents(self::USERINFO, false, $ctx);
        $data = $body ? json_decode($body, true) : [];
        return $data['email'] ?? '';
    }

    private function postJson(string $url, array $data): array
    {
        $ctx = stream_context_create(['http' => [
            'method'  => 'POST',
            'header'  => "Content-Type: application/x-www-form-urlencoded\r\n",
            'content' => http_build_query($data),
        ]]);
        $body   = @file_get_contents($url, false, $ctx);
        $result = $body ? json_decode($body, true) : [];
        if (isset($result['error'])) {
            throw new \RuntimeException("OAuth error: {$result['error']} — {$result['error_description']}");
        }
        return $result;
    }
}
