/**
 * AWS Signature Version 4 (SigV4) Request Signing Utility
 *
 * This is a minimal, focused implementation for signing AWS API requests.
 * It uses only Node.js native `crypto` module — no AWS SDK dependencies.
 *
 * Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html
 */
import { createHmac, createHash } from "node:crypto";
// ─────────────────────────────────────────────────────────────────────────────
// Constants
// ─────────────────────────────────────────────────────────────────────────────
const ALGORITHM = "AWS4-HMAC-SHA256";
const AWS4_REQUEST = "aws4_request";
// ─────────────────────────────────────────────────────────────────────────────
// Helper Functions
// ─────────────────────────────────────────────────────────────────────────────
/**
 * SHA256 hash of a string, returned as hex.
 */
function sha256Hex(data) {
    return createHash("sha256").update(data, "utf8").digest("hex");
}
/**
 * HMAC-SHA256, returns raw bytes (Buffer).
 */
function hmacSha256(key, data) {
    return createHmac("sha256", key).update(data, "utf8").digest();
}
/**
 * HMAC-SHA256, returns hex string.
 */
function hmacSha256Hex(key, data) {
    return createHmac("sha256", key).update(data, "utf8").digest("hex");
}
/**
 * Format a Date as AWS timestamp: YYYYMMDD'T'HHMMSS'Z'
 */
function formatAmzDate(date) {
    return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
}
/**
 * Extract just the date portion: YYYYMMDD
 */
function formatDateOnly(amzDate) {
    return amzDate.substring(0, 8);
}
/**
 * Parse host from URL.
 */
function extractHost(url) {
    const parsed = new URL(url);
    return parsed.host;
}
/**
 * Parse path from URL (without query string).
 */
function extractPath(url) {
    const parsed = new URL(url);
    const path = parsed.pathname || "/";
    return path
        .split("/")
        .map((segment) => encodeURIComponent(segment))
        .join("/")
        .replace(/%20/g, "%20") // encodeURIComponent already does %20
        .replace(/'/g, "%27") // AWS expects single quotes encoded
        .replace(/\(/g, "%28")
        .replace(/\)/g, "%29")
        .replace(/\*/g, "%2A");
}
// ─────────────────────────────────────────────────────────────────────────────
// Core Signing Logic
// ─────────────────────────────────────────────────────────────────────────────
/**
 * Step 4: Derive the signing key.
 *
 * kSecret  = "AWS4" + secretAccessKey
 * kDate    = HMAC-SHA256(kSecret, dateStamp)
 * kRegion  = HMAC-SHA256(kDate, region)
 * kService = HMAC-SHA256(kRegion, service)
 * kSigning = HMAC-SHA256(kService, "aws4_request")
 */
function deriveSigningKey(secretAccessKey, dateStamp, region, service) {
    const kSecret = "AWS4" + secretAccessKey;
    const kDate = hmacSha256(kSecret, dateStamp);
    const kRegion = hmacSha256(kDate, region);
    const kService = hmacSha256(kRegion, service);
    const kSigning = hmacSha256(kService, AWS4_REQUEST);
    return kSigning;
}
/**
 * Step 2: Build the canonical request string.
 *
 * Format:
 *   HTTPMethod
 *   CanonicalURI
 *   CanonicalQueryString (empty for us)
 *   CanonicalHeaders
 *   (blank line)
 *   SignedHeaders
 *   HashedPayload
 */
function buildCanonicalRequest(method, path, headers, signedHeadersList, payloadHash) {
    // Sort headers alphabetically
    const sortedHeaders = signedHeadersList.sort();
    // Build canonical headers: each line is "lowercase-key:trimmed-value\n"
    const canonicalHeaders = sortedHeaders
        .map((key) => `${key}:${(headers[key] ?? "").trim()}`)
        .join("\n");
    // Signed headers: semicolon-separated list
    const signedHeaders = sortedHeaders.join(";");
    return [
        method,
        path,
        "", // Query string (empty)
        canonicalHeaders + "\n", // Must end with newline
        signedHeaders,
        payloadHash
    ].join("\n");
}
/**
 * Step 3: Build the string to sign.
 *
 * Format:
 *   Algorithm
 *   RequestDateTime
 *   CredentialScope
 *   HashedCanonicalRequest
 */
function buildStringToSign(amzDate, credentialScope, canonicalRequest) {
    const hashedCanonicalRequest = sha256Hex(canonicalRequest);
    return [ALGORITHM, amzDate, credentialScope, hashedCanonicalRequest].join("\n");
}
/**
 * Step 6: Build the Authorization header value.
 */
function buildAuthorizationHeader(accessKeyId, credentialScope, signedHeaders, signature) {
    return `${ALGORITHM} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
}
// ─────────────────────────────────────────────────────────────────────────────
// Public API
// ─────────────────────────────────────────────────────────────────────────────
/**
 * Sign an AWS API request using SigV4.
 *
 * @param options - The request details and credentials
 * @returns Headers to include in the HTTP request
 *
 * @example
 * ```ts
 * const headers = signRequest({
 *   method: "POST",
 *   url: "https://bedrock-runtime.us-east-1.amazonaws.com/model/.../converse",
 *   body: JSON.stringify({ messages: [...] }),
 *   credentials: { accessKeyId: "...", secretAccessKey: "..." },
 *   region: "us-east-1",
 *   service: "bedrock"
 * });
 *
 * fetch(url, { headers: { ...headers, "Content-Type": "application/json" }, body });
 * ```
 */
export function signRequest(options) {
    const { method, url, body, credentials, region, service, timestamp } = options;
    // Timestamps (use provided timestamp for testing, or current time)
    const now = timestamp ?? new Date();
    const amzDate = formatAmzDate(now);
    const dateStamp = formatDateOnly(amzDate);
    // Step 1: Hash the payload
    const payloadHash = sha256Hex(body);
    // Extract URL components
    const host = extractHost(url);
    const path = extractPath(url);
    // Build headers to sign (sorted alphabetically)
    const headersToSign = {
        host: host,
        "x-amz-content-sha256": payloadHash,
        "x-amz-date": amzDate
    };
    // Add session token if present
    if (credentials.sessionToken) {
        headersToSign["x-amz-security-token"] = credentials.sessionToken;
    }
    const signedHeadersList = Object.keys(headersToSign);
    // Step 2: Canonical Request
    const canonicalRequest = buildCanonicalRequest(method, path, headersToSign, signedHeadersList, payloadHash);
    // Credential scope: date/region/service/aws4_request
    const credentialScope = `${dateStamp}/${region}/${service}/${AWS4_REQUEST}`;
    // Step 3: String to Sign
    const stringToSign = buildStringToSign(amzDate, credentialScope, canonicalRequest);
    // Step 4: Signing Key
    const signingKey = deriveSigningKey(credentials.secretAccessKey, dateStamp, region, service);
    // Step 5: Signature
    const signature = hmacSha256Hex(signingKey, stringToSign);
    // Step 6: Authorization Header
    const signedHeaders = signedHeadersList.sort().join(";");
    const authorization = buildAuthorizationHeader(credentials.accessKeyId, credentialScope, signedHeaders, signature);
    // Build final headers
    const result = {
        host: host,
        "x-amz-date": amzDate,
        "x-amz-content-sha256": payloadHash,
        authorization: authorization
    };
    if (credentials.sessionToken) {
        result["x-amz-security-token"] = credentials.sessionToken;
    }
    return result;
}
