"""
Audio PCM (Proof-of-Calibrated Meaning) helpers.

This module provides a small pilot loop for conversation outcome prophecies:
- prophecy.conversation_outcome.v1 → receipt.audio_calibration.v1

It does NOT write receipts to disk; it just builds a dict matching the schema
shape so callers can hand it to the existing receipt writer or inspect it in
tests / HUDs.
"""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Dict, List, Mapping, Optional

from receipts.base_event import generate_ulid


def _now_utc_iso() -> str:
    return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")


def multiclass_brier_score(probabilities: Mapping[str, float], outcome: str) -> float:
    """
    Compute multi-class Brier score for a categorical outcome.

    Standard definition:
        Brier = (1/K) * Σ_k (p_k - y_k)^2
    where:
        - K is the number of mutually exclusive classes
        - p_k is the predicted probability for class k
        - y_k is 1.0 for the realized class, else 0.0
    """
    if not probabilities:
        return 0.0

    # Normalize to avoid pathological sums; keep relative proportions
    total = float(sum(max(0.0, min(1.0, v)) for v in probabilities.values()))
    if total <= 0.0:
        return 0.0

    classes = list(probabilities.keys())
    k = float(len(classes))
    score = 0.0
    for cls in classes:
        p = max(0.0, min(1.0, probabilities[cls] / total))
        y = 1.0 if cls == outcome else 0.0
        score += (p - y) ** 2
    return score / k


@dataclass
class ConversationCalibrationInput:
    """Minimal view of a conversation outcome prophecy needed for calibration."""

    prophecy_id: str
    probabilities: Dict[str, float]
    conversation_ref: Optional[str] = None
    speaker_ref: Optional[str] = None
    traceparent: Optional[str] = None


def build_audio_calibration_receipt(
    prophecy: Mapping[str, Any],
    *,
    realized_outcome: str,
    evidence_refs: Optional[List[str]] = None,
    metric: str = "brier",
    drift_alert: bool = False,
) -> Dict[str, Any]:
    """
    Build a receipt.audio_calibration.v1 payload from a conversation prophecy.

    Args:
        prophecy: prophecy.conversation_outcome.v1 payload
        realized_outcome: realized outcome label (approve|decline|no_decision|...)
        evidence_refs: optional list of receipts/events used to resolve the prophecy
        metric: scoring metric name (currently only 'brier' is implemented)
        drift_alert: whether this outcome triggered a drift alert

    Returns:
        Dict payload matching schemas/receipts/audio.calibration.receipt.v1.json
        (caller is responsible for signing/persisting via the receipt writer).
    """
    schema = prophecy.get("schema")
    if schema and not schema.startswith("prophecy.conversation_outcome."):
        raise ValueError(f"Unexpected prophecy schema for audio calibration: {schema}")

    prophecy_id = prophecy.get("prophecy_id") or prophecy.get("id")
    if not prophecy_id:
        raise ValueError("prophecy_id is required to build audio calibration receipt")

    probabilities = prophecy.get("probabilities") or {}
    if not isinstance(probabilities, Mapping):
        raise ValueError("prophecy.probabilities must be a mapping")

    # Compute score
    metric_name = metric or "brier"
    if metric_name != "brier":
        raise ValueError(f"Unsupported audio calibration metric: {metric_name}")
    score_value = multiclass_brier_score(probabilities, realized_outcome)

    receipt_id = f"receipt-{generate_ulid()}"

    payload: Dict[str, Any] = {
        "schema": "receipt.audio_calibration.v1",
        "receipt_id": receipt_id,
        "traceparent": prophecy.get("traceparent"),
        "prophecy_ref": prophecy_id,
        "ts_resolved": _now_utc_iso(),
        "outcome": realized_outcome,
        "score": {
            "metric": metric_name,
            "value": score_value,
        },
        "drift_alert": bool(drift_alert),
        "evidence_refs": list(evidence_refs or []),
        # Signature is left empty here; caller should use the standard writer/signing path.
        "signature": "",
    }
    return payload


__all__ = [
    "multiclass_brier_score",
    "ConversationCalibrationInput",
    "build_audio_calibration_receipt",
]

