"""
TBR (Thread-Budget-Replay) Cryptographic Primitives

Provides deterministic hash computation for TBR snapshot verification.
Uses JCS (RFC 8785) for canonical JSON serialization to ensure
client-server parity.

Court-tier decisions require snapshot_hash computed from:
1. case_id (thread identifier)
2. replay_passport_id
3. suggestion content hash
4. patient_context hash (demographics, allergies, etc.)
5. timestamp

Usage:
    from receipts.tbr_crypto import compute_tbr_snapshot_hash, verify_tbr_snapshot

    # Client: compute hash before sending
    snapshot_hash = compute_tbr_snapshot_hash(
        case_id="PA-2024-001",
        replay_passport_id="pp-555",
        suggestion={"assessment": "...", "plan": [...]},
        patient_context={"demographics": {...}},
    )

    # Server: verify hash matches recomputed value
    is_valid = verify_tbr_snapshot(
        provided_hash=request.snapshot_hash,
        case_id=request.case_id,
        replay_passport_id=request.replay_passport_id,
        suggestion=request.suggestion,
        patient_context=request.patient_context,
    )
"""

from __future__ import annotations

import hashlib
import json
import logging
from datetime import datetime, timezone
from typing import Any, Dict, Optional

logger = logging.getLogger(__name__)

# JCS (JSON Canonicalization Scheme, RFC 8785)
try:
    import jcs
    JCS_AVAILABLE = True
except ImportError:
    JCS_AVAILABLE = False
    logger.warning("jcs library not available - using fallback JSON canonicalization")


def _canonicalize(data: Dict[str, Any]) -> bytes:
    """
    Canonicalize JSON using JCS (RFC 8785) for deterministic hashing.

    Falls back to sorted JSON if jcs not available.

    Args:
        data: Dictionary to canonicalize

    Returns:
        Canonical JSON bytes
    """
    if JCS_AVAILABLE:
        return jcs.canonicalize(data)

    # Fallback: compact JSON with sorted keys (not RFC 8785 compliant but deterministic)
    return json.dumps(data, sort_keys=True, separators=(",", ":")).encode("utf-8")


def _hash_sha256(data: bytes) -> str:
    """Compute SHA-256 hex digest."""
    return hashlib.sha256(data).hexdigest()


def _normalize_for_hash(obj: Any) -> Any:
    """
    Normalize object for deterministic hashing.

    - Rounds floats to 6 decimal places
    - Converts datetime to ISO format
    - Recursively normalizes dicts and lists
    """
    if isinstance(obj, float):
        return round(obj, 6)
    elif isinstance(obj, datetime):
        return obj.isoformat()
    elif isinstance(obj, dict):
        return {k: _normalize_for_hash(v) for k, v in obj.items()}
    elif isinstance(obj, (list, tuple)):
        return [_normalize_for_hash(item) for item in obj]
    return obj


def compute_tbr_snapshot_hash(
    case_id: str,
    replay_passport_id: str,
    suggestion: Dict[str, Any],
    patient_context: Dict[str, Any],
    timestamp: Optional[datetime] = None,
    proof_tier: str = "court",
) -> str:
    """
    Compute TBR snapshot hash for court-tier verification.

    This hash anchors a decision to its inputs, enabling:
    1. Replay verification (same inputs → same hash)
    2. Tamper detection (any change invalidates hash)
    3. Court-grade evidence (deterministic, verifiable)

    Args:
        case_id: Thread/case identifier
        replay_passport_id: Replay passport for this decision
        suggestion: Clinical suggestion being evaluated
        patient_context: Patient demographics, allergies, etc.
        timestamp: Decision timestamp (defaults to now)
        proof_tier: Proof tier (court requires snapshot_hash)

    Returns:
        SHA-256 hex digest of JCS-canonicalized snapshot

    Example:
        >>> hash = compute_tbr_snapshot_hash(
        ...     case_id="PA-2024-001",
        ...     replay_passport_id="pp-555",
        ...     suggestion={"assessment": "COPD", "plan": ["Albuterol"]},
        ...     patient_context={"demographics": {"age": 65}},
        ... )
        >>> print(hash)  # e.g., "a1b2c3d4..."
    """
    if timestamp is None:
        timestamp = datetime.now(timezone.utc)

    # Normalize inputs for deterministic hashing
    snapshot = {
        "version": "tbr_snapshot_v1",
        "case_id": case_id,
        "replay_passport_id": replay_passport_id,
        "suggestion_hash": _hash_sha256(_canonicalize(_normalize_for_hash(suggestion))),
        "context_hash": _hash_sha256(_canonicalize(_normalize_for_hash(patient_context))),
        "timestamp": timestamp.isoformat() if isinstance(timestamp, datetime) else timestamp,
        "proof_tier": proof_tier,
    }

    canonical = _canonicalize(snapshot)
    return _hash_sha256(canonical)


def verify_tbr_snapshot(
    provided_hash: str,
    case_id: str,
    replay_passport_id: str,
    suggestion: Dict[str, Any],
    patient_context: Dict[str, Any],
    timestamp: Optional[datetime] = None,
    proof_tier: str = "court",
) -> bool:
    """
    Verify TBR snapshot hash matches recomputed value.

    Server-side verification that the provided snapshot_hash
    was computed from the actual request inputs.

    Args:
        provided_hash: Hash provided by client
        case_id: Thread/case identifier
        replay_passport_id: Replay passport for this decision
        suggestion: Clinical suggestion being evaluated
        patient_context: Patient demographics, allergies, etc.
        timestamp: Decision timestamp (if known)
        proof_tier: Proof tier

    Returns:
        True if hash matches, False otherwise
    """
    if not provided_hash:
        return False

    # If no timestamp provided, we can't verify (hash depends on timestamp)
    # In production, timestamp should be extracted from passport or receipt
    if timestamp is None:
        logger.warning("Cannot verify snapshot_hash without timestamp")
        return False

    recomputed = compute_tbr_snapshot_hash(
        case_id=case_id,
        replay_passport_id=replay_passport_id,
        suggestion=suggestion,
        patient_context=patient_context,
        timestamp=timestamp,
        proof_tier=proof_tier,
    )

    # Constant-time comparison to prevent timing attacks
    return _constant_time_compare(provided_hash, recomputed)


def _constant_time_compare(a: str, b: str) -> bool:
    """Constant-time string comparison to prevent timing attacks."""
    if len(a) != len(b):
        return False
    result = 0
    for x, y in zip(a, b):
        result |= ord(x) ^ ord(y)
    return result == 0


def compute_content_hash(content: Dict[str, Any]) -> str:
    """
    Compute SHA-256 hash of arbitrary content.

    Uses JCS canonicalization for deterministic output.

    Args:
        content: Dictionary to hash

    Returns:
        SHA-256 hex digest
    """
    normalized = _normalize_for_hash(content)
    canonical = _canonicalize(normalized)
    return _hash_sha256(canonical)


# Convenience aliases
hash_suggestion = lambda s: compute_content_hash(s)
hash_patient_context = lambda c: compute_content_hash(c)


# Blake3 support (faster alternative to SHA-256)
try:
    import blake3
    BLAKE3_AVAILABLE = True
except ImportError:
    BLAKE3_AVAILABLE = False


def compute_tbr_snapshot_hash_blake3(
    case_id: str,
    replay_passport_id: str,
    suggestion: Dict[str, Any],
    patient_context: Dict[str, Any],
    timestamp: Optional[datetime] = None,
    proof_tier: str = "court",
) -> str:
    """
    Compute TBR snapshot hash using Blake3 (faster than SHA-256).

    Same semantics as compute_tbr_snapshot_hash but uses Blake3.
    Requires blake3 library: pip install blake3

    Returns:
        Blake3 hex digest (prefixed with "blake3:")
    """
    if not BLAKE3_AVAILABLE:
        raise ImportError("blake3 library not available. Install with: pip install blake3")

    if timestamp is None:
        timestamp = datetime.now(timezone.utc)

    snapshot = {
        "version": "tbr_snapshot_v1_blake3",
        "case_id": case_id,
        "replay_passport_id": replay_passport_id,
        "suggestion_hash": blake3.blake3(_canonicalize(_normalize_for_hash(suggestion))).hexdigest(),
        "context_hash": blake3.blake3(_canonicalize(_normalize_for_hash(patient_context))).hexdigest(),
        "timestamp": timestamp.isoformat() if isinstance(timestamp, datetime) else timestamp,
        "proof_tier": proof_tier,
    }

    canonical = _canonicalize(snapshot)
    return f"blake3:{blake3.blake3(canonical).hexdigest()}"


def detect_hash_algorithm(hash_value: str) -> str:
    """
    Detect hash algorithm from hash value.

    Args:
        hash_value: Hash string (possibly prefixed)

    Returns:
        "blake3" or "sha256"
    """
    if hash_value.startswith("blake3:"):
        return "blake3"
    return "sha256"
