<?php

namespace Authstack\OpenIDConnect\Model\Token;

use Assert\Assertion;
use Authstack\OpenIDConnect\Client;
use Authstack\OpenIDConnect\Provider;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer;
use Lcobucci\JWT\Signer\Hmac;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\ValidationData;

class IDToken
{
    protected $token;
    protected $provider;
    protected $client;
    protected $jwt;

    public function __construct(string $token, Provider $provider, Client $client)
    {
        $this->token = $token;
        $this->provider = $provider;
        $this->client = $client;
    }

    public static function create(array $data)
    {
        Assertion::keyIsset($data, 'token', 'Required key "token" not set');
        Assertion::keyIsset($data, 'provider', 'Required key "provider" not set');
        Assertion::keyIsset($data, 'client', 'Required key "client" not set');

        Assertion::string($data['token'], 'Key "token" must be a string');
        Assertion::isInstanceOf($data['provider'], Provider::class, 'Key "provider" must be an instance of Authstack\OpenIDConnect\Provider class');
        Assertion::isInstanceOf($data['client'], Provider::class, 'Key "client" must be an instance of Authstack\OpenIDConnect\Client class');

        $me = new static($data['token'], $data['provider'], $data['client']);

        $token = (new Parser())->parse($data['token']);

        $me->setJWT($token);
    }

    /**
     * @param ValidationData $data
     * @return bool
     */
    public function validate(ValidationData $data): bool
    {
        return $this->getJWT()->validate($data);
    }

    /**
     * @param Signer $signer
     * @return bool
     */
    public function verifySignature(Signer $signer): bool
    {
        if($signer instanceof Hmac)
        {
            return $this->getJWT()->verify($signer, $this->provider->getHMACSecret());
        }

        return $this->getJWT()->verify($signer, $this->provider->getCertificate());
    }

    /**
     * @param Token $token
     * @return IDToken
     */
    public function setJWT(Token $token): self
    {
        $this->jwt = $token;

        return $this;
    }

    /**
     * @return Token
     */
    public function getJWT(): Token
    {
        return $this->jwt;
    }

    /**
     * @return ValidationData
     */
    public function getTokenValidationData(): ValidationData
    {
        $validator = new ValidationData();

        $validator->setAudience($this->client->getClientID());
        $validator->setIssuer($this->provider->getIssuer());

        return $validator;
    }

    /**
     * @param string $alg
     * @return Signer
     */
    public function getTokenSigner(string $alg): Signer
    {
        $map = [
            'HS256' => \Lcobucci\JWT\Signer\Hmac\Sha256::class,
            'HS384' => \Lcobucci\JWT\Signer\Hmac\Sha384::class,
            'HS512' => \Lcobucci\JWT\Signer\Hmac\Sha512::class,

            'RS256' => \Lcobucci\JWT\Signer\Rsa\Sha256::class,
            'RS384' => \Lcobucci\JWT\Signer\Rsa\Sha384::class,
            'RS512' => \Lcobucci\JWT\Signer\Rsa\Sha512::class,

            'EC256' => \Lcobucci\JWT\Signer\Ecdsa\Sha256::class,
            'EC384' => \Lcobucci\JWT\Signer\Ecdsa\Sha384::class,
            'ES512' => \Lcobucci\JWT\Signer\Ecdsa\Sha512::class,
        ];

        Assertion::keyIsset($map, $alg, sprintf("Unsupported JWT signature algorithm: %s", $alg));

        $signer = new $map[$alg];

        return $signer;
    }
}