<?php

namespace Firebase\JWT;

/**
 * Minimal drop-in replacement for firebase/php-jwt's JWT class, covering
 * only what JwtService.php actually uses: HS256 encode/decode with
 * standard claim validation (exp). Written so shared-hosting deploys
 * without Composer access still get full JWT-based API authentication —
 * see 10-deployment-guide.md and the "Composer-less install" README notes.
 *
 * This intentionally does NOT support the full firebase/php-jwt feature
 * set (RS256/ES256/other algorithms, JWK sets, etc.) — only what this
 * application's JwtService actually calls. If you later run `composer
 * install`, the real firebase/php-jwt package (loaded from vendor/) will
 * take precedence automatically as long as this ThirdParty copy is
 * removed or the Composer autoloader's classmap is regenerated first.
 */
class JWT
{
    public static function encode(array $payload, string $key, string $alg = 'HS256'): string
    {
        if ($alg !== 'HS256') {
            throw new \InvalidArgumentException('This minimal JWT implementation only supports HS256.');
        }

        $header = ['typ' => 'JWT', 'alg' => 'HS256'];

        $segments = [
            self::base64UrlEncode(json_encode($header, JSON_UNESCAPED_SLASHES)),
            self::base64UrlEncode(json_encode($payload, JSON_UNESCAPED_SLASHES)),
        ];

        $signingInput = implode('.', $segments);
        $signature = hash_hmac('sha256', $signingInput, $key, true);
        $segments[] = self::base64UrlEncode($signature);

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

    /**
     * @return object Decoded payload as a stdClass, matching firebase/php-jwt's return shape.
     */
    public static function decode(string $jwt, Key $key): object
    {
        $parts = explode('.', $jwt);
        if (count($parts) !== 3) {
            throw new \UnexpectedValueException('Wrong number of segments.');
        }

        [$headB64, $payloadB64, $sigB64] = $parts;

        $header = json_decode(self::base64UrlDecode($headB64), true);
        if (! is_array($header) || ($header['alg'] ?? null) !== 'HS256') {
            throw new \UnexpectedValueException('Unsupported or missing algorithm in token header.');
        }

        $signingInput = "{$headB64}.{$payloadB64}";
        $expectedSig = hash_hmac('sha256', $signingInput, $key->getKeyMaterial(), true);
        $actualSig = self::base64UrlDecode($sigB64);

        if (! hash_equals($expectedSig, $actualSig)) {
            throw new SignatureInvalidException('Signature verification failed.');
        }

        $payload = json_decode(self::base64UrlDecode($payloadB64));
        if (! is_object($payload)) {
            throw new \UnexpectedValueException('Invalid payload encoding.');
        }

        if (isset($payload->exp) && time() >= $payload->exp) {
            throw new ExpiredException('Expired token.');
        }

        return $payload;
    }

    private static function base64UrlEncode(string $data): string
    {
        return str_replace('=', '', strtr(base64_encode($data), '+/', '-_'));
    }

    private static function base64UrlDecode(string $data): string
    {
        $padded = str_pad($data, strlen($data) % 4 === 0 ? strlen($data) : strlen($data) + (4 - strlen($data) % 4), '=');
        return base64_decode(strtr($padded, '-_', '+/'));
    }
}
