<?php
declare(strict_types=1);
namespace PpPhp\Generated\SocialAuth;

final class JwtVerifier
{
    public static function verify(string $jwt, string $jwksUrl, array $issuers, string $audience, string $nonce = ''): array
    {
        $parts = explode('.', $jwt);
        if (count($parts) !== 3) throw new \RuntimeException('Malformed identity token.');
        $header = self::json($parts[0]); $claims = self::json($parts[1]);
        if (($header['alg'] ?? '') !== 'RS256' || empty($header['kid'])) throw new \RuntimeException('Unsupported identity-token signature.');
        $jwks = HttpClient::get($jwksUrl); $jwk = null;
        foreach (($jwks['keys'] ?? []) as $candidate) if (($candidate['kid'] ?? '') === $header['kid']) { $jwk = $candidate; break; }
        if (!$jwk) throw new \RuntimeException('Identity-token signing key was not found.');
        $ok = openssl_verify($parts[0] . '.' . $parts[1], self::b64($parts[2]), self::rsaPem($jwk), OPENSSL_ALGO_SHA256);
        if ($ok !== 1) throw new \RuntimeException('Identity-token signature is invalid.');
        $now = time();
        if (($claims['exp'] ?? 0) < $now - 60 || ($claims['iat'] ?? $now) > $now + 60) throw new \RuntimeException('Identity token has expired or is not active.');
        if (!in_array((string)($claims['iss'] ?? ''), $issuers, true)) throw new \RuntimeException('Identity-token issuer mismatch.');
        $aud = $claims['aud'] ?? '';
        if (!(is_array($aud) ? in_array($audience, $aud, true) : hash_equals($audience, (string)$aud))) throw new \RuntimeException('Identity-token audience mismatch.');
        if ($nonce !== '' && !hash_equals($nonce, (string)($claims['nonce'] ?? ''))) throw new \RuntimeException('Identity-token nonce mismatch.');
        return $claims;
    }
    public static function encodeEs256(array $header, array $claims, string $privateKey): string
    {
        $input = self::url(json_encode($header, JSON_UNESCAPED_SLASHES)) . '.' . self::url(json_encode($claims, JSON_UNESCAPED_SLASHES));
        $key = openssl_pkey_get_private(str_replace('\\n', "\n", trim($privateKey)));
        if (!$key || !openssl_sign($input, $der, $key, OPENSSL_ALGO_SHA256)) throw new \RuntimeException('Apple private key could not sign the client secret.');
        return $input . '.' . self::url(self::derToJose($der, 64));
    }
    private static function json(string $part): array { $v = json_decode(self::b64($part), true); if (!is_array($v)) throw new \RuntimeException('Malformed JWT data.'); return $v; }
    private static function b64(string $v): string { $v = strtr($v, '-_', '+/'); return (string)base64_decode($v . str_repeat('=', (4 - strlen($v) % 4) % 4), true); }
    private static function url(string $v): string { return rtrim(strtr(base64_encode($v), '+/', '-_'), '='); }
    private static function rsaPem(array $jwk): string
    {
        $n = self::b64((string)$jwk['n']); $e = self::b64((string)$jwk['e']);
        $seq = self::asn1("\x02" . self::len(strlen($n) + ($n !== '' && ord($n[0]) > 127 ? 1 : 0)) . (($n !== '' && ord($n[0]) > 127) ? "\0" : '') . $n . "\x02" . self::len(strlen($e)) . $e);
        $alg = hex2bin('300d06092a864886f70d0101010500'); $bit = "\x03" . self::len(strlen($seq) + 1) . "\0" . $seq; $der = self::asn1($alg . $bit);
        return "-----BEGIN PUBLIC KEY-----\n" . chunk_split(base64_encode($der), 64, "\n") . "-----END PUBLIC KEY-----\n";
    }
    private static function asn1(string $v): string { return "\x30" . self::len(strlen($v)) . $v; }
    private static function len(int $n): string { if ($n < 128) return chr($n); $s = ltrim(pack('N', $n), "\0"); return chr(0x80 | strlen($s)) . $s; }
    private static function derToJose(string $der, int $length): string
    {
        $offset = 0;
        if (ord($der[$offset++]) !== 0x30) throw new \RuntimeException('Invalid ECDSA signature sequence.');
        self::readLength($der, $offset);
        if (ord($der[$offset++]) !== 0x02) throw new \RuntimeException('Invalid ECDSA signature.');
        $rLen = self::readLength($der, $offset); $r = substr($der, $offset, $rLen); $offset += $rLen;
        if (ord($der[$offset++]) !== 0x02) throw new \RuntimeException('Invalid ECDSA signature.');
        $sLen = self::readLength($der, $offset); $s = substr($der, $offset, $sLen);
        $part = intdiv($length, 2); return str_pad(ltrim($r, "\0"), $part, "\0", STR_PAD_LEFT) . str_pad(ltrim($s, "\0"), $part, "\0", STR_PAD_LEFT);
    }
    private static function readLength(string $der, int &$offset): int
    {
        $length = ord($der[$offset++]);
        if (($length & 0x80) === 0) return $length;
        $bytes = $length & 0x7f; $length = 0;
        for ($i = 0; $i < $bytes; $i++) $length = ($length << 8) | ord($der[$offset++]);
        return $length;
    }
}
