"""
Cache key derivation — HMAC-SHA256 over JSON-canonical form.

Protocol-critical: every Mizan adapter must produce identical output
for identical inputs. Cross-language conformance verified by pin tests.

Scoped purge recomputes the key directly — no reverse index needed.
Broad purge uses a context prefix scan.
"""

from __future__ import annotations

import hashlib
import hmac
import json
from typing import Any

# Context prefix for broad purge (SCAN pattern)
CONTEXT_KEY_PREFIX = "ctx:"


def derive_cache_key(
    secret: str,
    context: str,
    params: dict[str, Any],
    user_id: str | None = None,
    rev: int = 0,
) -> str:
    """
    Derive a deterministic HMAC-SHA256 cache key.

    Returns a prefixed key: "ctx:{context}:{hmac_hex}" so that
    broad purge can SCAN by prefix "ctx:{context}:*".
    """
    def _normalize(v: Any) -> str:
        """Normalize values for cross-language HMAC consistency.
        Python str(True)="True" but JS String(true)="true". Use JSON-native forms."""
        if v is True:
            return "true"
        if v is False:
            return "false"
        if v is None:
            return "null"
        return str(v)

    sorted_params = {k: _normalize(v) for k, v in sorted(params.items())}

    key_data: dict[str, Any] = {"c": context, "p": sorted_params, "r": rev}
    if user_id is not None:
        key_data["u"] = str(user_id)

    message = json.dumps(key_data, sort_keys=True, separators=(",", ":"))
    hmac_hex = hmac.new(
        secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    return f"{CONTEXT_KEY_PREFIX}{context}:{hmac_hex}"
