<?php

namespace sent\jwt\provider\JWT;

use Exception;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Ecdsa;
use Lcobucci\JWT\Signer\Ecdsa\Sha256 as ES256;
use Lcobucci\JWT\Signer\Ecdsa\Sha384 as ES384;
use Lcobucci\JWT\Signer\Ecdsa\Sha512 as ES512;
use Lcobucci\JWT\Signer\Hmac\Sha256 as HS256;
use Lcobucci\JWT\Signer\Hmac\Sha384 as HS384;
use Lcobucci\JWT\Signer\Hmac\Sha512 as HS512;
use Lcobucci\JWT\Signer\Keychain;
use Lcobucci\JWT\Signer\Rsa;
use Lcobucci\JWT\Signer\Rsa\Sha256 as RS256;
use Lcobucci\JWT\Signer\Rsa\Sha384 as RS384;
use Lcobucci\JWT\Signer\Rsa\Sha512 as RS512;
use ReflectionClass;
use sent\jwt\exception\JWTException;
use sent\jwt\exception\TokenInvalidException;

class Lcobucci extends Provider {
	protected $signers = [
		'HS256' => HS256::class,
		'HS384' => HS384::class,
		'HS512' => HS512::class,
		'RS256' => RS256::class,
		'RS384' => RS384::class,
		'RS512' => RS512::class,
		'ES256' => ES256::class,
		'ES384' => ES384::class,
		'ES512' => ES512::class,
	];
	protected $builder;
	protected $parser;

	public function __construct(Builder $builder, Parser $parser, $algo, $keys) {
		$this->builder = $builder;
		$this->parser  = $parser;
		$this->algo    = $algo;
		$this->keys    = $keys;
		$this->signer  = $this->getSign();
	}

	public function encode(array $payload) {
		$this->builder->unsign();
		try {
			foreach ($payload as $key => $val) {
				$this->builder->set($key, $val->getValue());
			}
			$this->builder->sign($this->signer, $this->getSigningKey());
		} catch (Exception $e) {
			throw new JWTException('Could not create token :' . $e->getMessage(),
				$e->getCode(), $e);
		}

		return (string) $this->builder->getToken();
	}

	public function decode($token) {
		try {
			$jwt = $this->parser->parse($token);
		} catch (Exception $e) {
			throw new TokenInvalidException('Could not decode token: '
				. $e->getMessage(), $e->getCode(), $e);
		}

		if (!$jwt->verify($this->signer, $this->getVerificationKey())) {
			throw new TokenInvalidException('Token Signature could not be verified.');
		}
		return $jwt->getClaims();
	}

	protected function isAsymmetric() {
		$reflect = new ReflectionClass($this->signer);

		return $reflect->isSubclassOf(Rsa::class)
		|| $reflect->isSubclassOf(Ecdsa::class);
	}

	protected function getSigningKey() {
		return $this->isAsymmetric()
		?
		(new Keychain())->getPrivateKey($this->getPrivateKey(),
			$this->getPassword())
		:
		$this->getSecret();
	}

	protected function getVerificationKey() {
		return $this->isAsymmetric()
		?
		(new Keychain())->getPublicKey($this->getPublicKey())
		:
		$this->getSecret();
	}

	protected function getSign() {
		if (!isset($this->signers[$this->algo])) {
			throw new JWTException('Cloud not find ' . $this->algo . ' algo');
		}

		return new $this->signers[$this->algo];
	}
}
