"""
Operational Receipt Types - From INNOVATION_OPPORTUNITIES_NOV2025.md

These receipt types support operational workflows (anchoring, override quality,
DIP reproducibility, timestamp policy, gatebook experiments, fairness deltas,
vector index refresh, selective inference).

All receipts follow schema_version "3.0" and are frozen for immutability.
"""

from __future__ import annotations
from datetime import datetime
from enum import Enum
from typing import Any, Literal, Optional
from receipts.compat.pyd import BaseModel, ConfigDictLike, Field
from receipts.base import BaseReceipt


# ============================================================================
# OverrideQualityReceipt - Guardian Approval Quality Check
# ============================================================================


class OverrideVerdict(str, Enum):
    """Verdict on override quality."""
    ACCEPT = "ACCEPT"
    REJECT = "REJECT"
    REVIEW = "REVIEW"  # Needs human review


class OverridePolicy(BaseModel):
    """Policy for override quality assessment."""
    model_config = ConfigDictLike(frozen=True, protected_namespaces=())

    min_chars: int = 50  # Minimum justification length
    min_score: float = 0.70  # Minimum quality score
    denylist: list[str] = ["override", "test", "na", "n/a", "todo"]  # Banned words


class OverrideQualityReceipt(BaseReceipt):
    """
    Override quality assessment receipt.

    Minted when:
    - GuardianApproval contains override justification
    - Quality check evaluates justification clarity

    Purpose:
    - Enforce minimum standards for override justifications
    - Prevent low-quality/generic overrides
    - Auditability of override decisions
    """
    receipt_type: Literal["OverrideQualityReceipt"] = "OverrideQualityReceipt"

    approval_id: str  # GuardianApproval receipt ID
    policy: OverridePolicy
    score: float  # Quality score [0, 1]
    verdict: OverrideVerdict
    reasons: list[str]  # Why ACCEPT/REJECT
    related_ids: list[str] = []  # Related receipts (e.g., GuardianApproval)
    schema_version: str = "3.0"


# ============================================================================
# DIPReproReceipt - Data Input Proof Reproducibility
# ============================================================================


class DIPReproProfile(str, Enum):
    """Reproducibility strictness level."""
    STRICT = "STRICT"  # Bitwise match required
    LENIENT = "LENIENT"  # Allow minor differences (rounding, BLAS)


class DIPReproReceipt(BaseReceipt):
    """
    DIP reproducibility verification receipt.

    Minted when:
    - Re-running DIP with same inputs
    - Verifying bitwise reproducibility

    Purpose:
    - Prove predictions are deterministic
    - Detect non-reproducible environments
    - Auditability for regulatory compliance
    """
    receipt_type: Literal["DIPReproReceipt"] = "DIPReproReceipt"

    dip_id: str  # Original DIP receipt ID
    profile: DIPReproProfile
    fingerprints: dict[str, str]  # python, numpy, blas, os versions
    matched: bool  # True if bitwise match
    notes: str  # Details (e.g., "Bitwise match; seeds & BLAS identical")
    schema_version: str = "3.0"


# ============================================================================
# TimestampPolicyReceipt - Timestamp Verification
# ============================================================================


class TimestampVerdict(str, Enum):
    """Timestamp policy verdict."""
    PASS = "PASS"
    WARN = "WARN"
    FAIL = "FAIL"


class TimestampPolicy(BaseModel):
    """Timestamp policy constraints."""
    model_config = ConfigDictLike(frozen=True, protected_namespaces=())

    lead_days: int = 7  # Max days before event
    lag_days: int = 30  # Max days after event


class TimestampObservation(BaseModel):
    """Observed timestamp values."""
    model_config = ConfigDictLike(frozen=True, protected_namespaces=())

    valid_time: datetime  # Event time (when prediction applies)
    system_time: datetime  # System time (when receipt created)
    skew_seconds: int  # Difference between valid_time and system_time


class TimestampPolicyReceipt(BaseReceipt):
    """
    Timestamp policy verification receipt.

    Minted when:
    - Verifying ProphecyStone timestamp within policy window

    Purpose:
    - Detect backdated predictions
    - Enforce lead/lag time windows
    - Prevent temporal cheating
    """
    receipt_type: Literal["TimestampPolicyReceipt"] = "TimestampPolicyReceipt"

    policy: TimestampPolicy
    observed: TimestampObservation
    verdict: TimestampVerdict
    related_ids: list[str] = []  # E.g., ProphecyStone ID
    schema_version: str = "3.0"


# ============================================================================
# AnchoringBatchReceipt - Blockchain/Rekor Anchoring
# ============================================================================


class AnchoringBatchWindow(BaseModel):
    """Time window for anchoring batch."""
    model_config = ConfigDictLike(frozen=True, protected_namespaces=())

    start: datetime
    end: datetime


class AnchoringBatchCounts(BaseModel):
    """Anchoring batch statistics."""
    model_config = ConfigDictLike(frozen=True, protected_namespaces=())

    total: int  # Total receipts in batch
    anchored: int  # Successfully anchored
    failed: int  # Failed to anchor


class AnchoringBatchReceipt(BaseReceipt):
    """
    Blockchain anchoring batch receipt.

    Minted when:
    - Batch of receipts anchored to Rekor/Bitcoin/Ethereum

    Purpose:
    - Prove receipt existence at specific time
    - Merkle root commitment
    - Tamper-evident audit trail

    See: RFC-002-WITNESS-STORE.md
    """
    receipt_type: Literal["AnchoringBatchReceipt"] = "AnchoringBatchReceipt"

    window: AnchoringBatchWindow
    counts: AnchoringBatchCounts
    backlog_minutes: int  # How long until all receipts anchored
    rekor_url: str = "https://rekor.sigstore.dev"  # Transparency log URL
    merkle_root: Optional[str] = None  # Merkle tree root hash
    schema_version: str = "3.0"


# ============================================================================
# GatebookExperimentReceipt - A/B Testing Gates
# ============================================================================


class GatebookDeltas(BaseModel):
    """Delta metrics between baseline and candidate gatebooks."""
    model_config = ConfigDictLike(frozen=True, protected_namespaces=())

    ECE: float  # Change in ECE worst-slice
    Coverage: float  # Change in coverage
    Refusals: float  # Change in refusal rate (percentage points)
    SavedEV: float  # Change in saved expected value (percentage points)


class GatebookExperimentReceipt(BaseReceipt):
    """
    Gatebook A/B experiment receipt.

    Minted when:
    - Testing new gate thresholds
    - Comparing baseline vs candidate gatebook

    Purpose:
    - Validate gate changes before production
    - Prove impact analysis was performed
    - Rollback support

    See: RFC-005-GATEBOOK.md
    """
    receipt_type: Literal["GatebookExperimentReceipt"] = "GatebookExperimentReceipt"

    baseline: str  # Baseline gatebook reference (file#hash)
    candidate: str  # Candidate gatebook reference
    deltas: GatebookDeltas
    verdict: Literal["PROMOTE", "REJECT", "ITERATE"] = "ITERATE"
    schema_version: str = "3.0"


# ============================================================================
# FairnessDeltaReceipt - Slice-Level Fairness Check
# ============================================================================


class FairnessVerdict(str, Enum):
    """Fairness check verdict."""
    PASS = "PASS"
    WARN = "WARN"
    VETO = "VETO"


class FairnessDeltaReceipt(BaseReceipt):
    """
    Fairness delta check receipt.

    Minted when:
    - Comparing model performance across demographic slices
    - Detecting unfair degradation on protected groups

    Purpose:
    - Enforce fairness constraints
    - Veto models that harm specific cohorts
    - Compliance with anti-discrimination laws
    """
    receipt_type: Literal["FairnessDeltaReceipt"] = "FairnessDeltaReceipt"

    worst_slice: str  # Cohort with worst degradation
    metric: str  # "coverage" | "ece" | "refusal_rate"
    baseline: float  # Baseline metric value
    candidate: float  # Candidate metric value
    delta: float  # Change (candidate - baseline)
    threshold: float = -0.03  # Max acceptable degradation
    verdict: FairnessVerdict = FairnessVerdict.PASS
    schema_version: str = "3.0"

    @property
    def is_unfair(self) -> bool:
        """Check if delta exceeds fairness threshold."""
        return self.delta < self.threshold


# ============================================================================
# VectorIndexRefreshReceipt - Search Index Maintenance
# ============================================================================


class VectorIndexRefreshReceipt(BaseReceipt):
    """
    Vector index refresh receipt.

    Minted when:
    - Compost/receipt search index rebuilt
    - Embeddings regenerated

    Purpose:
    - Track index freshness
    - Prove search quality
    - Alert on stale indices
    """
    receipt_type: Literal["VectorIndexRefreshReceipt"] = "VectorIndexRefreshReceipt"

    index_backend: str  # "sqlite:receipt_vectors" | "faiss" | "milvus"
    started_at: datetime
    finished_at: datetime
    embeddings_count: int
    prior_age_hours: int  # Age of previous index
    fresh: bool  # True if < 48 hours old
    schema_version: str = "3.0"

    @property
    def duration_seconds(self) -> float:
        """Compute refresh duration."""
        return (self.finished_at - self.started_at).total_seconds()


# ============================================================================
# SelectiveInferenceReceipt (A12) - Post-Selection Coverage Correction
# ============================================================================


class SelectionRule(BaseModel):
    """Selection rule for selective inference."""
    model_config = ConfigDictLike(frozen=True, protected_namespaces=())

    rule_type: Literal["confidence_threshold", "edge_threshold", "cohort_filter"]
    threshold: float
    description: str


class SelectiveInferenceReceipt(BaseReceipt):
    """
    Selective inference coverage correction receipt.

    Minted when:
    - Filtering predictions by confidence/edge
    - Applying post-selection coverage correction (PoSI)

    Purpose:
    - Correct coverage under selection bias
    - Honest confidence intervals after filtering
    - Regulatory compliance (don't overstate certainty)

    See: A12 in INNOVATION_OPPORTUNITIES_NOV2025.md
    """
    receipt_type: Literal["SelectiveInferenceReceipt"] = "SelectiveInferenceReceipt"

    selection_rule: SelectionRule
    correction_factor: float  # Multiplicative correction to CI width
    target_coverage: float = 0.90
    empirical_coverage_preselection: float  # Before selection
    empirical_coverage_postselection: float  # After correction
    schema_version: str = "3.0"

    @property
    def coverage_matches_target(self) -> bool:
        """Check if post-selection coverage ≈ target."""
        return abs(self.empirical_coverage_postselection - self.target_coverage) <= 0.02


# ============================================================================
# NegativeStone (C12) - Failure Pattern Receipt
# ============================================================================


class NegativeStone(BaseReceipt):
    """
    Negative/failure pattern receipt minted from recurring RefusalStones.

    Minted when:
    - Pattern mining identifies ≥3 RefusalStones with same (cohort, reason)
    - Average dignity score ≥ 0.70 (dignity gate)
    - Pattern is novel vs historical baseline

    Purpose:
    - Surface actionable failure modes
    - Guide labeling/feature collection priorities
    - Drive continuous system improvement (compost → resurrection)

    See: C12 in SND+CRIF prompt pack
    """
    receipt_type: Literal["NegativeStone"] = "NegativeStone"

    pattern_id: str = Field(description="Deterministic pattern hash")
    cohort: Optional[str] = Field(
        default=None,
        description="Cohort identifier (e.g., 'pneumonia-icu-age75p')",
    )
    reason_cluster: str = Field(
        description="Primary refusal reason or cluster representative",
    )
    frequency: int = Field(
        ge=1,
        description="Number of occurrences in detection window",
    )
    dignity_score: float = Field(
        ge=0.0,
        le=1.0,
        description="Average dignity score across pattern instances",
    )
    first_seen: datetime = Field(description="Earliest occurrence timestamp")
    last_seen: datetime = Field(description="Latest occurrence timestamp")
    representative_receipts: list[str] = Field(
        default_factory=list,
        description="Sample RefusalStone receipt IDs (max 5)",
    )
    resurrection_hypothesis: Optional[str] = Field(
        default=None,
        description="Suggested action to resolve pattern",
    )
    novelty_score: float = Field(
        ge=0.0,
        description="Novelty vs historical baseline (1.0 = completely new)",
    )
    rarity: float = Field(
        default=1.0,
        ge=0.0,
        le=1.0,
        description="Inverse frequency (rare patterns are more valuable)",
    )
    schema_version: str = "3.0"


class CompostStone(BaseReceipt):
    """
    Compost/resurrection receipt linking NegativeStone to resolution action.

    Minted when:
    - NegativeStone pattern is addressed (labeling, recalibration, policy change)
    - Resolution is validated (pattern frequency decreases)

    Purpose:
    - Close the loop on failure patterns
    - Track system antifragility (failures → improvements)
    - Audit trail for continuous learning
    """
    receipt_type: Literal["CompostStone"] = "CompostStone"

    negative_stone_id: str = Field(description="Parent NegativeStone receipt_id")
    resolution_action: str = Field(
        description="Action taken (e.g., 'labeled 50 examples', 'recalibrated model')",
    )
    action_receipts: list[str] = Field(
        default_factory=list,
        description="Receipt IDs for labeling/training/deployment actions",
    )
    frequency_before: int = Field(ge=0, description="Pattern frequency before resolution")
    frequency_after: int = Field(ge=0, description="Pattern frequency after resolution")
    improvement_ratio: float = Field(
        description="(before - after) / before; 1.0 = fully resolved",
    )
    validation_window_hours: float = Field(
        default=168.0,  # 7 days
        description="Hours of data used to validate resolution",
    )
    schema_version: str = "3.0"


class ChainContinuityReceipt(BaseReceipt):
    """
    Chain continuity verification receipt.

    Minted when:
    - A receipt log or chain segment is rotated/compacted
    - Chain head integrity is verified after the operation

    Purpose:
    - Prove that chain heads before and after rotation are consistent
    - Surface anomalies when continuity breaks
    """
    receipt_type: Literal["ChainContinuityReceipt"] = "ChainContinuityReceipt"

    tenant: str
    thread_id: str
    previous_root: Optional[str] = None
    new_root: Optional[str] = None
    continuity_verified: bool = True
    error: Optional[str] = None
    schema_version: str = "3.0"


# ============================================================================
# Export All
# ============================================================================

__all__ = [
    "OverrideVerdict",
    "OverridePolicy",
    "OverrideQualityReceipt",
    "DIPReproProfile",
    "DIPReproReceipt",
    "TimestampVerdict",
    "TimestampPolicy",
    "TimestampObservation",
    "TimestampPolicyReceipt",
    "AnchoringBatchWindow",
    "AnchoringBatchCounts",
    "AnchoringBatchReceipt",
    "GatebookDeltas",
    "GatebookExperimentReceipt",
    "FairnessVerdict",
    "FairnessDeltaReceipt",
    "VectorIndexRefreshReceipt",
    "SelectionRule",
    "SelectiveInferenceReceipt",
    "NegativeStone",
    "CompostStone",
    "ChainContinuityReceipt",
]


# ============================================================================
# Export All
# ============================================================================

__all__ = [
    "OverrideVerdict",
    "OverridePolicy",
    "OverrideQualityReceipt",
    "DIPReproProfile",
    "DIPReproReceipt",
    "TimestampVerdict",
    "TimestampPolicy",
    "TimestampObservation",
    "TimestampPolicyReceipt",
    "AnchoringBatchWindow",
    "AnchoringBatchCounts",
    "AnchoringBatchReceipt",
    "GatebookDeltas",
    "GatebookExperimentReceipt",
    "FairnessVerdict",
    "FairnessDeltaReceipt",
    "VectorIndexRefreshReceipt",
    "SelectionRule",
    "SelectiveInferenceReceipt",
    "NegativeStone",
    "CompostStone",
    "ChainContinuityReceipt",
]
