<?php

namespace Authstack\OpenIDConnect;

use Assert\Assertion;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Assert\InvalidArgumentException;
use Illuminate\Http\RedirectResponse;
use Authstack\OpenIDConnect\Model\Protocol\Flow\FlowInterface;

class Client
{
    protected $provider;
    protected $providerPubKey;
    protected $clientID;
    protected $secret;
    protected $redirectUri;
    protected $authorizationEndpoint;
    protected $tokenEndpoint;
    protected $userInfoEndpoint;
    protected $authorizationCode;
    protected $nonce;
    protected $scopes = [];

    /**
     * Additional parameters passed to OpenID Provider when requesting authorization/authentication
     *
     * @var
     */
    protected $additionalParams;

    /**
     * @var FlowInterface
     */
    protected $flow;

    public function __construct(string $clientID, string $secret, string $redirectUri)
    {
        $this->clientID = $clientID;
        $this->secret = $secret;
        $this->redirectUri = $redirectUri;
    }

    /**
     * @param array $data
     * @return Client
     * @throws InvalidArgumentException
     */
    public static function create(array $data): self
    {
        Assertion::keyIsset($data, 'clientID', 'Required key "clientID" not set');
        Assertion::keyIsset($data, 'secret', 'Required key "secret" not set');
        Assertion::keyIsset($data, 'redirectURI', 'Required key "redirectURI" not set');

        $me = new static($data['clientID'], $data['secret'], $data['redirectURI']);

        return $me;
    }

    /**
     * @param Request $request
     * @return array
     */
    public function consume(Request $request): array
    {
        return $this->flow->consume($this->getProvider(), $this, $request);
    }

    /**
     * @param FlowInterface $flow
     * @return Client
     */
    public function setFlow(FlowInterface $flow): self
    {
        $this->flow = $flow;

        return $this;
    }

    /**
     * @param Provider $provider
     * @return Client
     */
    public function setProvider(Provider $provider): self
    {
        $this->provider = $provider;

        return $this;
    }

    /**
     * @return Provider
     */
    public function getProvider(): Provider
    {
        return $this->provider;
    }

    /**
     * @return string
     */
    public function getClientID(): string
    {
        return $this->clientID;
    }

    /**
     * @return string
     */
    public function getClientSecret(): string
    {
        return $this->secret;
    }

    /**
     * @return string
     */
    public function getRedirectURI(): string
    {
        return $this->redirectUri;
    }

    /**
     * @param int $length
     * @return string
     */
    public function getNonce(int $length = 16)
    {
        if(empty($this->nonce))
        {
            $this->nonce = str_random($length);
        }

        return $this->nonce;
    }

    /**
     * @param string $scope
     * @return Client
     */
    public function addScope(string $scope): self
    {
        if(!in_array($scope, $this->scopes))
        {
            $this->scopes[] = $scope;
        }

        return $this;
    }

    /**
     * @return string
     */
    public function getScopes(): string
    {
        return implode(' ', $this->scopes);
    }

    /**
     * @param string $key
     * @param string $value
     * @return $this
     */
    public function setQueryParam(string $key, string $value)
    {
        $this->additionalParams[$key] = $value;

        return $this;
    }

    /**
     * @return array
     */
    public function getQueryParams(): array
    {
        return $this->additionalParams;
    }

    /**
     * @return bool
     */
    public function hasQueryParams(): bool
    {
        return is_array($this->additionalParams) && sizeof($this->additionalParams) > 0;
    }

    /**
     * @return RedirectResponse
     */
    public function authenticate(): RedirectResponse
    {
        Assertion::isInstanceOf($this->flow, FlowInterface::class, 'Now flow has been set. Set the flow using setFlow() method.');
        Assertion::isInstanceOf($this->provider, Provider::class, 'Unable to start authentication process - no OpenID Provider has been set. Use setProvider() method to set a provider.');

        // Add the openID
        $this->addScope('openid');

        return $this->flow->authenticate($this->getProvider(), $this);
    }

    /**
     * @return FlowInterface
     */
    public function buildAuthenticationRequest(): FlowInterface
    {
        Assertion::isInstanceOf($this->flow, FlowInterface::class, 'Now flow has been set. Set the flow using setFlow() method.');
        Assertion::isInstanceOf($this->provider, Provider::class, 'Unable to start authentication process - no OpenID Provider has been set. Use setProvider() method to set a provider.');

        // Add the openID
        $this->addScope('openid');

        return $this->flow->build($this->getProvider(), $this);
    }
}