<?php
/**
 * GSC OAuth.
 *
 * Handles the "Connect with Google" flow. This sidesteps the current Google
 * bug that blocks adding service accounts to Search Console: OAuth uses the
 * authorising user's OWN access to the property, so there is no "add user"
 * step to fail.
 *
 *   1. build_auth_url()      — send the admin to Google's consent screen.
 *   2. maybe_handle_callback — Google redirects back with ?code; we exchange
 *      it for a refresh token and store it.
 *
 * The refresh token is stored encrypted (see GscCredentials). Access tokens
 * are derived from it on demand by GscClient.
 *
 * @package Platform\Helm\Modules\GSC
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\GSC;

use Platform\Helm\Core\Authorization;

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

final class GscOAuth {

	private const AUTH_ENDPOINT  = 'https://accounts.google.com/o/oauth2/v2/auth';
	private const TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token';
	private const SCOPE          = 'https://www.googleapis.com/auth/webmasters.readonly';
	private const NONCE_ACTION   = 'platform_helm_gsc_oauth_state';
	private const ERROR_TRANSIENT = 'platform_helm_gsc_oauth_error';

	public function register_hooks(): void {
		add_action( 'admin_init', [ $this, 'maybe_handle_callback' ] );
	}

	/** The redirect URI the admin must register in Google Cloud (exact match). */
	public static function redirect_uri(): string {
		return admin_url( 'admin.php?page=' . GscSettingsPage::PAGE_SLUG );
	}

	/** Build the Google consent URL to start authorisation. */
	public static function build_auth_url(): string {
		$args = [
			'client_id'             => GscCredentials::oauth_client_id(),
			'redirect_uri'          => self::redirect_uri(),
			'response_type'         => 'code',
			'scope'                 => self::SCOPE,
			'access_type'           => 'offline',
			'include_granted_scopes' => 'true',
			'prompt'                => 'consent', // force a refresh_token every time.
			'state'                 => wp_create_nonce( self::NONCE_ACTION ),
		];
		return self::AUTH_ENDPOINT . '?' . http_build_query( $args );
	}

	public static function last_error(): string {
		$e = get_transient( self::ERROR_TRANSIENT );
		if ( $e ) {
			delete_transient( self::ERROR_TRANSIENT );
		}
		return is_string( $e ) ? $e : '';
	}

	/**
	 * Detect Google's redirect back to the settings page and complete the flow.
	 */
	public function maybe_handle_callback(): void {
		if ( ! is_admin() ) {
			return;
		}
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
		if ( GscSettingsPage::PAGE_SLUG !== $page || ! isset( $_GET['code'], $_GET['state'] ) ) {
			return;
		}
		if ( ! Authorization::is_master_admin() ) {
			return;
		}
		$state = sanitize_text_field( wp_unslash( $_GET['state'] ) );
		$code  = sanitize_text_field( wp_unslash( $_GET['code'] ) );
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		if ( ! wp_verify_nonce( $state, self::NONCE_ACTION ) ) {
			$this->fail( __( 'Security check failed (state mismatch). Please try connecting again.', 'platform-helm' ) );
			return;
		}

		$exchanged = $this->exchange_code( $code );
		if ( is_wp_error( $exchanged ) ) {
			$this->fail( $exchanged->get_error_message() );
			return;
		}

		wp_safe_redirect( add_query_arg( 'gsc_connected', '1', self::redirect_uri() ) );
		exit;
	}

	/**
	 * Exchange an authorisation code for tokens and store the refresh token.
	 *
	 * @return true|\WP_Error
	 */
	private function exchange_code( string $code ) {
		$resp = wp_remote_post( self::TOKEN_ENDPOINT, [
			'timeout' => 25,
			'body'    => [
				'grant_type'    => 'authorization_code',
				'code'          => $code,
				'client_id'     => GscCredentials::oauth_client_id(),
				'client_secret' => GscCredentials::oauth_client_secret(),
				'redirect_uri'  => self::redirect_uri(),
			],
		] );
		if ( is_wp_error( $resp ) ) {
			return $resp;
		}
		$body = (string) wp_remote_retrieve_body( $resp );
		$data = json_decode( $body, true );
		$data = is_array( $data ) ? $data : [];
		$code_http = (int) wp_remote_retrieve_response_code( $resp );

		if ( $code_http < 200 || $code_http >= 300 ) {
			$msg = (string) ( $data['error_description'] ?? $data['error'] ?? __( 'Token exchange failed.', 'platform-helm' ) );
			return new \WP_Error( 'gsc_oauth_exchange', $msg );
		}

		$refresh = (string) ( $data['refresh_token'] ?? '' );
		if ( '' === $refresh ) {
			return new \WP_Error( 'gsc_oauth_no_refresh', __( 'Google did not return a refresh token. Remove this app at myaccount.google.com/permissions, then connect again.', 'platform-helm' ) );
		}
		GscCredentials::set_refresh_token( $refresh );
		return true;
	}

	private function fail( string $message ): void {
		set_transient( self::ERROR_TRANSIENT, $message, 120 );
		wp_safe_redirect( self::redirect_uri() );
		exit;
	}
}
