from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Dict, List, Literal, Optional, Union
import uuid

from pydantic import Field

from receipts.base import BaseReceipt, Domain
from receipts.compat.pyd import field_validator


class PolicyRecommendationReceipt(BaseReceipt):
    """
    Receipt capturing a policy recommendation from an external policy scientist.

    This is the CCIO side of the Quintet → CCIO policy loop:
    1. Quintet analyzes episodes, produces PolicyRecommendation
    2. CCIO receives it, creates PolicyRecommendationReceipt
    3. Disposition is recorded (initially "recorded")
    4. Later, if applied, a full law-change episode references this receipt

    Phase 1: disposition is always "recorded" or "rejected" (no auto-apply)
    Phase 2+: "applied_dev", "applied_canary", "applied_prod" dispositions

    Schema: policy_recommendation.v1
    """

    receipt_type: str = Field(default="PolicyRecommendationReceipt")
    domain: str = Field(default=Domain.GOVERNANCE.value)

    # What lever
    lever: str = Field(
        ...,
        description="Policy lever name (e.g., 'brain_temperature', 'guardian_strictness')"
    )
    context: str = Field(
        ...,
        description="Domain/context this recommendation applies to (e.g., 'development')"
    )

    # Values
    current_value: Union[float, str, int] = Field(
        ...,
        description="Current configured value in CCIO"
    )
    recommended_value: Union[float, str, int] = Field(
        ...,
        description="Value recommended by policy scientist"
    )
    delta: Optional[float] = Field(
        default=None,
        description="Numeric difference (recommended - current) when applicable"
    )
    recommended_action: str = Field(
        ...,
        description="Action suggested: 'PROMOTE', 'HOLD', 'ROLLBACK'"
    )

    # Confidence & evidence
    confidence: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Policy scientist's confidence in this recommendation (0.0-1.0)"
    )
    evidence: Dict[str, Any] = Field(
        default_factory=dict,
        description="Structured evidence: episodes_analyzed, effect_size, coverage, etc."
    )

    # Risk assessment
    risk_level: Literal["low", "medium", "high"] = Field(
        default="medium",
        description="Overall risk assessment for this change"
    )
    risk_factors: List[str] = Field(
        default_factory=list,
        description="Key risks identified during analysis"
    )

    # Provenance (from policy scientist)
    recommender: str = Field(
        default="quintet",
        description="Source of the recommendation (e.g., 'quintet', 'nightshift')"
    )
    recommender_run_id: Optional[str] = Field(
        default=None,
        description="Run/session ID on the recommender side"
    )
    recommender_generated_at: Optional[str] = Field(
        default=None,
        description="ISO8601 timestamp when recommendation was generated"
    )
    episodes_analyzed: int = Field(
        default=0,
        description="Number of episodes that went into this analysis"
    )

    # Disposition (what CCIO did with it)
    disposition: str = Field(
        default="recorded",
        description="What CCIO did: 'recorded', 'applied_dev', 'applied_canary', 'applied_prod', 'rejected'"
    )
    disposition_reason: Optional[str] = Field(
        default=None,
        description="Why that disposition was chosen"
    )
    disposition_at: Optional[datetime] = Field(
        default=None,
        description="When the disposition was decided"
    )

    # Link to law-change episode if applied
    law_change_episode_id: Optional[str] = Field(
        default=None,
        description="If disposition is 'applied_*', the episode ID of the law-change"
    )

    # Extension
    extra: Dict[str, Any] = Field(
        default_factory=dict,
        description="Reserved for additional metadata"
    )

    @field_validator("disposition_at", mode="before")
    @classmethod
    def _ensure_disposition_at_tz(cls, v: Any) -> Optional[datetime]:
        """Ensure disposition_at is timezone-aware."""
        if v is None:
            return None
        if isinstance(v, str):
            v = datetime.fromisoformat(v.replace("Z", "+00:00"))
        if isinstance(v, datetime) and v.tzinfo is None:
            return v.replace(tzinfo=timezone.utc)
        return v

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        if hasattr(self, "model_dump"):
            return self.model_dump(mode="json")
        else:
            return self.dict()


def create_policy_recommendation_receipt(
    lever: str,
    context: str,
    current_value: Union[float, str, int],
    recommended_value: Union[float, str, int],
    recommended_action: str,
    confidence: float,
    evidence: Dict[str, Any],
    recommender: str = "quintet",
    recommender_run_id: Optional[str] = None,
    recommender_generated_at: Optional[str] = None,
    episodes_analyzed: int = 0,
    risk_level: Literal["low", "medium", "high"] = "medium",
    risk_factors: Optional[List[str]] = None,
    disposition: str = "recorded",
    disposition_reason: Optional[str] = None,
    extra: Optional[Dict[str, Any]] = None,
) -> PolicyRecommendationReceipt:
    """
    Create a PolicyRecommendationReceipt from Quintet output.

    Args:
        lever: Policy lever name
        context: Domain context
        current_value: Current configured value
        recommended_value: Quintet's recommended value
        recommended_action: PROMOTE/HOLD/ROLLBACK
        confidence: Quintet's confidence (0.0-1.0)
        evidence: Structured evidence from analysis
        recommender: Source (default "quintet")
        recommender_run_id: Quintet run ID
        recommender_generated_at: When Quintet generated this
        episodes_analyzed: Number of episodes analyzed
        risk_level: low/medium/high
        risk_factors: List of identified risks
        disposition: What CCIO did with it
        disposition_reason: Why
        extra: Additional metadata

    Returns:
        PolicyRecommendationReceipt ready for storage
    """
    now = datetime.now(timezone.utc)
    receipt_id = f"prr_{uuid.uuid4().hex[:16]}"

    # Compute delta if numeric
    delta = None
    if isinstance(current_value, (int, float)) and isinstance(recommended_value, (int, float)):
        delta = float(recommended_value) - float(current_value)

    return PolicyRecommendationReceipt(
        receipt_id=receipt_id,
        lever=lever,
        context=context,
        current_value=current_value,
        recommended_value=recommended_value,
        delta=delta,
        recommended_action=recommended_action,
        confidence=confidence,
        evidence=evidence,
        risk_level=risk_level,
        risk_factors=risk_factors or [],
        recommender=recommender,
        recommender_run_id=recommender_run_id,
        recommender_generated_at=recommender_generated_at,
        episodes_analyzed=episodes_analyzed,
        disposition=disposition,
        disposition_reason=disposition_reason,
        disposition_at=now if disposition != "recorded" else None,
        extra=extra or {},
    )


class PolicyPatchReceipt(BaseReceipt):
    """
    Proposal-only record of a policy change suggestion from NightShift
    or any offline simulation. Nothing is auto-applied.
    """

    receipt_type: str = "PolicyPatchReceipt"
    domain: str = "governance"
    organ: str = "nightshift"

    def __init__(
        self,
        *,
        patch_id: str,
        target: str,  # e.g. "voice_sentinel.loop_threshold"
        old_value: Any,
        suggested_value: Any,
        evidence_summary: str,
        replay_stats: Dict[str, Any],
        ts: Optional[datetime] = None,
        parent_ids: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> None:
        ts = ts or datetime.now(timezone.utc)
        super().__init__(
            receipt_type=self.receipt_type,
            domain=self.domain,
            organ=self.organ,
            patch_id=patch_id,
            target=target,
            old_value=old_value,
            suggested_value=suggested_value,
            evidence_summary=evidence_summary,
            replay_stats=replay_stats,
            ts=ts.isoformat().replace("+00:00", "Z"),
            parent_ids=parent_ids or [],
            **kwargs,
        )


class PAGuardianRefusalReceipt(BaseReceipt):
    """
    Backward-compatible stub for PA guardian refusal receipts used in governance flows.
    """

    receipt_type: str = "PAGuardianRefusalReceipt"
    domain: str = "governance"

    def __init__(self, **kwargs: Any) -> None:
        ts = kwargs.pop("ts", None) or datetime.now(timezone.utc)
        super().__init__(
            receipt_type=self.receipt_type,
            domain=self.domain,
            ts=ts,
            **kwargs,
        )

class GatehouseRunReceipt(BaseReceipt):
    """
    Evidence record for a NightShift/Gatehouse replay run.
    """

    receipt_type: str = "GatehouseRunReceipt"
    domain: str = "governance"
    organ: str = "nightshift"

    def __init__(
        self,
        *,
        run_id: str,
        sources: List[str],
        coverage: Dict[str, Any],
        baseline_metrics: Dict[str, Any],
        candidate_metrics: Dict[str, Any],
        surprises: List[str],
        passed: bool,
        ts: Optional[datetime] = None,
        parent_ids: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> None:
        ts = ts or datetime.now(timezone.utc)
        super().__init__(
            receipt_type=self.receipt_type,
            domain=self.domain,
            organ=self.organ,
            run_id=run_id,
            sources=sources,
            coverage=coverage,
            baseline_metrics=baseline_metrics,
            candidate_metrics=candidate_metrics,
            surprises=surprises,
            passed=passed,
            ts=ts.isoformat().replace("+00:00", "Z"),
            parent_ids=parent_ids or [],
            **kwargs,
        )
