"""
Route Outcome Receipts - Audit trail for routing decisions vs actual outcomes.

This is the "self-knowledge nerve" that connects routing predictions to reality.
Every routing decision gets matched with its outcome, enabling the organism
to learn which routes actually work.

---
feature:
  id: feature.route_outcome_receipts
  name: Route Outcome Domain Receipts
  kind: module
  summary: Receipt models for routing prediction vs outcome calibration
  owners: ["@codex"]
  stage: production
  tags: [receipts, routing, learning, calibration, p0]
  depends_on: [feature.receipt_writer, feature.thalamus_router]
  inputs: [RouteDecision, ModelScores]
  outputs: [Signed receipts in ledger, calibration signals]
  invariants:
    - Every outcome evaluation emits a receipt
    - Calibration errors are always computed when data available
    - Router regret is tracked for learning
  risk_axes:
    missing_prediction: 0.01
    calibration_drift: 0.05
---
"""

from __future__ import annotations

import logging
from enum import Enum
from typing import Any, List, Optional

from pydantic import Field

from receipts.base import BaseReceipt

logger = logging.getLogger(__name__)


class RouteOutcomeVerdict(str, Enum):
    """How well did the router do?"""
    IMPROVED = "improved"      # Router chose better than baseline
    REGRESSED = "regressed"    # Router chose worse than alternatives
    NEUTRAL = "neutral"        # Roughly equal performance
    UNKNOWN = "unknown"        # Insufficient data to judge


class ModelOutcomeRecord(BaseReceipt):
    """
    Per-model outcome for a routing decision.

    Tracks predicted vs actual for each model considered.
    """
    receipt_type: str = Field(default="ModelOutcomeRecord", alias="type")
    domain: str = Field(default="route_outcome")

    # Model identification
    model_id: str = Field(description="Model identifier (e.g., 'cloud_a', 'mlx')")
    is_primary: bool = Field(description="True if this was the chosen model")

    # EVC predictions (what router thought would happen)
    evc_predicted_quality: float = Field(
        ge=0.0, le=1.0,
        description="Router's quality prediction for this model"
    )
    evc_predicted_cost: float = Field(
        ge=0.0,
        description="Router's cost prediction (USD)"
    )
    evc_predicted_latency_ms: float = Field(
        ge=0.0,
        description="Router's latency prediction (milliseconds)"
    )

    # Actual outcomes (what actually happened)
    actual_quality: Optional[float] = Field(
        default=None,
        ge=0.0, le=1.0,
        description="Measured quality score"
    )
    actual_cost: Optional[float] = Field(
        default=None,
        ge=0.0,
        description="Measured cost (USD)"
    )
    actual_latency_ms: Optional[float] = Field(
        default=None,
        ge=0.0,
        description="Measured latency (milliseconds)"
    )

    # Ground truth label if available
    ground_truth_label: Optional[str] = Field(
        default=None,
        description="Label: 'correct', 'incorrect', 'partial', etc."
    )

    # Calibration errors (actual - predicted)
    quality_error: Optional[float] = Field(
        default=None,
        description="actual_quality - predicted_quality"
    )
    cost_error: Optional[float] = Field(
        default=None,
        description="actual_cost - predicted_cost"
    )
    latency_error: Optional[float] = Field(
        default=None,
        description="actual_latency - predicted_latency"
    )

    # Privacy-preserving output comparison
    output_hash: Optional[str] = Field(
        default=None,
        description="SHA256 hash of output (first 16 chars)"
    )
    is_equal_to_primary: Optional[bool] = Field(
        default=None,
        description="Did this model produce same output as primary?"
    )


class RouteOutcomeReceipt(BaseReceipt):
    """
    Receipt for how a routing decision actually turned out.

    This is the core "learning signal" that tells the router:
    - How accurate were my EVC predictions?
    - Did I choose the best model?
    - What's my calibration error?

    Emitted after we know the outcome of a routed request.
    """
    receipt_type: str = Field(default="RouteOutcomeReceipt", alias="type")
    domain: str = Field(default="route_outcome")

    # Identifiers
    envelope_id: str = Field(description="Original request envelope ID")
    route_id: str = Field(description="Routing decision ID")

    # Task context at routing time
    task_type: str = Field(description="Task type: code, reasoning, etc.")
    task_domain: str = Field(description="Domain from ContextState")
    risk_level: float = Field(
        ge=0.0, le=1.0,
        description="Risk level at routing time"
    )
    dignity_floor: float = Field(
        ge=0.0, le=1.0,
        description="Dignity floor constraint"
    )

    # The routing decision
    chosen_model: str = Field(description="Model that was selected")
    verdict: RouteOutcomeVerdict = Field(
        description="Overall assessment: improved/regressed/neutral/unknown"
    )

    # Per-model outcomes (primary + shadows)
    model_outcomes: List[ModelOutcomeRecord] = Field(
        default_factory=list,
        description="Outcome data for each model considered"
    )

    # Aggregate calibration signals (the learning signal)
    evc_calibration_error: Optional[float] = Field(
        default=None,
        description="Primary model: actual_quality - predicted_quality"
    )
    router_regret: Optional[float] = Field(
        default=None,
        description="best_model_quality - chosen_model_quality (opportunity cost)"
    )

    # Metadata
    notes: List[str] = Field(
        default_factory=list,
        description="Free-form notes (user feedback, etc.)"
    )


class CalibrationSnapshotReceipt(BaseReceipt):
    """
    Periodic snapshot of router calibration state.

    Emitted every N outcomes to track calibration drift over time.
    """
    receipt_type: str = Field(default="CalibrationSnapshotReceipt", alias="type")
    domain: str = Field(default="route_outcome")

    # Scope
    model_id: str = Field(description="Model this snapshot is for")
    task_type: Optional[str] = Field(
        default=None,
        description="Task type (None = all tasks)"
    )

    # Calibration metrics
    sample_count: int = Field(
        ge=0,
        description="Number of outcomes in this calibration window"
    )
    mean_quality_error: float = Field(
        description="Average (actual - predicted) quality"
    )
    mean_cost_error: float = Field(
        description="Average (actual - predicted) cost"
    )
    mean_latency_error: float = Field(
        description="Average (actual - predicted) latency"
    )

    # Bias direction
    quality_bias: str = Field(
        description="'overconfident' | 'underconfident' | 'calibrated'"
    )

    # Recommended adjustment
    suggested_quality_adjustment: float = Field(
        description="Multiplier to apply to quality predictions (e.g., 0.9 = reduce by 10%)"
    )
