<?php
/**
 * REST endpoints for Google OAuth flow.
 *
 * @package StoreSheet
 */

declare( strict_types=1 );

namespace StoreSheet\Rest;

defined( 'ABSPATH' ) || exit;

use StoreSheet\API\GoogleOAuthClient;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

class OAuthController {

	private const NAMESPACE       = 'storesheet/v1';
	private const STATE_TRANSIENT = 'storesheet_oauth_state';

	private GoogleOAuthClient $oauth;

	public function __construct( GoogleOAuthClient $oauth ) {
		$this->oauth = $oauth;
	}

	public function register_routes(): void {
		register_rest_route(
			self::NAMESPACE,
			'/oauth/init',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'init_oauth' ),
				'permission_callback' => array( $this, 'admin_permission' ),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/oauth/callback',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'handle_callback' ),
				'permission_callback' => '__return_true', // verified via state nonce
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/oauth/disconnect',
			array(
				'methods'             => WP_REST_Server::DELETABLE,
				'callback'            => array( $this, 'disconnect' ),
				'permission_callback' => array( $this, 'admin_permission' ),
			)
		);

		register_rest_route(
			self::NAMESPACE,
			'/oauth/status',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_status' ),
				'permission_callback' => array( $this, 'admin_permission' ),
			)
		);
	}

	public function init_oauth( WP_REST_Request $request ): WP_REST_Response {
		$state        = wp_generate_uuid4();
		$redirect_uri = $this->callback_url();

		set_transient( self::STATE_TRANSIENT, $state, 5 * MINUTE_IN_SECONDS );

		$url = $this->oauth->get_auth_url( $redirect_uri, $state );
		if ( is_wp_error( $url ) ) {
			return new WP_REST_Response( array( 'message' => $url->get_error_message() ), 400 );
		}

		return new WP_REST_Response( array( 'url' => $url ), 200 );
	}

	public function handle_callback( WP_REST_Request $request ): void {
		$code  = sanitize_text_field( $request->get_param( 'code' ) ?? '' );
		$state = sanitize_text_field( $request->get_param( 'state' ) ?? '' );
		$error = sanitize_text_field( $request->get_param( 'error' ) ?? '' );

		$settings_url = admin_url( 'admin.php?page=storesheet' );

		if ( $error ) {
			wp_safe_redirect( add_query_arg( 'storesheet_oauth_error', urlencode( $error ), $settings_url ) );
			exit;
		}

		$saved_state = get_transient( self::STATE_TRANSIENT );
		if ( ! $saved_state || ! hash_equals( $saved_state, $state ) ) {
			wp_safe_redirect( add_query_arg( 'storesheet_oauth_error', 'state_mismatch', $settings_url ) );
			exit;
		}

		delete_transient( self::STATE_TRANSIENT );

		$tokens = $this->oauth->exchange_code( $code, $this->callback_url() );
		if ( is_wp_error( $tokens ) ) {
			wp_safe_redirect( add_query_arg( 'storesheet_oauth_error', urlencode( $tokens->get_error_message() ), $settings_url ) );
			exit;
		}

		$this->oauth->store_tokens( $tokens );

		wp_safe_redirect( add_query_arg( 'storesheet_oauth_success', '1', $settings_url ) );
		exit;
	}

	public function disconnect(): WP_REST_Response {
		$this->oauth->revoke();
		return new WP_REST_Response( array( 'disconnected' => true ), 200 );
	}

	public function get_status(): WP_REST_Response {
		return new WP_REST_Response( $this->oauth->get_status(), 200 );
	}

	public function admin_permission(): bool {
		return current_user_can( 'manage_woocommerce' );
	}

	/**
	 * The OAuth redirect URI.
	 *
	 * Used on both legs of the handshake — building the consent URL, and again
	 * when exchanging the code — because Google compares the two literally and
	 * answers `redirect_uri_mismatch` if they differ by so much as a host.
	 * Anything overriding this therefore overrides both at once, which is the
	 * only way an override can be correct.
	 *
	 * @return string
	 */
	private function callback_url(): string {
		/**
		 * Filter the OAuth redirect URI.
		 *
		 * Exists for sites whose public address is not the address WordPress
		 * knows about. The common case is local development: Google refuses to
		 * register a redirect URI on a reserved top-level domain — `.test`,
		 * `.local`, `.localhost` — so a site at `https://mystore.test` cannot
		 * complete the handshake at all, and the consent screen rejects it with
		 * `Error 400: invalid_request` rather than anything that names the
		 * cause. Pointing this at a tunnel hostname is enough to get through:
		 *
		 *     add_filter(
		 *         'storesheet_oauth_callback_url',
		 *         fn() => 'https://your-tunnel.example.com/wp-json/storesheet/v1/oauth/callback'
		 *     );
		 *
		 * The value must match a redirect URI registered on the OAuth client
		 * exactly, and the browser must be able to reach it — Google sends the
		 * authorization code there, and this plugin reads it from that request.
		 *
		 * Narrower than rewriting `home_url()`, which is the other way to solve
		 * this and changes every URL the site generates to fix one of them.
		 *
		 * @since 1.1.1
		 *
		 * @param string $callback_url The redirect URI, from rest_url().
		 */
		return (string) apply_filters(
			'storesheet_oauth_callback_url',
			rest_url( self::NAMESPACE . '/oauth/callback' )
		);
	}
}
