<?php

namespace local_learnwise\local\OAuth2\Controller;

use local_learnwise\local\OAuth2\TokenType\TokenTypeInterface;
use local_learnwise\local\OAuth2\Storage\AccessTokenInterface;
use local_learnwise\local\OAuth2\ScopeInterface;
use local_learnwise\local\OAuth2\RequestInterface;
use local_learnwise\local\OAuth2\ResponseInterface;
use local_learnwise\local\OAuth2\Scope;

/**
 * @see ResourceControllerInterface
 */
class ResourceController implements ResourceControllerInterface
{
    /**
     * @var array
     */
    private $token;

    /**
     * @var TokenTypeInterface
     */
    protected $tokenType;

    /**
     * @var AccessTokenInterface
     */
    protected $tokenStorage;

    /**
     * @var array
     */
    protected $config;

    /**
     * @var ScopeInterface
     */
    protected $scopeUtil;

    /**
     * Constructor
     *
     * @param TokenTypeInterface   $tokenType
     * @param AccessTokenInterface $tokenStorage
     * @param array                $config
     * @param ScopeInterface       $scopeUtil
     */
    public function __construct(TokenTypeInterface $tokenType, AccessTokenInterface $tokenStorage, $config = array(), ?ScopeInterface $scopeUtil = null)
    {
        $this->tokenType = $tokenType;
        $this->tokenStorage = $tokenStorage;

        $this->config = array_merge(array(
            'www_realm' => 'Service',
        ), $config);

        if (is_null($scopeUtil)) {
            $scopeUtil = new Scope();
        }
        $this->scopeUtil = $scopeUtil;
    }

    /**
     * Verify the resource request
     *
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param null              $scope
     * @return bool
     */
    public function verifyResourceRequest(RequestInterface $request, ResponseInterface $response, $scope = null)
    {
        $token = $this->getAccessTokenData($request, $response);

        // Check if we have token data
        if (is_null($token)) {
            return false;
        }

        /**
         * Check scope, if provided
         * If token doesn't have a scope, it's null/empty, or it's insufficient, then throw 403
         * @see http://tools.ietf.org/html/rfc6750#section-3.1
         */
        if ($scope && (!isset($token["scope"]) || !$token["scope"] || !$this->scopeUtil->checkScope($scope, $token["scope"]))) {
            $response->setError(403, 'insufficient_scope', 'The request requires higher privileges than provided by the access token');
            $response->addHttpHeaders(array(
                'WWW-Authenticate' => sprintf('%s realm="%s", scope="%s", error="%s", error_description="%s"',
                    $this->tokenType->getTokenType(),
                    $this->config['www_realm'],
                    $scope,
                    $response->getParameter('error'),
                    $response->getParameter('error_description')
                )
            ));

            return false;
        }

        // allow retrieval of the token
        $this->token = $token;

        return (bool) $token;
    }

    /**
     * Get access token data.
     *
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @return array|null
     */
    public function getAccessTokenData(RequestInterface $request, ResponseInterface $response)
    {
        // Get the token parameter
        if ($token_param = $this->tokenType->getAccessTokenParameter($request, $response)) {
            // Get the stored token data (from the implementing subclass)
            // Check we have a well formed token
            // Check token expiration (expires is a mandatory paramter)
            if (!$token = $this->tokenStorage->getAccessToken($token_param)) {
                $response->setError(401, 'invalid_token', 'The access token provided is invalid');
            } elseif (!isset($token["expires"]) || !isset($token["client_id"])) {
                $response->setError(401, 'malformed_token', 'Malformed token (missing "expires")');
            } elseif (time() > $token["expires"]) {
                $response->setError(401, 'invalid_token', 'The access token provided has expired');
            } else {
                return $token;
            }
        }

        $authHeader = sprintf('%s realm="%s"', $this->tokenType->getTokenType(), $this->config['www_realm']);

        if ($error = $response->getParameter('error')) {
            $authHeader = sprintf('%s, error="%s"', $authHeader, $error);
            if ($error_description = $response->getParameter('error_description')) {
                $authHeader = sprintf('%s, error_description="%s"', $authHeader, $error_description);
            }
        }

        $response->addHttpHeaders(array('WWW-Authenticate' => $authHeader));

        return null;
    }

    /**
     * convenience method to allow retrieval of the token.
     *
     * @return array
     */
    public function getToken()
    {
        return $this->token;
    }
}
