<?php
// Prevent direct access
if (count(get_included_files()) === 1) {
    http_response_code(403);
    exit('Forbidden');
}

/** Se lanza cuando Google devuelve invalid_grant (token revocado o expirado). */
class InvalidGrantException extends RuntimeException {}

/**
 * GoogleOAuth — login con Google (OpenID Connect) + consentimiento de Calendar.
 * Las credenciales (Client ID/Secret) viven en variables de entorno.
 */
class GoogleOAuth {
    // Permisos progresivos: al ENTRAR pedimos solo identidad.
    // El scope de Calendar se pide aparte, recién al conectar ese servicio.
    const SCOPES = 'openid email profile';
    const CALENDAR_SCOPES = 'openid'
        . ' https://www.googleapis.com/auth/userinfo.email'
        . ' https://www.googleapis.com/auth/userinfo.profile'
        . ' https://www.googleapis.com/auth/calendar.app.created'
        . ' https://www.googleapis.com/auth/calendar.freebusy';

    public static function clientId(): string {
        return getenv('GOOGLE_CLIENT_ID') ?: '';
    }

    public static function clientSecret(): string {
        return getenv('GOOGLE_CLIENT_SECRET') ?: '';
    }

    public static function isConfigured(): bool {
        return self::clientId() !== '' && self::clientSecret() !== '';
    }

    public static function redirectUri(): string {
        $env = getenv('GOOGLE_REDIRECT_URI');
        if ($env) {
            return $env;
        }
        $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
            || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https') ? 'https' : 'http';
        $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
        return $scheme . '://' . $host . '/auth/google/callback';
    }

    /**
     * URL de autorización Google.
     * Sin prompt por defecto → usuarios que ya consintieron entran directo.
     * $forceConsent = true solo en el primer login o cuando perdemos el refresh_token.
     */
    public static function authUrl(string $state, ?string $scopes = null, bool $forceConsent = false): string {
        $params = [
            'client_id'              => self::clientId(),
            'redirect_uri'           => self::redirectUri(),
            'response_type'          => 'code',
            'scope'                  => $scopes ?: self::SCOPES,
            'access_type'            => 'offline',
            'include_granted_scopes' => 'true',
            'state'                  => $state,
        ];
        if ($forceConsent) {
            $params['prompt'] = 'consent'; // solo cuando lo necesitamos explícitamente
        }
        return 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query($params);
    }

    /** Refresca un access_token usando el refresh_token guardado. */
    public static function refreshAccessToken(string $refreshToken): array {
        $post = http_build_query([
            'client_id'     => self::clientId(),
            'client_secret' => self::clientSecret(),
            'refresh_token' => $refreshToken,
            'grant_type'    => 'refresh_token',
        ]);
        $ch = curl_init('https://oauth2.googleapis.com/token');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => $post,
            CURLOPT_TIMEOUT        => 20,
        ]);
        $resp = curl_exec($ch);
        $data = json_decode($resp ?: '{}', true);
        return is_array($data) ? $data : [];
    }

    /** Cambia el code por tokens (access_token, id_token, refresh_token). */
    public static function exchangeCode(string $code): array {
        $post = http_build_query([
            'code' => $code,
            'client_id' => self::clientId(),
            'client_secret' => self::clientSecret(),
            'redirect_uri' => self::redirectUri(),
            'grant_type' => 'authorization_code',
        ]);

        $ch = curl_init('https://oauth2.googleapis.com/token');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
        curl_setopt($ch, CURLOPT_TIMEOUT, 20);
        $resp = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $err = curl_error($ch);

        if ($resp === false) {
            throw new Exception('Google token request failed: ' . $err);
        }
        $data = json_decode($resp, true);
        if ($httpCode !== 200) {
            $msg = $data['error_description'] ?? $data['error'] ?? 'unknown';
            throw new Exception("Google token exchange HTTP $httpCode: $msg");
        }
        return is_array($data) ? $data : [];
    }

    /**
     * Decodifica los claims del id_token (sub, email, name, picture).
     * El id_token viene directo de Google por HTTPS, así que confiamos en él
     * sin verificar la firma (guía oficial para canal seguro server-to-server).
     */
    public static function decodeIdToken(string $idToken): array {
        $parts = explode('.', $idToken);
        if (count($parts) < 2) {
            return [];
        }
        $payload = base64_decode(strtr($parts[1], '-_', '+/'));
        $claims = json_decode($payload, true);
        return is_array($claims) ? $claims : [];
    }
}
