from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Literal, Mapping, Optional, Sequence, Set, Tuple

from receipts.compat.pyd import BaseModel, ConfigDictLike, Field, model_validator
from receipts.domains.governance import ForecastMetadata

from receipts.base import BaseReceipt, Domain
from metrics.builders import MarketCoherenceBuilder
from metrics.models import MarketCoherenceMetrics


def _extract_market_coherence_slice(metrics: Any) -> Optional[Dict[str, Any]]:
    if not isinstance(metrics, Mapping):
        return None

    candidates: List[Mapping[str, Any]] = [metrics]
    nested = metrics.get("market_coherence")
    if isinstance(nested, Mapping):
        candidates.append(nested)

    for block in candidates:
        if "max_gap" in block:
            payload: Dict[str, Any] = {"max_gap": block["max_gap"]}
            if "offenders" in block and isinstance(block["offenders"], list):
                payload["offenders"] = block["offenders"]
            return payload
    return None


class MarketsReceipt(BaseReceipt):
    """Base class for market and remediation receipts."""

    domain: Domain = Domain.MARKETS
    domain_namespace: Domain = Domain.MARKETS


class PerfBudgetReceipt(MarketsReceipt):
    """Benchmark results for hot-path performance budgets."""

    receipt_type: str = "PerfBudgetReceipt"
    domain_namespace: Domain = Domain.MARKETS
    domain: Domain = Domain.MARKETS
    period: str = Field(description="Benchmark window label (e.g., 2025-11-01)")
    sample_count: int = Field(description="Number of benchmark samples per step")
    latency_ms: Dict[str, Dict[str, float]] = Field(
        default_factory=dict,
        description="Latency percentiles keyed by step name (contains p50/p95)",
    )
    budget: Dict[str, float] = Field(
        default_factory=dict,
        description="Budget thresholds for monitored metrics (e.g., vet_path_p95_ms)",
    )
    regression_percent: Dict[str, float] = Field(
        default_factory=dict,
        description="Percent regression vs. baseline for monitored metrics",
    )
    status: Literal["OK", "WARN", "FAIL"] = Field(
        default="OK",
        description="Overall budget assessment relative to thresholds",
    )
    baseline_digest: Optional[str] = Field(
        default=None,
        description="SHA-256 digest of the baseline JSON used for comparison",
    )
    notes: List[str] = Field(default_factory=list, description="Optional annotations for operators")

    @model_validator(mode="before")
    @classmethod
    def _force_namespace(cls, data: Any) -> Any:
        if not isinstance(data, dict):
            return data
        payload = dict(data)
        payload.setdefault("domain", Domain.MARKETS.value)
        payload.setdefault("domain_namespace", Domain.MARKETS)
        return payload


class SignerHealthReceipt(MarketsReceipt):
    """Operational SLO proof for the receipt signer service."""

    receipt_type: str = "SignerHealthReceipt"
    period: str = Field(description="Collection window label (e.g., 2025-11-01).")
    sample_count: int = Field(
        ge=0,
        description="Number of signing samples captured for the SLO window.",
    )
    latency_ms: Dict[str, float] = Field(
        default_factory=dict,
        description="Latency percentiles (milliseconds) keyed by percentile label (p50, p95, etc.).",
    )
    throughput_qps: float = Field(
        ge=0.0,
        description="Observed signing throughput in signatures per second.",
    )
    targets: Dict[str, float] = Field(
        default_factory=dict,
        description="Target thresholds for SLO evaluation (e.g., {'p95_ms': 12, 'qps_min': 200}).",
    )
    status: Literal["OK", "WARN", "FAIL"] = Field(
        default="OK",
        description="Overall signer health assessment relative to the configured thresholds.",
    )
    notes: List[str] = Field(
        default_factory=list,
        description="Optional annotations describing warnings or failures observed during sampling.",
    )


class AnchoringSLOReceipt(MarketsReceipt):
    """Operational SLO proof for anchoring freshness and backlog."""

    receipt_type: str = "AnchoringSLOReceipt"
    period: str = Field(description="Collection window label (e.g., 2025-11-01).")
    inspected_count: int = Field(
        ge=0,
        description="Total number of critical receipts inspected for anchoring freshness.",
    )
    anchored_count: int = Field(
        ge=0,
        description="Number of inspected receipts with anchor metadata present.",
    )
    pending_count: int = Field(
        ge=0,
        description="Number of inspected receipts still missing anchor metadata.",
    )
    stale_count: int = Field(
        ge=0,
        description="Number of receipts missing anchors past the stale threshold (backlog).",
    )
    latency_seconds: Dict[str, float] = Field(
        default_factory=dict,
        description="Time-to-anchor statistics (seconds) keyed by percentile label (p50, p95, etc.).",
    )
    backlog: Dict[str, int] = Field(
        default_factory=dict,
        description="Breakdown of backlog categories (e.g., {'pending': 3, 'stale': 1}).",
    )
    thresholds: Dict[str, float] = Field(
        default_factory=dict,
        description="Anchoring SLO thresholds (e.g., {'p95_seconds_warn': 900, 'stale_warn': 5}).",
    )
    rekor_anchored_count: int = Field(
        ge=0,
        default=0,
        description="Number of anchors recorded with Rekor UUIDs.",
    )
    status: Literal["OK", "WARN", "FAIL"] = Field(
        default="OK",
        description="Overall anchoring health assessment relative to configured thresholds.",
    )
    notes: List[str] = Field(
        default_factory=list,
        description="Optional annotations describing warnings or failures observed during sampling.",
    )


class MarketCoherenceOffender(BaseModel):
    """Offending market entries where ML margin vs direct lines diverged."""

    asset_id: str = Field(description="Identifier for the MLB slate entry that breached coherence.")
    ml_margin: float = Field(description="Centred moneyline margin value produced by the model.")
    ml_direct: float = Field(description="Raw direct moneyline value observed from the market.")
    gap: float = Field(description="Absolute difference |ml_margin - ml_direct| for this entry.")


class MarketCoherenceReceipt(MarketsReceipt):
    """Receipt proving MLB market coherence monitors executed for the slate."""

    receipt_type: str = "MarketCoherenceReceipt"
    slate_id: str = Field(description="Slate or run identifier evaluated for MLB market coherence.")
    threshold: float = Field(ge=0.0, description="Maximum allowed absolute gap between model and direct moneylines.")
    total_games: int = Field(ge=0, description="Total number of games evaluated in the slate.")
    passing_games: int = Field(ge=0, description="Games with |ml_margin - ml_direct| ≤ threshold.")
    failing_games: int = Field(ge=0, description="Games with |ml_margin - ml_direct| > threshold.")
    pass_rate: float = Field(ge=0.0, le=1.0, description="passing_games / total_games (or 1.0 when total is zero).")
    max_gap: float = Field(ge=0.0, description="Maximum absolute gap observed across the slate.")
    worst_asset_id: Optional[str] = Field(
        default=None,
        description="Asset identifier for the largest gap encountered (None when no games evaluated).",
    )
    offenders: List[MarketCoherenceOffender] = Field(
        default_factory=list,
        description="Ordered list (descending gap) of offending games breaching the threshold (max 10 entries).",
    )
    status: Literal["PASS", "FAIL"] = Field(
        description="PASS when all games meet the threshold, FAIL otherwise.",
    )
    market_coherence_typed: Optional[MarketCoherenceMetrics] = Field(
        default=None,
        description="Typed market coherence metrics hydrated from legacy dict payloads.",
    )

    @model_validator(mode="after")
    def _hydrate_typed_metrics(self) -> "MarketCoherenceReceipt":
        metrics_block: Dict[str, Any] = {
            "max_gap": self.max_gap,
            "offenders": [offender.model_dump() for offender in self.offenders],
        }
        if self.market_coherence_typed is not None:
            return self

        legacy = getattr(self, "metrics", None)
        slice_data = _extract_market_coherence_slice(legacy) if legacy else None
        if not slice_data:
            slice_data = metrics_block
        try:
            typed = MarketCoherenceBuilder(slice_data).finalize()
        except ValueError:
            return self
        object.__setattr__(self, "market_coherence_typed", typed)
        return self


class RemediationProposalReceipt(MarketsReceipt):
    """Receipt capturing the hypothesis and remediation plan before execution."""

    receipt_type: str = "RemediationProposalReceipt"
    incident_id: Optional[str] = Field(
        default=None,
        description="Incident, DIP, or investigation identifier motivating the remediation.",
    )
    root_cause: str = Field(description="Diagnosed root cause driving the remediation effort.")
    lever: str = Field(description="Primary lever or mechanism being adjusted (gate tweak, feature fix, retrain, etc.).")
    planned_change: str = Field(description="Planned change scoped for deployment.")
    hypothesis_metrics: Dict[str, Any] = Field(
        default_factory=dict,
        description="Expected metric shifts (e.g., {'ece_overall': -0.01, 'coverage_90': 0.02}).",
    )
    baseline_window_label: Optional[str] = Field(
        default=None,
        description="Label for the baseline window used for comparison (e.g., '2025-10-01→2025-10-07').",
    )
    target_window_label: Optional[str] = Field(
        default=None,
        description="Label for the post-change evaluation window.",
    )
    method: Optional[str] = Field(
        default=None,
        description="Evaluation method (shadow, holdout, offline replay, etc.).",
    )
    owner: Optional[str] = Field(
        default=None,
        description="Person or team accountable for executing and validating the remediation.",
    )
    notes: Optional[str] = Field(default=None, description="Supplemental context or validation checklist.")


class WindowMetricsSummary(BaseModel):
    """Aggregate metrics describing a comparison window."""

    model_config = ConfigDictLike(frozen=True)

    label: str
    start_ts: Optional[datetime] = None
    end_ts: Optional[datetime] = None
    n_prophecies: int
    n_outcomes: int
    n_refusals: int
    issued_fraction: float = Field(description="Issued share (coverage) for the window.")
    brier: float
    ece_overall: float
    ece_worst: float
    refusal_rate: float
    saved_ev_mean: Optional[float] = None
    saved_ev_ci95: Optional[Tuple[float, float]] = None
    match_fraction: float = Field(
        description="Fraction of prophecies with matched outcomes used in the evaluation.",
    )
    refused_outcome_fraction: Optional[float] = Field(
        default=None,
        description="Fraction of refused prophecies with realized outcomes (for Saved-EV computation).",
    )


class MetricEffectSummary(BaseModel):
    """Effect size summary for a metric comparing baseline vs remediation windows."""

    model_config = ConfigDictLike(frozen=True)

    metric: str
    preferred_direction: Literal["higher", "lower"]
    baseline_value: float
    candidate_value: float
    delta: float
    ci_lower: float
    ci_upper: float
    significant: bool
    assessment: Literal["improved", "regressed", "inconclusive"]


class ProofOfRepairReceipt(MarketsReceipt):
    """Receipt documenting measured impact and confidence for a remediation attempt."""

    receipt_type: str = "ProofOfRepairReceipt"
    proposal_receipt_id: Optional[str] = Field(
        default=None,
        description="Reference to the originating RemediationProposalReceipt, when available.",
    )
    comparison_method: Literal["shadow", "holdout", "offline", "replay", "other"] = Field(
        description="Evaluation method used for matched window comparison.",
    )
    alpha: float = Field(default=0.05, description="Significance level used for confidence intervals.")
    baseline_window: WindowMetricsSummary
    candidate_window: WindowMetricsSummary
    effects: Dict[str, MetricEffectSummary] = Field(
        default_factory=dict,
        description="Per-metric deltas with confidence intervals and qualitative assessment.",
    )
    saved_ev_delta: Optional[MetricEffectSummary] = Field(
        default=None,
        description="Dedicated summary for Saved-EV delta when refused outcomes are present.",
    )
    verdict: Literal["IMPROVED", "REGRESSED", "INCONCLUSIVE"] = Field(
        description="Overall determination derived from metric effects and sample sufficiency.",
    )
    warnings: List[str] = Field(
        default_factory=list,
        description="Warnings raised during evaluation (e.g., insufficient sample coverage).",
    )
    notes: Optional[str] = Field(default=None, description="Additional commentary or manual validation notes.")
    forecast: Optional[ForecastMetadata] = Field(
        default=None,
        description="Optional forecast for calibration (probability remediation holds without regression).",
    )
    baseline_snapshot_refs: List[str] = Field(
        default_factory=list,
        description="Snapshot receipt identifiers linked to the baseline window.",
    )
    candidate_snapshot_refs: List[str] = Field(
        default_factory=list,
        description="Snapshot receipt identifiers linked to the post-remediation window.",
    )
    remediation_receipt_ids: List[str] = Field(
        default_factory=list,
        description="Related remediation receipts (e.g., RemediationProposalReceipt, incident logs).",
    )


class RecoveryReceipt(MarketsReceipt):
    """Receipt documenting crash recovery actions for receipt storage."""

    receipt_type: str = "RecoveryReceipt"
    asset_id: str = Field(default="receipts-ledger")
    operations: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="List of recovery operations (completed/purged/error) performed during startup check",
    )
    errors: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Errors encountered while attempting automatic recovery",
    )
    forecast: Optional[ForecastMetadata] = Field(
        default=None,
        description="Optional forecast for calibration (probability recovery avoids repeat failure within horizon).",
    )


class CompostStone(MarketsReceipt):
    """Bounty credit receipt minted from a refusal remediation backlog."""

    receipt_type: str = "CompostStone"
    refusal_receipt_id: str = Field(description="RefusalStone identifier being composted")
    asset_id: Optional[str] = Field(default=None, description="Asset identifier inherited from the refusal")
    cohort: Optional[str] = Field(default=None, description="Operational cohort or slice for tracking fixes")
    refusal_reasons: List[str] = Field(default_factory=list, description="Reasons captured from the originating refusal")
    rarity: float = Field(description="Relative rarity score for the refusal condition")
    severity: float = Field(description="Impact severity score assigned during triage")
    dignity_loss: float = Field(description="Measured dignity loss associated with the refusal")
    bounty_credits: float = Field(default=0.0, description="Minted compost bounty credits (rarity × severity × dignity_loss)")
    minted_by: Optional[str] = Field(default=None, description="Identifier for the analyst or automation minting the compost")
    minted_context: Dict[str, Any] = Field(default_factory=dict, description="Additional narrative captured at mint time")
    lineage_chain: List[str] = Field(
        default_factory=list,
        description="Ordered lineage from originating DIP through intermediate receipts to this compost",
    )
    root_dip_id: Optional[str] = Field(
        default=None,
        description="Root DIP receipt identifier anchoring this lineage",
    )
    estimated_fix_hours: Optional[float] = Field(
        default=None,
        description="Estimated effort (in hours) required to remediate the composted issue",
    )
    expected_impact: Dict[str, float] = Field(
        default_factory=dict,
        description="Expected impact deltas (e.g., {'delta_clv': 0.05, 'delta_ece': -0.01})",
    )

    @model_validator(mode="after")
    def _compute_bounty(self) -> "CompostStone":
        for field_name in ("rarity", "severity", "dignity_loss"):
            value = getattr(self, field_name)
            if value is None:
                raise ValueError(f"{field_name} is required to mint compost")
            if float(value) <= 0:
                raise ValueError(f"{field_name} must be positive")
        object.__setattr__(
            self,
            "bounty_credits",
            float(self.rarity) * float(self.severity) * float(self.dignity_loss),
        )
        lineage: List[str] = []
        seen: Set[str] = set()
        for candidate in list(self.lineage_chain or []):
            token = str(candidate)
            if token and token not in seen:
                lineage.append(token)
                seen.add(token)
        if self.refusal_receipt_id not in seen:
            lineage.append(self.refusal_receipt_id)
            seen.add(self.refusal_receipt_id)
        related_ids = getattr(self, "related_ids", {}) or {}
        root_hint: Optional[str] = None
        for key in ("root_dip_id", "dip_receipt_id", "dip_id", "dip"):
            value = related_ids.get(key)
            if isinstance(value, str) and value:
                root_hint = value
                break
        if self.root_dip_id:
            root_hint = self.root_dip_id
        if root_hint:
            token = str(root_hint)
            object.__setattr__(self, "root_dip_id", token)
            if token not in seen:
                lineage.insert(0, token)
                seen.add(token)
        else:
            for candidate in lineage:
                if str(candidate).upper().startswith("DIP"):
                    object.__setattr__(self, "root_dip_id", str(candidate))
                    break
        object.__setattr__(self, "lineage_chain", lineage)
        if not self.expected_impact:
            object.__setattr__(self, "expected_impact", {"delta_clv": 0.0, "delta_ece": 0.0})
        else:
            prepared: Dict[str, float] = {}
            for key, val in self.expected_impact.items():
                try:
                    prepared[key] = float(val)
                except (TypeError, ValueError):
                    continue
            if "delta_clv" not in prepared:
                prepared["delta_clv"] = 0.0
            if "delta_ece" not in prepared:
                prepared["delta_ece"] = 0.0
            object.__setattr__(self, "expected_impact", prepared)
        return self


class ForgivenessStone(MarketsReceipt):
    """Redemption receipt proving a compost item has been resolved."""

    receipt_type: str = "ForgivenessStone"
    compost_receipt_id: str = Field(description="Identifier of the CompostStone being redeemed")
    cohort: Optional[str] = Field(default=None, description="Cohort evaluated to confirm remediation")
    evaluation_receipt_id: Optional[str] = Field(
        default=None,
        description="Receipt identifier of the cohort re-evaluation run",
    )
    gate_snapshot: Dict[str, Any] = Field(
        default_factory=dict,
        description="Guard results collected during redemption validation",
    )
    redeemed_bounty: float = Field(description="Bounty credits released after validation")
    redeemed_by: Optional[str] = Field(default=None, description="Operator or automation confirming remediation")
    redemption_notes: Optional[str] = Field(default=None, description="Narrative summary of the remediation effort")

    @model_validator(mode="after")
    def _validate_bounty(self) -> "ForgivenessStone":
        if self.redeemed_bounty < 0:
            raise ValueError("redeemed_bounty must be non-negative")
        return self


__all__ = [
    "MarketsReceipt",
    "PerfBudgetReceipt",
    "SignerHealthReceipt",
    "AnchoringSLOReceipt",
    "MarketCoherenceOffender",
    "MarketCoherenceReceipt",
    "RemediationProposalReceipt",
    "WindowMetricsSummary",
    "MetricEffectSummary",
    "ProofOfRepairReceipt",
    "RecoveryReceipt",
    "CompostStone",
    "ForgivenessStone",
]
