<?php
/**
 * OAuth 2.0 + PKCE handler.
 *
 * @package GoValid_QR
 */

defined( 'ABSPATH' ) || exit;

class GoValid_OAuth {

	/** @var GoValid_Token_Manager */
	private $token_manager;

	public function __construct() {
		$this->token_manager = new GoValid_Token_Manager();
	}

	/**
	 * Build the authorization URL and store PKCE state.
	 *
	 * @return string Authorization URL to redirect the user to.
	 */
	public function initiate_authorization(): string {
		$state          = wp_generate_password( 40, false );
		$code_verifier  = $this->generate_code_verifier();
		$code_challenge = $this->generate_code_challenge( $code_verifier );

		// Store code_verifier in a transient keyed by state (10 min TTL).
		set_transient( 'govalid_oauth_state_' . $state, $code_verifier, 10 * MINUTE_IN_SECONDS );

		$base_url     = GoValid_QR::get_base_url();
		$client_id    = get_option( 'govalid_qr_client_id', '' );
		$redirect_uri = $this->get_callback_url();
		$scopes       = 'read write profile email qr:read qr:write link:read link:write offline_access';

		$params = array(
			'response_type'         => 'code',
			'client_id'             => $client_id,
			'redirect_uri'          => $redirect_uri,
			'scope'                 => $scopes,
			'state'                 => $state,
			'code_challenge'        => $code_challenge,
			'code_challenge_method' => 'S256',
		);

		return $base_url . '/oauth/authorize/?' . http_build_query( $params, '', '&' );
	}

	/**
	 * Handle the OAuth callback: exchange code for tokens.
	 *
	 * @param string $code  Authorization code.
	 * @param string $state State parameter for CSRF validation.
	 * @return true|WP_Error
	 */
	public function handle_callback( string $code, string $state ) {
		// Validate state and retrieve code_verifier.
		$code_verifier = get_transient( 'govalid_oauth_state_' . $state );
		delete_transient( 'govalid_oauth_state_' . $state );

		if ( false === $code_verifier ) {
			return new WP_Error(
				'govalid_invalid_state',
				__( 'Invalid or expired authorization state. Please try again.', 'govalid-qr' )
			);
		}

		$client_id     = get_option( 'govalid_qr_client_id', '' );
		$client_secret = get_option( 'govalid_qr_client_secret', '' );
		$redirect_uri  = $this->get_callback_url();

		$client   = new GoValid_API_Client();
		$response = $client->post_unauthenticated( '/oauth/token/', array(
			'grant_type'    => 'authorization_code',
			'code'          => $code,
			'redirect_uri'  => $redirect_uri,
			'client_id'     => $client_id,
			'client_secret' => $client_secret,
			'code_verifier' => $code_verifier,
		) );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		if ( empty( $response['access_token'] ) ) {
			return new WP_Error(
				'govalid_token_exchange_failed',
				__( 'Failed to obtain access token.', 'govalid-qr' )
			);
		}

		// Store encrypted tokens.
		$this->token_manager->store( $response );

		// Fetch and store user info.
		$this->fetch_and_store_userinfo();

		return true;
	}

	/**
	 * Disconnect: revoke tokens and clear stored data.
	 */
	public function disconnect(): void {
		$tokens = $this->token_manager->get_tokens();

		// Best-effort token revocation.
		if ( $tokens && ! empty( $tokens['access_token'] ) ) {
			$client    = new GoValid_API_Client();
			$client_id = get_option( 'govalid_qr_client_id', '' );

			$client->post_unauthenticated( '/oauth/revoke/', array(
				'token'     => $tokens['access_token'],
				'client_id' => $client_id,
			) );
		}

		$this->token_manager->clear();
		delete_option( 'govalid_qr_userinfo' );

		// Clear all cached data.
		GoValid_Cache::get_instance()->flush_all();
	}

	/**
	 * Get the OAuth callback URL.
	 */
	public function get_callback_url(): string {
		return admin_url( 'admin.php?page=govalid-qr-oauth-callback' );
	}

	/**
	 * Fetch user info from GoValid and store it.
	 */
	private function fetch_and_store_userinfo(): void {
		$client   = new GoValid_API_Client();
		$response = $client->get( '/oauth/userinfo/' );

		if ( ! is_wp_error( $response ) && ! empty( $response ) ) {
			update_option( 'govalid_qr_userinfo', array(
				'name'   => sanitize_text_field( $response['name'] ?? '' ),
				'email'  => sanitize_email( $response['email'] ?? '' ),
				'avatar' => esc_url_raw( $response['avatar'] ?? '' ),
			), false );
		}
	}

	/**
	 * Generate a random code verifier (43-128 chars, unreserved characters).
	 */
	private function generate_code_verifier(): string {
		$random = wp_generate_password( 64, false );
		return rtrim( strtr( base64_encode( $random ), '+/', '-_' ), '=' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
	}

	/**
	 * Generate S256 code challenge from verifier.
	 */
	private function generate_code_challenge( string $verifier ): string {
		$hash = hash( 'sha256', $verifier, true );
		return rtrim( strtr( base64_encode( $hash ), '+/', '-_' ), '=' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
	}
}
