/**
 * Cache key derivation — HMAC-SHA256 over JSON-canonical form.
 *
 * Protocol-critical: must produce identical output to Python's derive_cache_key.
 * Cross-language conformance verified by pin tests.
 *
 * Key format: "ctx:{context}:{hmac_hex}" — enables broad purge by prefix scan.
 */

import { createHmac } from 'crypto'

const CONTEXT_KEY_PREFIX = 'ctx:'

/**
 * JSON.stringify with recursively sorted keys and no whitespace.
 * Equivalent to Python's json.dumps(obj, sort_keys=True, separators=(",", ":"))
 */
function stableStringify(obj: any): string {
    if (obj === null || obj === undefined) return 'null'
    if (typeof obj === 'string') return JSON.stringify(obj)
    if (typeof obj === 'number' || typeof obj === 'boolean') return String(obj)
    if (Array.isArray(obj)) {
        return '[' + obj.map(stableStringify).join(',') + ']'
    }
    const keys = Object.keys(obj).sort()
    const pairs = keys.map(k => JSON.stringify(k) + ':' + stableStringify(obj[k]))
    return '{' + pairs.join(',') + '}'
}

/**
 * Derive a deterministic HMAC-SHA256 cache key.
 *
 * Returns "ctx:{context}:{hmac_hex}".
 */
export function deriveCacheKey(
    secret: string,
    context: string,
    params: Record<string, any>,
    userId?: string,
    rev: number = 0,
): string {
    const sortedParams: Record<string, string> = {}
    for (const [k, v] of Object.entries(params).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
        sortedParams[k] = String(v)
    }

    const keyData: Record<string, any> = { c: context, p: sortedParams, r: rev }
    if (userId !== undefined) {
        keyData.u = String(userId)
    }

    const message = stableStringify(keyData)
    const hmacHex = createHmac('sha256', secret).update(message).digest('hex')
    return `${CONTEXT_KEY_PREFIX}${context}:${hmacHex}`
}

export { CONTEXT_KEY_PREFIX }
