<?php

namespace AuthStack\OAuth2\Model\Protocol\Grant\tests\units;

use mageekguy\atoum;
use Illuminate\Http\Request;
use AuthStack\OAuth2\Exception\OAuth2Exception;
use AuthStack\OAuth2\Model\Protocol\Grant\GrantFactory;
use AuthStack\OAuth2\Model\Protocol\Request\AuthorizationCode\TokenRequest;
use AuthStack\OAuth2\Model\Protocol\Response\AuthorizationCode\TokenResponse;
use AuthStack\OAuth2\Model\Protocol\Request\AuthorizationCode\AuthorizationRequest;
use AuthStack\OAuth2\Model\Protocol\Response\AuthorizationCode\AuthorizationResponse;
use AuthStack\OAuth2\Model\Protocol\Grant\AuthorizationCode as AuthorizationCodeGrant;

class AuthorizationCode extends atoum\test
{
    public function testCreateAuthorizationRequest()
    {
        $this->given($authcodegrant = (new GrantFactory)->authorizationCode())
            ->then
                ->object($authcodegrant)->isInstanceOf(AuthorizationCodeGrant::class)
                ->object($request = $authcodegrant->authorizationRequest())->isInstanceOf(AuthorizationRequest::class)
                ->object($request
                            ->setClientID('My Client ID')
                            ->setState('My State')
                            ->setScope('read write delete')
                            ->setProperty('audience', 'https://my.audience.rest')
                            ->setRedirectUri('https://my.redirect.uri/callback')

                        )->isInstanceOf(AuthorizationRequest::class)
                ->array($request->toArray())
                    ->hasKeys(['response_type', 'client_id', 'redirect_uri', 'scope', 'state', 'audience'])
                    ->strictlyContainsValues([
                        'response_type' => 'code',
                        'client_id' => 'My Client ID',
                        'state' => 'My State',
                        'scope' => 'read write delete',
                        'redirect_uri' => 'https://my.redirect.uri/callback',
                        'audience' => 'https://my.redirect.uri/callback'
                    ])
            ;
    }

    public function testReceiveAuthorizationResponseAndCreateAuthorizationRequest()
    {
        $this->given($request = Request::create('https://your.authorization.server/authorize', 'GET', [
                    'response_type' => 'code',
                    'client_id' => 'My Client ID',
                    'state' => 'My State',
                    'scope' => 'read write delete',
                    'redirect_uri' => 'https://my.redirect.uri/callback'
                ]))
                ->and($authrequest = (new GrantFactory())->authorizationCode()->authorizationRequest())
                ->then
                    ->object($authrequest)->isInstanceOf(AuthorizationRequest::class)

                    // Receive the request. The ->receive() method simply reads from HTTP request and fills in the model
                    ->object($req = $authrequest->receive($request))->isInstanceOf(AuthorizationRequest::class)

                    // The resulting object property values should equal to the ones above, representing the request
                    ->string($req->getClientID())->isEqualTo('My Client ID')
                    ->string($req->getState())->isEqualTo('My State')
                    ->string($req->getScope())->isEqualTo('read write delete')
                    ->string($req->getRedirectURI())->isEqualTo('https://my.redirect.uri/callback')

                    // Assumption is that the request ($req) is valid, client authenticated and authorization server should reply with Authorization Response
                    // Authorization Response contains 'code' and 'state' parameters. 'state' (if present) is verified by the client app
                    // and if it checks out, the 'code' value is exchanged for an access token at authorization server. We're replying with 'code' and 'state'

                    ->object($authresponse = (new GrantFactory())->authorizationCode()->authorizationResponse($request))->isInstanceOf(AuthorizationResponse::class)
                    ->string($authresponse->getState())->isEqualTo('My State') // We read this from the $request while receiving
                    ->array($authresponse->toArray())->hasKeys(['code', 'state'])
            ;
    }

    public function testReceiveAuthorizationRequestFails()
    {
        $this->given($request = Request::create('https://your.authorization.server/authorize', 'GET', [
            'response_type' => 'code should be here, but it is not and this causes the error',
            'client_id' => 'My Client ID',
            'state' => 'My State',
            'scope' => 'read write delete',
            'redirect_uri' => 'https://my.redirect.uri/callback'
        ]))
            ->and($authrequest = (new GrantFactory())->authorizationCode()->authorizationRequest())
            ->then
            ->object($authrequest)->isInstanceOf(AuthorizationRequest::class)
            ->exception(function() use ($authrequest, $request)
            {
                $authrequest->receive($request);
            })->isInstanceOf(OAuth2Exception::class)
        ;
    }

    public function testReceiveAuthorizationReponseAndCreateAccessTokenRequest()
    {
        $code = str_random(32);
        $state = 'My State';

        $this->given($request = Request::create('https://your.relying.party/callback', 'GET', [
            'code' => $code,
            'state' => $state
        ]))
            ->then
                ->object($authresp = (new GrantFactory())->authorizationCode()->authorizationResponse()->receive($request)) // receive() fills the object with data from http request
                ->string($authresp->getCode())->isEqualTo($code)
                ->string($authresp->getState())->isEqualTo($state)

                // The authorization response is 1.) received 2.) verified and then the RP can make a token request to obtain access token

                ->object($tokenrequest = (new GrantFactory())->authorizationCode()->tokenRequest())->isInstanceOf(TokenRequest::class)

                // Fill the token request with mandatory values
                ->object(
                    $tokenrequest
                        ->setClientID('client ID')
                        ->setClientSecret('secret')
                        ->setCode($authresp->getCode())
                        ->setRedirectUri('https://return.here/callback')
                )->isInstanceOf(TokenRequest::class)

                // Now check whether the model contains all 5 required values
                ->array($tokenrequest->toArray())
                    ->hasKeys(['client_id', 'client_secret', 'grant_type', 'code', 'redirect_uri'])
                    ->strictlyContainsValues([
                        'client_id' => 'client ID',
                        'client_secret' => 'secret',
                        'grant_type' => 'authorization_code',
                        'code' => $code,
                        'redirect_uri' => 'https://return.here/callback'
                    ])
            ;
    }

    public function testReceiveAccessTokenRequestAndCreateAccessTokenResponse()
    {
        $this->given($model = [
            'client_id' => str_random(16),
            'client_secret' => str_random(16),
            'grant_type' => 'authorization_code',
            'code' => str_random(16),
            'redirect_uri' => 'https://return.to.app/callback'])

            ->and($request = Request::create('https://your.authorization.server/access_token', 'GET', $model))

            ->and($access_token = str_random(16))
            ->and($token_type = 'bearer')
            ->and($expires_in = 900)
            ->and($refresh_token = str_random(16))

            ->then
                ->object($tokenrequest = (new GrantFactory())->authorizationCode()->tokenRequest()->receive($request))->isInstanceOf(TokenRequest::class)

                // Check whether our token request contains expected values
                ->string($tokenrequest->getClientID())->isEqualTo($model['client_id'])
                ->string($tokenrequest->getClientSecret())->isEqualTo($model['client_secret'])
                ->string($tokenrequest->getGrantType())->isEqualTo($model['grant_type'])
                ->string($tokenrequest->getCode())->isEqualTo($model['code'])
                ->string($tokenrequest->getRedirectURI())->isEqualTo($model['redirect_uri'])

                // Create token response
                ->object($response = (new GrantFactory())->authorizationCode()->tokenResponse())->isInstanceOf(TokenResponse::class)
                ->object($response->setAccessToken($access_token)->setTokenType($token_type)->setExpiresIn($expires_in)->setRefreshToken($refresh_token))->isInstanceOf(TokenResponse::class)
                ->array($response->toArray())
                    ->hasKeys(['access_token', 'token_type', 'expires_in', 'refresh_token'])
                    ->strictlyContainsValues([
                        'access_token' => $access_token,
                        'token_type' => $token_type,
                        'expires_in' => $expires_in,
                        'refresh_token' => $refresh_token
                    ])
            ;

    }
}
