"""
ProofOfRepairReceipt — Compost loop closure receipt.

Emitted when a previously failed gate check is repaired through:
- Model recalibration (ECE threshold met)
- Policy refinement (coherence restored)
- Data augmentation (EVHD support increased)

This receipt closes the feedback loop by proving that the organism
detected a problem, attempted repair, and achieved measurable improvement.

Part of the self-healing architecture that implements:
- Kairos (timing of repair)
- Mnemosyne (memory of past failures)
- Brainstem reflexes (automatic repair triggers)
"""
from __future__ import annotations

from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, List, Optional, Literal

from pydantic import BaseModel, Field
try:
    from pydantic import ConfigDict as ConfigDictLike
except ImportError:
    ConfigDictLike = dict

from receipts.base import BaseReceipt


class RepairStrategy(str, Enum):
    """Strategy used to effect the repair."""
    RECALIBRATION = "recalibration"      # Model recalibration (isotonic, Platt, etc.)
    POLICY_REFINEMENT = "policy_refinement"  # Policy parameter adjustment
    DATA_AUGMENTATION = "data_augmentation"  # Additional training data
    THRESHOLD_ADJUSTMENT = "threshold_adjustment"  # Threshold relaxation (risk trade-off)
    DOMAIN_RESTRICTION = "domain_restriction"  # Narrowed scope of operation
    HUMAN_REVIEW = "human_review"         # Human-in-the-loop override
    ENSEMBLE_FALLBACK = "ensemble_fallback"  # Switched to fallback model
    RETRY_WITH_CONTEXT = "retry_with_context"  # Retried with additional context
    UNKNOWN = "unknown"


class RepairOutcome(str, Enum):
    """Outcome of the repair attempt."""
    SUCCESS = "success"           # Metric now passes threshold
    PARTIAL = "partial"           # Improvement but not to threshold
    FAILED = "failed"             # No improvement or regression
    PENDING = "pending"           # Repair in progress, not yet verified


class GateType(str, Enum):
    """Type of gate that was repaired."""
    COHERENCE = "coherence"       # ΔC coherence gate
    CALIBRATION = "calibration"   # ECE calibration gate
    EVHD = "evhd"                 # EVHD monotonicity gate
    GUARDIAN = "guardian"         # Guardian constitutional check
    TOOL_SAFETY = "tool_safety"   # Tool safety pre-check
    ARCHITECT = "architect"       # Architect structural validation


class ProofOfRepairReceipt(BaseReceipt):
    """
    Receipt proving that a constitutional gate violation was repaired.
    
    Emitted when the compost loop detects a failure, attempts repair,
    and achieves measurable improvement. This closes the feedback loop
    and provides audit trail for self-healing operations.
    
    Key fields:
    - original_failure_id: Receipt ID of the original failing gate
    - gate_type: Which constitutional gate failed
    - metric_before: Value before repair
    - metric_after: Value after repair
    - threshold: The threshold that needed to be met
    - strategy: How the repair was attempted
    - outcome: Whether repair succeeded
    
    Lineage:
    - parent_ids includes original failure receipt
    - Links to learning signal that triggered repair
    - Links to any policy update receipts generated
    """

    receipt_type: Literal["ProofOfRepairReceipt"] = "ProofOfRepairReceipt"
    domain: str = "organism"
    organ: str = "compost_loop"

    # Original failure reference
    original_failure_id: str = Field(
        description="Receipt ID of the original gate failure being repaired"
    )
    original_envelope_id: Optional[str] = Field(
        default=None,
        description="Envelope ID where the original failure occurred"
    )
    original_failure_ts: Optional[datetime] = Field(
        default=None,
        description="Timestamp when original failure occurred"
    )
    
    # Gate identification
    gate_type: GateType = Field(
        description="Type of gate that failed and was repaired"
    )
    profile: Optional[str] = Field(
        default=None,
        description="Profile under which the gate was operating"
    )
    domain_context: Optional[str] = Field(
        default=None,
        description="Domain context (clinical, governance, etc.)"
    )
    
    # Metric trajectory
    metric_name: str = Field(
        default="",
        description="Name of the metric being repaired (e.g., 'ece_worst', 'rho', 'delta_c')"
    )
    metric_before: Optional[float] = Field(
        default=None,
        description="Metric value before repair attempt"
    )
    metric_after: Optional[float] = Field(
        default=None,
        description="Metric value after repair attempt"
    )
    threshold: Optional[float] = Field(
        default=None,
        description="Threshold that needed to be met"
    )
    improvement_pct: Optional[float] = Field(
        default=None,
        description="Percentage improvement (positive = better)"
    )
    
    # Repair details
    strategy: RepairStrategy = Field(
        default=RepairStrategy.UNKNOWN,
        description="Strategy used to attempt repair"
    )
    strategy_details: Dict[str, Any] = Field(
        default_factory=dict,
        description="Additional details about the repair strategy"
    )
    outcome: RepairOutcome = Field(
        default=RepairOutcome.PENDING,
        description="Outcome of the repair attempt"
    )
    
    # Timing
    repair_started_at: Optional[datetime] = Field(
        default=None,
        description="When repair attempt started"
    )
    repair_completed_at: Optional[datetime] = Field(
        default=None,
        description="When repair attempt completed"
    )
    repair_duration_ms: Optional[float] = Field(
        default=None,
        description="Duration of repair in milliseconds"
    )
    
    # Related receipts
    learning_signal_id: Optional[str] = Field(
        default=None,
        description="Learning signal that triggered this repair"
    )
    policy_update_ids: List[str] = Field(
        default_factory=list,
        description="Policy update receipts generated during repair"
    )
    
    # Verification
    verification_envelope_id: Optional[str] = Field(
        default=None,
        description="Envelope ID used to verify the repair"
    )
    verification_passed: bool = Field(
        default=False,
        description="Whether verification envelope passed the gate"
    )
    
    # Risk assessment
    risk_delta: Optional[str] = Field(
        default=None,
        description="Change in risk level (e.g., 'critical→medium')"
    )
    residual_risk: Optional[str] = Field(
        default=None,
        description="Remaining risk after repair"
    )
    
    @classmethod
    def from_repair(
        cls,
        original_failure_id: str,
        gate_type: GateType,
        metric_name: str,
        metric_before: float,
        metric_after: float,
        threshold: float,
        strategy: RepairStrategy,
        parent_ids: Optional[List[str]] = None,
        profile: Optional[str] = None,
        domain_context: Optional[str] = None,
        original_envelope_id: Optional[str] = None,
        original_failure_ts: Optional[datetime] = None,
        strategy_details: Optional[Dict[str, Any]] = None,
        learning_signal_id: Optional[str] = None,
        policy_update_ids: Optional[List[str]] = None,
        verification_envelope_id: Optional[str] = None,
        repair_started_at: Optional[datetime] = None,
        ts: Optional[datetime] = None,
    ) -> "ProofOfRepairReceipt":
        """
        Create a ProofOfRepairReceipt from repair data.
        
        Automatically calculates:
        - improvement_pct
        - outcome (based on whether metric_after meets threshold)
        - repair_duration_ms
        """
        from receipts.base_event import generate_ulid
        
        ts = ts or datetime.now(timezone.utc)
        receipt_id = f"repair_{generate_ulid()}"
        
        # Calculate improvement percentage
        if metric_before is not None and metric_before != 0:
            # For metrics where higher is better (like rho)
            if gate_type == GateType.EVHD:
                improvement_pct = ((metric_after - metric_before) / abs(metric_before)) * 100
            # For metrics where lower is better (like ECE)
            elif gate_type == GateType.CALIBRATION:
                improvement_pct = ((metric_before - metric_after) / abs(metric_before)) * 100
            # For coherence delta_c (higher is better)
            else:
                improvement_pct = ((metric_after - metric_before) / abs(metric_before)) * 100
        else:
            improvement_pct = None
        
        # Determine outcome
        if gate_type in (GateType.EVHD, GateType.COHERENCE):
            # Higher is better
            if metric_after >= threshold:
                outcome = RepairOutcome.SUCCESS
            elif metric_after > metric_before:
                outcome = RepairOutcome.PARTIAL
            else:
                outcome = RepairOutcome.FAILED
        else:
            # Lower is better (ECE)
            if metric_after <= threshold:
                outcome = RepairOutcome.SUCCESS
            elif metric_after < metric_before:
                outcome = RepairOutcome.PARTIAL
            else:
                outcome = RepairOutcome.FAILED
        
        # Calculate duration
        repair_duration_ms = None
        if repair_started_at:
            repair_duration_ms = (ts - repair_started_at).total_seconds() * 1000
        
        # Ensure parent_ids includes original failure
        parent_ids_final = list(parent_ids or [])
        if original_failure_id not in parent_ids_final:
            parent_ids_final.insert(0, original_failure_id)
        if learning_signal_id and learning_signal_id not in parent_ids_final:
            parent_ids_final.append(learning_signal_id)
        
        return cls(
            receipt_id=receipt_id,
            original_failure_id=original_failure_id,
            original_envelope_id=original_envelope_id,
            original_failure_ts=original_failure_ts,
            gate_type=gate_type,
            profile=profile,
            domain_context=domain_context,
            metric_name=metric_name,
            metric_before=metric_before,
            metric_after=metric_after,
            threshold=threshold,
            improvement_pct=improvement_pct,
            strategy=strategy,
            strategy_details=strategy_details or {},
            outcome=outcome,
            repair_started_at=repair_started_at,
            repair_completed_at=ts,
            repair_duration_ms=repair_duration_ms,
            learning_signal_id=learning_signal_id,
            policy_update_ids=policy_update_ids or [],
            verification_envelope_id=verification_envelope_id,
            verification_passed=(outcome == RepairOutcome.SUCCESS),
            parent_ids=parent_ids_final,
            ts=ts,
        )

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to dict for logging/storage."""
        return {
            "receipt_id": self.receipt_id,
            "receipt_type": self.receipt_type,
            "domain": self.domain,
            "organ": self.organ,
            "original_failure_id": self.original_failure_id,
            "original_envelope_id": self.original_envelope_id,
            "original_failure_ts": self.original_failure_ts.isoformat() if self.original_failure_ts else None,
            "gate_type": self.gate_type.value,
            "profile": self.profile,
            "domain_context": self.domain_context,
            "metric_name": self.metric_name,
            "metric_before": self.metric_before,
            "metric_after": self.metric_after,
            "threshold": self.threshold,
            "improvement_pct": self.improvement_pct,
            "strategy": self.strategy.value,
            "strategy_details": self.strategy_details,
            "outcome": self.outcome.value,
            "repair_started_at": self.repair_started_at.isoformat() if self.repair_started_at else None,
            "repair_completed_at": self.repair_completed_at.isoformat() if self.repair_completed_at else None,
            "repair_duration_ms": self.repair_duration_ms,
            "learning_signal_id": self.learning_signal_id,
            "policy_update_ids": self.policy_update_ids,
            "verification_envelope_id": self.verification_envelope_id,
            "verification_passed": self.verification_passed,
            "risk_delta": self.risk_delta,
            "residual_risk": self.residual_risk,
            "parent_ids": self.parent_ids,
            "ts": self.ts.isoformat() if self.ts else None,
        }

    @property
    def is_success(self) -> bool:
        """Check if repair was successful."""
        return self.outcome == RepairOutcome.SUCCESS
    
    @property
    def is_partial(self) -> bool:
        """Check if repair achieved partial improvement."""
        return self.outcome == RepairOutcome.PARTIAL
    
    @property
    def is_failed(self) -> bool:
        """Check if repair failed."""
        return self.outcome == RepairOutcome.FAILED


def emit_proof_of_repair(
    original_failure_id: str,
    gate_type: GateType,
    metric_name: str,
    metric_before: float,
    metric_after: float,
    threshold: float,
    strategy: RepairStrategy,
    **kwargs,
) -> ProofOfRepairReceipt:
    """
    Convenience function to create and emit a ProofOfRepairReceipt.
    
    Example:
        receipt = emit_proof_of_repair(
            original_failure_id="cal_gate_001",
            gate_type=GateType.CALIBRATION,
            metric_name="ece_worst",
            metric_before=0.10,
            metric_after=0.025,
            threshold=0.03,
            strategy=RepairStrategy.RECALIBRATION,
            profile="clinical_control",
        )
    """
    return ProofOfRepairReceipt.from_repair(
        original_failure_id=original_failure_id,
        gate_type=gate_type,
        metric_name=metric_name,
        metric_before=metric_before,
        metric_after=metric_after,
        threshold=threshold,
        strategy=strategy,
        **kwargs,
    )
