<?php

namespace Miruni\RestApi;

if (!defined('ABSPATH'))
    exit; // Exit if accessed directly

use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use Exception;

class MiruniTokenValidator
{
    private string $public_key;

    public function __construct(string $public_key)
    {
        $this->public_key = $this->formatPublicKey($public_key);
    }

    private function formatPublicKey(string $key): string
    {
        // Remove any existing formatting and whitespace
        $key = str_replace(["\r", "\n", " ", "\t"], '', $key);

        // Remove headers if present (be more thorough)
        $key = str_replace([
            '-----BEGIN PUBLIC KEY-----',
            '-----END PUBLIC KEY-----',
            '-----BEGINPUBLICKEY-----',  // Also remove malformed headers
            '-----ENDPUBLICKEY-----'
        ], '', $key);

        // Make sure we only have the base64 content
        $key = trim($key);

        // Add proper formatting
        $formatted = "-----BEGIN PUBLIC KEY-----\n";
        $formatted .= chunk_split($key, 64, "\n");
        $formatted .= "-----END PUBLIC KEY-----";

        return $formatted;
    }


    /**
     * Summary of validateToken
     * @param string $token
     * @return array<mixed>|null
     */
    public function validateToken(string $token): ?array
    {
        if (!$this->public_key) {
            error_log('Miruni: No public key configured');
            return null;
        }

        $parts = explode('.', $token);
        if (count($parts) !== 3) {
            error_log('Miruni: Invalid JWT format');
            return null;
        }

        [$header, $payload, $signature] = $parts;

        try {
            // Verify signature with public key
            $data_to_verify = "$header.$payload";
            $signature_binary = $this->base64url_decode($signature);

            $verify_result = openssl_verify(
                $data_to_verify,
                $signature_binary,
                $this->public_key,
                OPENSSL_ALGO_SHA256
            );

            if ($verify_result !== 1) {
                error_log('Miruni: JWT signature verification failed');
                return null;
            }

            // Decode and validate payload
            $payload_data = json_decode($this->base64url_decode($payload), true);

            if (!$payload_data) {
                error_log('Miruni: Invalid JWT payload');
                return null;
            }

            // Check expiration
            if (isset($payload_data['expires_at']) && $payload_data['expires_at'] < time()) {
                error_log('Miruni: JWT token expired');
                return null;
            }


            return $payload_data;

        } catch (Exception $e) {
            error_log('Miruni: JWT validation error: ' . $e->getMessage());
            return null;
        }
    }

    private function base64url_decode(string $data): string
    {
        $remainder = strlen($data) % 4;
        if ($remainder) {
            $data .= str_repeat('=', 4 - $remainder);
        }
        return base64_decode(strtr($data, '-_', '+/'));
    }
}

/**
 * Base API class to be extended by specific API endpoints
 */
abstract class Miruni_BaseAPI
{
    /**
     * API namespace
     */
    protected static string $namespace = 'miruni/v1';

    /**
     * API route base
     */
    protected static string $route = '/';

    /**
     * Setup the API routes
     */
    abstract public static function setup(): void;

    /**
     * Permission callback for endpoints that require authentication
     * 
     * @param WP_REST_Request $request The request object
     * @return bool|WP_Error True if permission granted, WP_Error otherwise
     * @phpstan-ignore missingType.generics
     */
    public static function snippet_secret_required_permission_callback(WP_REST_Request $request): bool|WP_Error
    {
        error_log("Miruni: Permission callback called");

        $snippet_public_key = miruni_get_public_key();
        $request_jwt = $request->get_param('snippet_jwt');

        // New JWT authentication method
        if ($request_jwt && $snippet_public_key) {
            $validator = new MiruniTokenValidator($snippet_public_key);
            $token_data = $validator->validateToken($request_jwt);

            if ($token_data) {
                // Valid JWT token - store token info for potential use in endpoint
                $request->set_param('_miruni_token_data', $token_data);
                error_log("Miruni: Valid JWT access - Token ID: " . $token_data['token_id']);
                return true;
            } else {
                error_log("Miruni: Invalid or expired JWT token");
                return new WP_Error('rest_forbidden', 'Invalid or expired JWT token');
            }
        }



        // NOTE: snippet key auth needs to be deprecated - delete this soon
        // Check for authentication - can be customized based on needs
        $snippet_secret = miruni_get_snippet_secret_key();
        // get "snippet_secret param
        $request_secret = $request->get_param('snippet_secret');
        if ($snippet_secret !== $request_secret) {
            return new WP_Error(
                'rest_forbidden'
            );
        }

        return true;
    }

    /**
     * Get the full REST API URL for a specific endpoint
     * 
     * @param array<string,int|string> $params Optional query parameters
     * @param string|null $route Optional specific route to append
     * @return string The complete REST API URL
     */
    protected static function _get_rest_url(array $params = [], string|null $route = null): string
    {
        $base_url = home_url('/');
        $route_path = '/' . static::$namespace . ($route ?? static::$route);

        // Manually URL encode the rest_route
        $encoded_route = urlencode($route_path);

        // Build the base URL with rest_route
        $url = $base_url . '?rest_route=' . $encoded_route;

        // Add other parameters if they exist
        if (!empty($params)) {
            $url .= '&' . http_build_query($params);
        }

        return $url;
    }
}
