<?php
namespace GOSMTP\mailer\outlook;

class Auth{
		
	private $clientId;
	private $clientSecret;
	private $options;
	private $state;
	private $accessTokenMethod = 'POST';

	public function __construct($clientId = '', $clientSecret = '', $state = ''){
		$this->clientId = $clientId;
		$this->clientSecret = $clientSecret;
		$this->state = $state;

		//$this->assertRequiredOptions($this->getConfig());

		$this->options = $this->getConfig();
	}

	public function getAuthUrl(){
		return $this->getAuthorizationUrl();
	}

	public function generateToken($authCode){
		return $this->sendTokenRequest('authorization_code', [
			'code' => $authCode
		]);
	}

	public function sendTokenRequest($type, $params){
		try {
			$tokens = $this->getAccessToken($type, $params);
			return $tokens;
		} catch (\Exception $exception) {
			return new \WP_Error(423, $exception->getMessage());
		}
	}

	public function getRedirectUrl(){
		return rest_url('gosmtp-smtp/outlook_callback');
	}

	private function getConfig(){
		return array(
			'clientId' => $this->clientId,
			'clientSecret' => $this->clientSecret,
			'redirectUri' => $this->getRedirectUrl(),
			'urlAuthorize' => 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
			'urlAccessToken' => 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
			'urlResourceOwnerDetails' => '',
			'scopes' => 'offline_access user.read Mail.Send'
		);
	}

	public function getAuthorizationUrl($options = []){
		$base = $this->options['urlAuthorize'];

		$params = $this->getAuthorizationParameters($options);
		$query  = $this->getAuthorizationQuery($params);

		return $this->appendQuery($base, $query);
	}

	private function getAuthorizationParameters($options){
		if(empty($options['state'])){
			$options['state'] = $this->getRandomState().$this->state;
			update_option('_gosmtp_last_generated_state', $options['state']);
		}

		if(empty($options['scope'])){
			$options['scope'] = $this->options['scopes'];
		}

		$options += [
			'response_type'   => 'code',
			'approval_prompt' => 'auto'
		];

		if(is_array($options['scope'])){
			$separator = ',';
			$options['scope'] = implode($separator, $options['scope']);
		}

		// Store the state as it may need to be accessed later on.
		$this->options['state'] = $options['state'];

		// Business code layer might set a different redirect_uri parameter
		// depending on the context, leave it as-is
		if(!isset($options['redirect_uri'])){
			$options['redirect_uri'] = $this->options['redirectUri'];
		}

		$options['client_id'] = $this->options['clientId'];

		return $options;
	}

	/**
	* Appends a query string to a URL.
	*
	* @param  string $url The URL to append the query to
	* @param  string $query The HTTP query string
	* @return string The resulting URL
	*/
	protected function appendQuery($url, $query){
		$query = trim($query, '?&');

		if($query){
			$glue = strstr($url, '?') === false ? '?' : '&';
			return $url . $glue . $query;
		}

		return $url;
	}

	/**
	* Builds the authorization URL's query string.
	*
	* @param  array $params Query parameters
	* @return string Query string
	*/
	protected function getAuthorizationQuery(array $params){
		return $this->buildQueryString($params);
	}

	/**
	* Build a query string from an array.
	*
	* @param array $params
	*
	* @return string
	*/
	protected function buildQueryString(array $params){
		return http_build_query($params, '', '&', \PHP_QUERY_RFC3986);
	}

	/**
	* Verifies that all required options have been passed.
	*
	* @param  array $options
	* @return void
	* @throws \InvalidArgumentException
	*/
	private function assertRequiredOptions(array $options){
		$missing = array_diff_key(array_flip($this->getRequiredOptions()), $options);

		if (!empty($missing)) {
			throw new \InvalidArgumentException(
				'Required options not defined: ' . implode(', ', array_keys($missing))
			);
		}
	}

	/**
	* Returns all options that are required.
	*
	* @return array
	*/
	protected function getRequiredOptions(){
		return [
			'urlAuthorize',
			'urlAccessToken',
			'urlResourceOwnerDetails',
		];
	}

	/**
	* Returns a new random string to use as the state parameter in an
	* authorization flow.
	*
	* @param  int $length Length of the random string to be generated.
	* @return string
	*/
	protected function getRandomState($length = 32){
		// Converting bytes to hex will always double length. Hence, we can reduce
		// the amount of bytes by half to produce the correct length. 
		$state = bin2hex(random_bytes($length / 2));

		update_option('_gosmtp_last_generated_state', $state);

		return $state;
	}

	/**
	* Requests an access token using a specified grant and option set.
	*
	* @param  mixed $grant
	* @param  array $options
	* @throws \Exception
	* @return array tokens
	*/
	public function getAccessToken($grant, array $options = []){
		$params = [
			'client_id' => $this->options['clientId'],
			'client_secret' => $this->options['clientSecret'],
			'redirect_uri'  => $this->options['redirectUri'],
			'grant_type' => $grant,
		];

		$params += $options;

		$requestData = $this->getAccessTokenRequestDetails($params);

		$response = wp_remote_request($requestData['url'], $requestData['params']);

		if(is_wp_error($response)) {
			throw new \Exception(
				$response->get_error_message()
			);
		}

		$responseBody = wp_remote_retrieve_body($response);

		if(false === is_array($response)){
			throw new \Exception(
				'Invalid response received from Authorization Server. Expected JSON.'
			);
		}

		if(empty(['access_token'])){
			throw new \Exception(
				'Invalid response received from Authorization Server.'
			);
		}

		return \json_decode($responseBody, true);
	}

	/**
	* Returns a prepared request for requesting an access token.
	*
	* @param array $params Query string parameters
	* @return array $requestDetails
	*/
	protected function getAccessTokenRequestDetails($params){
		$method  = $this->accessTokenMethod;
		$url     = $this->getAccessTokenUrl($params);
		$options = $this->buildQueryString($params);

		return [
			'url' => $url,
			'params' => [
				'method' => $method,
				'body' => $options,
				'headers' => [
					'content-type' => 'application/x-www-form-urlencoded'
				]
			]
		];
	}

	/**
	* Returns the full URL to use when requesting an access token.
	*
	* @param array $params Query parameters
	* @return string
	*/
	protected function getAccessTokenUrl($params){
		$url = $this->options['urlAccessToken'];

		if($this->accessTokenMethod === 'GET'){
			$query = $this->getAccessTokenQuery($params);
			return $this->appendQuery($url, $query);
		}

		return $url;
	}

	protected function getAccessTokenQuery(array $params){
		return $this->buildQueryString($params);
	}

}
