<?php

namespace app\service;

use DateTimeImmutable;
use DateTimeZone;
use Exception;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\UnencryptedToken;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Lcobucci\JWT\Validation\Constraint\ValidAt;

class Jwt
{
    private static $secret = 'lF9XkOMfpsR0ODVfbasY2HtDrIps8GIX';
    private static $expire = 86400 * 3;//秒
    private static $iss = 'dxtc';//jwt签发者
    private static $sub = 'dxtc-customer';//jwt所面向的用户
    private static $aud = 'dxtc-customer';//接受jwt的一方

    private static function config(): Configuration
    {
        return Configuration::forSymmetricSigner(
        // You may use any HMAC variations (256, 384, and 512)
            new Sha256(),
            // replace the value below with a key of your own!
            InMemory::base64Encoded(self::$secret)
        // You may also override the JOSE encoder/decoder if needed by providing extra arguments here
        );
    }

    /**
     * 获取token有效期 单位秒
     *
     * @return int
     */
    public static function expire(): int
    {
        return self::$expire;
    }

    /**
     * 编码
     *
     * @param $data
     * @param  int  $expire
     * @return string
     * @throws Exception
     */
    public static function generate($data, int $expire = 0): string
    {
        $expire = $expire <= 0 ? self::$expire : $expire;
        $now = new DateTimeImmutable('now', new DateTimeZone('Asia/ShangHai'));

        $token = self::config()->builder()
            // Configures the issuer (iss claim)
            ->issuedBy(self::$iss)
            // Configures the audience (aud claim)
            ->permittedFor(self::$aud)
            // Configures the id (jti claim)
            //            ->identifiedBy($this->jti)
            // Configures the time that the token was issue (iat claim)
            ->issuedAt($now)
            // Configures the expiration time of the token (exp claim)
            ->expiresAt($now->modify(sprintf('+%d seconds', $expire)))
            // Configures a new claim, called "uid"
            ->withClaim('data', $data)
            // Configures a new header, called "foo"
            //            ->withHeader('foo', 'bar')
            // Builds a new token
            ->getToken(self::config()->signer(), self::config()->signingKey());

        return $token->toString();
    }

    /**
     * 解析
     *
     * @param  string  $tokenStr
     * @return array|mixed
     */
    public static function parse(string $tokenStr)
    {
        $config = self::config();

        try {
            $token = $config->parser()->parse($tokenStr);
            assert($token instanceof UnencryptedToken);
            return $token->claims()->all()['data'] ?? [];
        } catch (Exception $e) {
            return [];
        }
    }

    /**
     * 验证token
     *
     * @param  string  $tokenStr
     * @return bool
     */
    public static function validate(string $tokenStr): bool
    {
        $config = self::config();
        try {
            $token = $config->parser()->parse($tokenStr);
            assert($token instanceof UnencryptedToken);

            //验证签发人iss是否正确
            $validateIssued = new IssuedBy(self::$iss);
            $config->setValidationConstraints($validateIssued);
            //验证客户端aud是否匹配
            $validateAud = new PermittedFor(self::$aud);
            $config->setValidationConstraints($validateAud);

            //验证是否过期 exp
            $timezone        = new DateTimeZone('Asia/Shanghai');
            $now             = new SystemClock($timezone);
            $validateExpired = new LooseValidAt($now);
            $config->setValidationConstraints($validateExpired);

            $constraints = $config->validationConstraints();

            return $config->validator()->validate($token, ...$constraints);
        } catch (Exception $e) {
            return false;
        }
    }
}