// This file is licensed under the Fossorial Commercial License.
// Unauthorized use, copying, modification, or distribution is strictly prohibited.
//
// Copyright (c) 2025 Fossorial LLC. All rights reserved.

import * as crypto from "crypto";

/**
 * Validates a JWT using a public key
 * @param token - The JWT to validate
 * @param publicKey - The public key used for verification (PEM format)
 * @returns The decoded payload if validation succeeds, throws an error otherwise
 */
function validateJWT<Payload>(
    token: string,
    publicKey: string
): Payload {
    // Split the JWT into its three parts
    const parts = token.split(".");
    if (parts.length !== 3) {
        throw new Error("Invalid JWT format");
    }

    const [encodedHeader, encodedPayload, signature] = parts;

    // Decode the header to get the algorithm
    const header = JSON.parse(Buffer.from(encodedHeader, "base64").toString());
    const algorithm = header.alg;

    // Verify the signature
    const signatureInput = `${encodedHeader}.${encodedPayload}`;
    const isValid = verify(signatureInput, signature, publicKey, algorithm);

    if (!isValid) {
        throw new Error("Invalid signature");
    }

    // Decode the payload
    const payload = JSON.parse(
        Buffer.from(encodedPayload, "base64").toString()
    );

    // Check if the token has expired
    const now = Math.floor(Date.now() / 1000);
    if (payload.exp && payload.exp < now) {
        throw new Error("Token has expired");
    }

    return payload;
}

/**
 * Verifies the signature of a JWT
 */
function verify(
    input: string,
    signature: string,
    publicKey: string,
    algorithm: string
): boolean {
    let verifyAlgorithm: string;

    // Map JWT algorithm name to Node.js crypto algorithm name
    switch (algorithm) {
        case "RS256":
            verifyAlgorithm = "RSA-SHA256";
            break;
        case "RS384":
            verifyAlgorithm = "RSA-SHA384";
            break;
        case "RS512":
            verifyAlgorithm = "RSA-SHA512";
            break;
        case "ES256":
            verifyAlgorithm = "SHA256";
            break;
        case "ES384":
            verifyAlgorithm = "SHA384";
            break;
        case "ES512":
            verifyAlgorithm = "SHA512";
            break;
        default:
            throw new Error(`Unsupported algorithm: ${algorithm}`);
    }

    // Convert base64url signature to standard base64
    const base64Signature = base64URLToBase64(signature);

    // Verify the signature
    const verifier = crypto.createVerify(verifyAlgorithm);
    verifier.update(input);
    return verifier.verify(publicKey, base64Signature, "base64");
}

/**
 * Converts base64url format to standard base64
 */
function base64URLToBase64(base64url: string): string {
    // Add padding if needed
    let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");

    const pad = base64.length % 4;
    if (pad) {
        if (pad === 1) {
            throw new Error("Invalid base64url string");
        }
        base64 += "=".repeat(4 - pad);
    }

    return base64;
}

export { validateJWT };
