<?php

if (!defined('ABSPATH')) { exit; }

class AIEO_SSO_Provider_Google extends AIEO_SSO_Provider_Base {

    const AUTH_URL  = 'https://accounts.google.com/o/oauth2/v2/auth';
    const TOKEN_URL = 'https://oauth2.googleapis.com/token';
    const PROFILE_URL = 'https://openidconnect.googleapis.com/v1/userinfo';

    public function code()  { return 'google'; }
    public function label() { return 'Google'; }

    public function auth_url($state, $redirect_uri) {
        $cfg = $this->credentials();
        if ($cfg['client_id'] === '') {
            return new WP_Error('sso_no_client_id', 'Google client_id missing.');
        }
        return self::AUTH_URL . '?' . http_build_query(array(
            'client_id'     => $cfg['client_id'],
            'redirect_uri'  => $redirect_uri,
            'response_type' => 'code',
            'scope'         => 'openid email profile',
            'state'         => $state,
            'prompt'        => 'select_account',
            'access_type'   => 'online',
        ));
    }

    public function exchange_token($code, $redirect_uri) {
        $cfg = $this->credentials();
        return $this->http_post_form(self::TOKEN_URL, array(
            'code'          => $code,
            'client_id'     => $cfg['client_id'],
            'client_secret' => $cfg['client_secret'],
            'redirect_uri'  => $redirect_uri,
            'grant_type'    => 'authorization_code',
        ));
    }

    public function fetch_profile(array $token) {
        $access = (string) ($token['access_token'] ?? '');
        if ($access === '') {
            return new WP_Error('sso_no_token', 'Google did not return an access_token.');
        }
        $resp = $this->http_get_authorised(self::PROFILE_URL, $access);
        if (is_wp_error($resp)) { return $resp; }
        $email           = (string) ($resp['email']          ?? '');
        $email_verified  = !empty($resp['email_verified']);
        return array(
            'provider_uid' => (string) ($resp['sub']         ?? ''),
            'email'        => $email_verified ? $email : '', 
            'first_name'   => (string) ($resp['given_name']  ?? ''),
            'last_name'    => (string) ($resp['family_name'] ?? ''),
            'avatar'       => (string) ($resp['picture']     ?? ''),
            'raw'          => $resp,
        );
    }

    
    public function verify_id_token($id_token) {
        $parts = explode('.', trim((string) $id_token));
        if (count($parts) !== 3) {
            return new WP_Error('sso_jwt_malformed', __('Malformed ID token.', 'ai-eshop-optimizer'));
        }
        list($b64_header, $b64_payload, $b64_sig) = $parts;
        $header  = json_decode($this->b64url_decode($b64_header), true);
        $payload = json_decode($this->b64url_decode($b64_payload), true);
        if (!is_array($header) || !is_array($payload)) {
            return new WP_Error('sso_jwt_decode', __('Could not decode ID token.', 'ai-eshop-optimizer'));
        }
        if (($header['alg'] ?? '') !== 'RS256' || empty($header['kid'])) {
            return new WP_Error('sso_jwt_alg', __('Unsupported ID token algorithm.', 'ai-eshop-optimizer'));
        }

        
        $pem = $this->google_signing_key_pem((string) $header['kid']);
        if (is_wp_error($pem)) { return $pem; }
        $ok = openssl_verify($b64_header . '.' . $b64_payload, $this->b64url_decode($b64_sig), $pem, OPENSSL_ALGO_SHA256);
        if ($ok !== 1) {
            return new WP_Error('sso_jwt_signature', __('ID token signature failed verification.', 'ai-eshop-optimizer'));
        }

        
        $cfg = $this->credentials();
        if ($cfg['client_id'] === '' || (string) ($payload['aud'] ?? '') !== $cfg['client_id']) {
            return new WP_Error('sso_jwt_aud', __('ID token audience mismatch.', 'ai-eshop-optimizer'));
        }
        $iss = (string) ($payload['iss'] ?? '');
        if ($iss !== 'accounts.google.com' && $iss !== 'https://accounts.google.com') {
            return new WP_Error('sso_jwt_iss', __('ID token issuer mismatch.', 'ai-eshop-optimizer'));
        }
        if ((int) ($payload['exp'] ?? 0) <= time()) {
            return new WP_Error('sso_jwt_expired', __('ID token expired.', 'ai-eshop-optimizer'));
        }
        $email          = (string) ($payload['email'] ?? '');
        $email_verified = !empty($payload['email_verified']) || 'true' === ($payload['email_verified'] ?? '');

        return array(
            'provider_uid' => (string) ($payload['sub']         ?? ''),
            'email'        => $email_verified ? $email : '',
            'first_name'   => (string) ($payload['given_name']  ?? ''),
            'last_name'    => (string) ($payload['family_name'] ?? ''),
            'avatar'       => (string) ($payload['picture']     ?? ''),
            'raw'          => $payload,
        );
    }

    
    private function google_signing_key_pem($kid) {
        $keys = get_transient('aieo_google_jwks');
        if (!is_array($keys) || !isset($keys[$kid])) {
            $resp = wp_remote_get('https://www.googleapis.com/oauth2/v3/certs', array('timeout' => 15));
            if (is_wp_error($resp)) { return $resp; }
            $body = json_decode((string) wp_remote_retrieve_body($resp), true);
            if (!is_array($body) || empty($body['keys'])) {
                return new WP_Error('sso_jwks_fetch', __('Could not fetch Google signing keys.', 'ai-eshop-optimizer'));
            }
            $keys = array();
            foreach ($body['keys'] as $k) {
                if (($k['kty'] ?? '') === 'RSA' && !empty($k['kid']) && !empty($k['n']) && !empty($k['e'])) {
                    $pem = $this->jwk_rsa_to_pem((string) $k['n'], (string) $k['e']);
                    if ($pem !== '') { $keys[(string) $k['kid']] = $pem; }
                }
            }
            set_transient('aieo_google_jwks', $keys, 6 * HOUR_IN_SECONDS);
        }
        if (!isset($keys[$kid])) {
            return new WP_Error('sso_jwks_kid', __('No Google signing key matched the token.', 'ai-eshop-optimizer'));
        }
        return $keys[$kid];
    }

    
    private function b64url_decode($data) {
        $data = strtr((string) $data, '-_', '+/');
        $pad  = strlen($data) % 4;
        if ($pad) { $data .= str_repeat('=', 4 - $pad); }
        return (string) base64_decode($data);
    }

    
    private function jwk_rsa_to_pem($n_b64, $e_b64) {
        $n = $this->b64url_decode($n_b64);
        $e = $this->b64url_decode($e_b64);
        if ($n === '' || $e === '') { return ''; }
        $rsa_pub = $this->der_seq($this->der_uint($n) . $this->der_uint($e));               
        $algo    = $this->der_seq("\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00");    
        $spki    = $this->der_seq($algo . $this->der_bitstr("\x00" . $rsa_pub));             
        return "-----BEGIN PUBLIC KEY-----\n" . chunk_split(base64_encode($spki), 64, "\n") . "-----END PUBLIC KEY-----\n";
    }

    
    private function der_uint($bytes) {
        $bytes = ltrim($bytes, "\x00");
        if ($bytes === '') { $bytes = "\x00"; }
        if (ord($bytes[0]) & 0x80) { $bytes = "\x00" . $bytes; } 
        return "\x02" . $this->der_len(strlen($bytes)) . $bytes;
    }

    private function der_seq($contents)    { return "\x30" . $this->der_len(strlen($contents)) . $contents; }
    private function der_bitstr($contents) { return "\x03" . $this->der_len(strlen($contents)) . $contents; }

    
    private function der_len($len) {
        if ($len < 0x80) { return chr($len); }
        $out = '';
        while ($len > 0) { $out = chr($len & 0xff) . $out; $len >>= 8; }
        return chr(0x80 | strlen($out)) . $out;
    }
}

add_filter('aieo_sso_providers', static function (array $providers) {
    if (!isset($providers['google']) && class_exists('AIEO_SSO_Provider_Google')) {
        $providers['google'] = new AIEO_SSO_Provider_Google();
    }
    return $providers;
});
