<?php

use \Firebase\JWT\JWT;
use \Firebase\JWT\BeforeValidException;
use \Firebase\JWT\SignatureInvalidException;
use CoderCat\JWKToPEM\JWKConverter;
    class AppleHelper
    {
        private static $apple_verify_endpoint = "https://appleid.apple.com";


        public static function checkToken($authorizationCode){
            $client = new GuzzleHttp\Client([
                'base_uri' => self::$apple_verify_endpoint,
                'headers' => [
                    'content-type' => 'application/x-www-form-urlencoded'
                ]
            ]);
            //var_dump($clientSecret);
            $params = [
                "client_id" => getenv("APPLE_SERVICE_ID"),
                "client_secret" => self::generateSecretKey(),
                "code" => $authorizationCode,
                "grant_type" => "authorization_code",
                "redirect_uri" => ""
            ];
            $response = $client->request("POST", "/auth/token", ['form_params' => $params]);
            $body = $response->getBody();
            $result = json_decode($body, TRUE);
            $result['response_code'] = $response->getStatusCode();

            return $result;
        }

        public static function decodeIdentityToken($token)
        {
            $publicKeySet = self::getPublicKey();
            $publicKey = [];
            foreach ($publicKeySet as $key) {
                $publicKey[$key['kid']] = $key;
            }

            $result = self::decodeJWT($token, $publicKey, ['RS256']);
            return $result;
        }

        private static function generateSecretKey()
        {
            $payload = [
                "iat" => time(),
                "exp" => strtotime('+3 month'),
                "iss" => getenv("APPLE_TEAM_ID"),
                "aud" => "https://appleid.apple.com",
                "sub" => getenv("APPLE_SERVICE_ID")
            ];

            $temp = str_replace('\n', PHP_EOL, file_get_contents("file://" . __DIR__ . DIRECTORY_SEPARATOR . "apple_private_key.pem"));
            //var_dump($temp);
            $privateKey = openssl_pkey_get_private($temp);
            if($privateKey === false) {
                throw new CustomErrorException("Error", openssl_error_string());
            }
            //var_dump($payload);
            $secretKey = self::encodeJWT($payload, $privateKey, "ES256", getenv("APPLE_KEY_ID"));

            return $secretKey;
        }

        private static function encodeJWT($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
        {
            $header = array('alg' => $alg);
            if ($keyId !== null) {
                $header['kid'] = $keyId;
            }
            if (isset($head) && \is_array($head)) {
                $header = \array_merge($head, $header);
            }
            $segments = array();
            $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header));
            $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload));
            $signing_input = \implode('.', $segments);

            $signature = JWT::sign($signing_input, $key, $alg);
            $segments[] = JWT::urlsafeB64Encode($signature);

            return \implode('.', $segments);
        }

        private static function getPublicKey()
        {
            $client = new GuzzleHttp\Client([
                'base_uri' => self::$apple_verify_endpoint,
                'headers' => []
            ]);
            $response = $client->request("GET", "/auth/keys");
            $body = $response->getBody();
            $result = json_decode($body, TRUE)['keys'];

            return $result;
        }

        private static function decodeJWT($jwt, $key, array $allowed_algs = array())
        {
            $timestamp = \is_null(JWT::$timestamp) ? \time() : JWT::$timestamp;

            if (empty($key)) {
                throw new InvalidArgumentException('Key may not be empty');
            }
            $tks = \explode('.', $jwt);
            if (\count($tks) != 3) {
                throw new UnexpectedValueException('Wrong number of segments');
            }
            list($headb64, $bodyb64, $cryptob64) = $tks;
            if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) {
                throw new UnexpectedValueException('Invalid header encoding');
            }
            if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) {
                throw new UnexpectedValueException('Invalid claims encoding');
            }
            if (false === ($sig = JWT::urlsafeB64Decode($cryptob64))) {
                throw new UnexpectedValueException('Invalid signature encoding');
            }
            if (empty($header->alg)) {
                throw new UnexpectedValueException('Empty algorithm');
            }
            if (empty(JWT::$supported_algs[$header->alg])) {
                throw new UnexpectedValueException('Algorithm not supported');
            }
            if (!\in_array($header->alg, $allowed_algs)) {
                throw new UnexpectedValueException('Algorithm not allowed');
            }
            if ($header->alg === 'ES256') {
                // OpenSSL expects an ASN.1 DER sequence for ES256 signatures
                $sig = JWT::signatureToDER($sig);
            }

            if (\is_array($key) || $key instanceof \ArrayAccess) {
                if (isset($header->kid)) {

                    if (!isset($key[$header->kid])) {
                        throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
                    }

                    $jwkConverter = new JWKConverter();
                    $publicKey = $jwkConverter->toPEM($key[$header->kid]);
                    $key = openssl_pkey_get_public(str_replace(['\n', '\r'], ["\n", "\r"], $publicKey));
                    // $key = $key[$header->kid];
                } else {
                    throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
                }
            }

            // Check the signature
            if (!self::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
                throw new SignatureInvalidException('Signature verification failed');
            }

            // Check the nbf if it is defined. This is the time that the
            // token can actually be used. If it's not yet that time, abort.
            if (isset($payload->nbf) && $payload->nbf > ($timestamp + JWT::$leeway)) {
                throw new BeforeValidException(
                    'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->nbf)
                );
            }

            // Check that this token has been created before 'now'. This prevents
            // using tokens that have been created for later use (and haven't
            // correctly used the nbf claim).
            if (isset($payload->iat) && $payload->iat > ($timestamp + JWT::$leeway)) {
                throw new BeforeValidException(
                    'Cannot handle token prior to ' . \date(DateTime::ISO8601, $payload->iat)
                );
            }

            // Check if this token has expired.
            if (isset($payload->exp) && ($timestamp - JWT::$leeway) >= $payload->exp) {
                throw new ExpiredException('Expired token');
            }

            return $payload;
        }

        private static function verify($msg, $signature, $key, $alg)
        {
            if (empty(JWT::$supported_algs[$alg])) {
                throw new DomainException('Algorithm not supported');
            }

            list($function, $algorithm) = JWT::$supported_algs[$alg];
            switch ($function) {
                case 'openssl':
                    $success = \openssl_verify($msg, $signature, $key, $algorithm);
                    if ($success === 1) {
                        return true;
                    } elseif ($success === 0) {
                        return false;
                    }
                    // returns 1 on success, 0 on failure, -1 on error.
                    throw new DomainException(
                        'OpenSSL error: ' . \openssl_error_string()
                    );
                case 'hash_hmac':
                default:
                    $hash = \hash_hmac($algorithm, $msg, $key, true);
                    if (\function_exists('hash_equals')) {
                        return \hash_equals($signature, $hash);
                    }
                    $len = \min(JWT::safeStrlen($signature), JWT::safeStrlen($hash));

                    $status = 0;
                    for ($i = 0; $i < $len; $i++) {
                        $status |= (\ord($signature[$i]) ^ \ord($hash[$i]));
                    }
                    $status |= (JWT::safeStrlen($signature) ^ JWT::safeStrlen($hash));

                    return ($status === 0);
            }
        }
    }
