<?php
/**
 * 1MZ For Artists — JWT (HS256)
 *
 * Implementação minimalista de JWT (HS256) sem dependências externas.
 * Mantida pequena e auditável de propósito — não é firebase/php-jwt nem
 * pretende ser. Faz exactamente o que precisamos:
 *
 *   - rmbz_api_jwt_encode( $payload ) : string
 *   - rmbz_api_jwt_decode( $token )   : array|WP_Error
 *
 * Algoritmo: HS256 (HMAC-SHA256). Chave em RMBZ_API_JWT_SECRET.
 *
 * Notas de segurança:
 *   - Verificamos `alg` no header e rejeitamos qualquer coisa que não seja
 *     HS256 (mitigação do clássico ataque "alg: none").
 *   - hash_equals() na comparação da assinatura (time-safe).
 *   - Validamos exp e iat. Não permitimos tokens sem exp.
 *
 * @package 1MZ_For_Artists
 * @since   1.6.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Base64URL encode (RFC 7515 §2).
 */
function rmbz_api_b64url_encode( $data ) {
    return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' );
}

/**
 * Base64URL decode.
 */
function rmbz_api_b64url_decode( $data ) {
    $remainder = strlen( $data ) % 4;
    if ( $remainder ) {
        $data .= str_repeat( '=', 4 - $remainder );
    }
    return base64_decode( strtr( $data, '-_', '+/' ) );
}

/**
 * Codifica um payload em JWT HS256.
 *
 * @param array $payload Reivindicações. Devem incluir 'sub' (telemovel)
 *                       e o callee deve adicionar 'exp' e 'iat'.
 * @return string Token JWT compacto.
 */
function rmbz_api_jwt_encode( array $payload ) {
    $header = [
        'alg' => 'HS256',
        'typ' => 'JWT',
    ];

    $segments = [
        rmbz_api_b64url_encode( wp_json_encode( $header ) ),
        rmbz_api_b64url_encode( wp_json_encode( $payload ) ),
    ];

    $signing_input = implode( '.', $segments );
    $signature = hash_hmac( 'sha256', $signing_input, RMBZ_API_JWT_SECRET, true );
    $segments[] = rmbz_api_b64url_encode( $signature );

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

/**
 * Descodifica e verifica um JWT.
 *
 * @param string $token
 * @return array|WP_Error Payload em caso de sucesso, WP_Error com código
 *                        legível em caso de falha.
 */
function rmbz_api_jwt_decode( $token ) {
    if ( ! is_string( $token ) || substr_count( $token, '.' ) !== 2 ) {
        return new WP_Error( 'jwt_malformed', 'Token mal formado.', [ 'status' => 401 ] );
    }

    list( $h64, $p64, $s64 ) = explode( '.', $token );

    $header = json_decode( rmbz_api_b64url_decode( $h64 ), true );
    if ( ! is_array( $header ) || empty( $header['alg'] ) ) {
        return new WP_Error( 'jwt_bad_header', 'Cabeçalho inválido.', [ 'status' => 401 ] );
    }

    // CRÍTICO: rejeitar qualquer alg diferente de HS256.
    // O ataque "alg: none" e a confusão HS256/RS256 são mitigados aqui.
    if ( $header['alg'] !== 'HS256' ) {
        return new WP_Error( 'jwt_bad_alg', 'Algoritmo não suportado.', [ 'status' => 401 ] );
    }

    $payload = json_decode( rmbz_api_b64url_decode( $p64 ), true );
    if ( ! is_array( $payload ) ) {
        return new WP_Error( 'jwt_bad_payload', 'Payload inválido.', [ 'status' => 401 ] );
    }

    // Verificar assinatura
    $signing_input = $h64 . '.' . $p64;
    $expected = hash_hmac( 'sha256', $signing_input, RMBZ_API_JWT_SECRET, true );
    $provided = rmbz_api_b64url_decode( $s64 );

    if ( ! hash_equals( $expected, $provided ) ) {
        return new WP_Error( 'jwt_bad_signature', 'Assinatura inválida.', [ 'status' => 401 ] );
    }

    // exp obrigatório
    if ( empty( $payload['exp'] ) || ! is_numeric( $payload['exp'] ) ) {
        return new WP_Error( 'jwt_no_exp', 'Token sem expiração.', [ 'status' => 401 ] );
    }

    // Tolerância de relógio: 60s para evitar erros por skew entre servidor e device
    $clock_skew = 60;
    $now = time();

    // [1.5.236] Usar >= : token expira no instante exacto do exp, não 1s depois.
    if ( $now >= ( (int) $payload['exp'] + $clock_skew ) ) {
        return new WP_Error( 'jwt_expired', 'Token expirado.', [ 'status' => 401 ] );
    }

    if ( ! empty( $payload['iat'] ) && (int) $payload['iat'] > ( $now + $clock_skew ) ) {
        return new WP_Error( 'jwt_future', 'Token emitido no futuro.', [ 'status' => 401 ] );
    }

    return $payload;
}

/**
 * Helper para criar um access token padrão.
 *
 * @param string $telemovel
 * @param array  $extra Reivindicações adicionais (opcional).
 * @return string
 */
function rmbz_api_jwt_make_access_token( $telemovel, array $extra = [] ) {
    $now = time();
    $payload = array_merge( [
        'iss' => home_url(),
        'sub' => $telemovel,
        'iat' => $now,
        'nbf' => $now,
        'exp' => $now + RMBZ_API_ACCESS_TOKEN_TTL,
        'typ' => 'access',
    ], $extra );

    return rmbz_api_jwt_encode( $payload );
}
