<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
 * Hustle_Addon_Aweber_Oauth2 class
 *
 * @package Hustle
 */

/**
 * Class Hustle_Addon_Constant_Contact_Oauth
 * Helpers for OAuth
 */
class Hustle_Addon_Aweber_Oauth2 {

	/**
	 * Oauth Client ID
	 *
	 * @var string
	 */
	public $client_id;

	/**
	 * Redirect uri
	 *
	 * @var string
	 */
	public $redirect_uri;

	/**
	 * Instances of Hustle_Addon_Aweber_Oauth2
	 *
	 * @var Hustle_Addon_Aweber_Oauth2|null
	 */
	protected static $instance = null;

	/**
	 * ConstactContact Oauth Version
	 *
	 * @var string
	 */
	const HUSTLE_AWEBER_OAUTH = '2.0';

	/**
	 * Constructor
	 *
	 * @param string $client_id Client ID.
	 * @param string $redirect_uri Redirect URI.
	 */
	private function __construct( $client_id, $redirect_uri ) {
		$this->client_id    = $client_id;
		$this->redirect_uri = $redirect_uri;
	}

	/**
	 * Get singleton
	 *
	 * @since 4.0.2
	 *
	 * @param string $client_id Client ID.
	 * @param string $redirect_uri Redirect URI.
	 *
	 * @return Hustle_Campaignmonitor|null
	 */
	public static function boot( $client_id, $redirect_uri ) {
		if ( ! isset( self::$instance ) ) {
			self::$instance = new static( $client_id, $redirect_uri );
		}

		return self::$instance;
	}

	/**
	 * Get the URL at which the user can authenticate and authorize the requesting application
	 *
	 * @since 4.0.2
	 *
	 * @param boolean $server - Whether or not to use OAuth2 server flow, alternative is client flow.
	 * @param string  $state - An optional value used by the client to maintain state between the request and callback.
	 *
	 * @return string $url - The url to send a user to, to grant access to their account
	 */
	public function get_authorization_url( $server = true, $state = null ) {
		$params = array(
			'response_type'         => $this->options( 'response_type_code' ),
			'client_id'             => $this->client_id,
			'scope'                 => implode( ' ', $this->options( 'scopes' ) ),
			'code_challenge'        => $this->get_pkce(),
			'code_challenge_method' => 'S256',
			'redirect_uri'          => 'urn:ietf:wg:oauth:2.0:oob',
		);

		// add the state param if it was provided.
		if ( null !== $state ) {
			$params['state'] = $state;
		}

		$url = $this->options( 'authorization_endpoint' );
		$url = $url . '?' . http_build_query( $params, '', '&', \PHP_QUERY_RFC3986 );

		return $url;
	}

	/**
	 * Generate the PKCE hash
	 *
	 * @since 4.0.3
	 *
	 * @return string $hash - code verifier
	 */
	public function get_pkce() {
		return str_replace( array( '=', '+', '/' ), array( '', '-', '_' ), base64_encode( hash( 'sha256', $this->get_pkce_verifier(), true ) ) );// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
	}

	/**
	 * Generate the PKCE verifier
	 *
	 * @since 4.0.3
	 *
	 * @return string $code_verifier - code verifier
	 */
	private function get_pkce_verifier() {

		$code_verifier = get_transient( 'hustle_aweber_code_verifier' );

		// if transient not found.
		if ( empty( $code_verifier ) ) {
			$code_verifier = str_replace( array( '=', '+', '/' ), array( '', '-', '_' ), base64_encode( random_bytes( 32 ) ) );// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
			set_transient( 'hustle_aweber_code_verifier', $code_verifier, DAY_IN_SECONDS );
		}
		return $code_verifier;
	}

	/**
	 * Add custom user agent on request
	 *
	 * @since 4.0.2
	 *
	 * @param string $user_agent User agent.
	 *
	 * @return string
	 */
	public function filter_user_agent( $user_agent ) {
		$user_agent .= ' HustleAWEBERauth/' . self::HUSTLE_AWEBER_OAUTH;
		return $user_agent;
	}

	/**
	 * Obtain an access token
	 *
	 * @since 4.0.2
	 *
	 * @param string $code - code returned from Constant Contact after a user has granted access to their account.
	 *
	 * @return array
	 */
	public function get_access_token( $code ) {
		$verifier = $this->get_pkce_verifier();
		if ( empty( $verifier ) || empty( $code ) ) {
			return;
		}

		$params = array(
			'grant_type'    => $this->options( 'authorization_code_grant_type' ),
			'code'          => $code,
			'redirect_uri'  => 'urn:ietf:wg:oauth:2.0:oob',
			'code_verifier' => $verifier,
			'client_id'     => $this->client_id,
		);

		$header = array(
			'Content-Type' => 'application/x-www-form-urlencoded',
		);

		$url = esc_url( $this->options( 'base_url' ) . $this->options( 'token_endpoint' ) );

		$response      = $this->post( $url, $header, $params );
		$response_body = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( array_key_exists( 'error', $response_body ) ) {
			return false;
		}

		return $response_body;
	}

	/**
	 * Make an Http POST request
	 *
	 * @since 4.0.2
	 *
	 * @param string $url - request url.
	 * @param array  $headers - array of all http headers to send.
	 * @param array  $data - data to send with request.
	 *
	 * @return CurlResponse - The response body, http info, and error (if one exists)
	 * @throws Exception Failed to process request.
	 */
	public function post( $url, array $headers = array(), $data = null ) {
		// Adding extra user agent for wp remote request.
		add_filter( 'http_headers_useragent', array( $this, 'filter_user_agent' ) );

		$_args = array(
			'method'  => 'POST',
			'headers' => $headers,
		);

		$_args['body'] = $data;
		$response      = wp_remote_request( $url, $_args );
		remove_filter( 'http_headers_useragent', array( $this, 'filter_user_agent' ) );

		if ( is_wp_error( $response ) || ! $response ) {
			throw new Exception(
				esc_html__( 'Failed to process request, make sure your Webhook URL is correct and your server has internet connection.', 'hustle' )
			);
		}

		return $response;
	}

	/**
	 * Oauth Options
	 *
	 * @since 4.0.2
	 *
	 * @param string $key - key to fetch.
	 *
	 * @return string
	 */
	private function options( $key ) {
		$props = array(
			'auth' => array(
				'base_url'                      => 'https://auth.aweber.com/oauth2/',
				'response_type_code'            => 'code',
				'response_type_token'           => 'token',
				'authorization_code_grant_type' => 'authorization_code',
				'authorization_endpoint'        => 'https://auth.aweber.com/oauth2/authorize',
				'token_endpoint'                => 'token',
				'scopes'                        => array( 'account.read', 'list.read', 'list.write', 'subscriber.read', 'subscriber.write', 'subscriber.read-extended' ),
			),
		);

		return $props['auth'][ $key ];
	}
}
