"""
CoherenceGateReceipt - Constitutional ΔC Gating for High-Trust Profiles

Implements the first constitutional glyph (ΔC) as a hard gate:
- High-trust profiles (clinical_control, clinical_assist, governance_control) MUST have
  a coherence verdict to proceed
- Missing ΔC → DENY for high-trust, ADVISORY for others
- Low ΔC (below threshold) → DENY for high-trust
- Receipt emitted for every gate evaluation (audit trail)

This is the "physics govern, not just observe" layer.
"""
from __future__ import annotations

import logging
from datetime import datetime, timezone
import os
import hashlib
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Set

from pydantic import Field

from receipts.compat.pyd import BaseModel, ConfigDictLike, model_validator
from receipts.jcs import canonicalize
from receipts.base import BaseReceipt, Domain

logger = logging.getLogger(__name__)


# -----------------------------------------------------------------------------
# Gate Status Enum
# -----------------------------------------------------------------------------

class CoherenceGateStatus(str, Enum):
    """
    Status of the coherence gate evaluation.
    
    ALLOW: ΔC is present and above threshold; proceed with action
    HOLD: ΔC is in hysteresis/pending state; action deferred
    DENY: ΔC is missing or below threshold for high-trust; block action
    ADVISORY_ONLY: Gate evaluated but profile doesn't require hard gating
    """
    ALLOW = "allow"
    HOLD = "hold"
    DENY = "deny"
    ADVISORY_ONLY = "advisory_only"


# -----------------------------------------------------------------------------
# High-Trust Profile Constants
# -----------------------------------------------------------------------------

# Core profiles that REQUIRE coherence verdict to act (constitutional minimum)
# These are the profiles where missing/low ΔC results in DENY
CORE_HIGH_TRUST_PROFILES: Set[str] = {
    "clinical_control",
    "clinical_assist", 
    "governance_control",
}

# Extended high-trust profiles (spine-level enforcement)
# These get hard gating when LOOM_EXTENDED_HIGH_TRUST=1 or via config
EXTENDED_HIGH_TRUST_PROFILES: Set[str] = {
    "court-grade",
    "clinical-grade",
    "regulated",
    "strict",
}

# Default set used by coherence gate (can be extended via get_gate_config_for_profile)
HIGH_TRUST_PROFILES: Set[str] = CORE_HIGH_TRUST_PROFILES.copy()

# Environment override to enable extended profiles in coherence gate
import os
if os.getenv("LOOM_EXTENDED_HIGH_TRUST", "").lower() in ("1", "true", "yes"):
    HIGH_TRUST_PROFILES = CORE_HIGH_TRUST_PROFILES | EXTENDED_HIGH_TRUST_PROFILES

# Default thresholds by profile (can be overridden via config)
DEFAULT_LAMBDA_MIN: Dict[str, float] = {
    "clinical_control": 0.6,   # Strict: require strong coherence
    "clinical_assist": 0.4,    # Moderate: allow more uncertainty
    "governance_control": 0.5, # Middle ground for governance
    "court-grade": 0.7,        # Court-grade needs highest confidence
    "clinical-grade": 0.6,     # Same as clinical_control
    "regulated": 0.5,          # Regulated domains need medium confidence
    "strict": 0.5,             # Strict mode at medium threshold
    "regular": 0.0,            # No minimum for regular profile
    "clinical_shadow": 0.0,    # Shadow mode is observation only
}


# -----------------------------------------------------------------------------
# Gate Configuration
# -----------------------------------------------------------------------------

class CoherenceGateConfig(BaseModel):
    """Configuration for coherence gate thresholds and behavior."""
    
    model_config = ConfigDictLike(frozen=True)
    
    # Minimum ΔC required for high-trust profiles
    lambda_min: float = Field(
        default=0.4,
        ge=-1.0,
        le=1.0,
        description="Minimum ΔC threshold for ALLOW status",
    )
    
    # Profiles that require hard gating (DENY on failure)
    high_trust_profiles: Set[str] = Field(
        default_factory=lambda: HIGH_TRUST_PROFILES.copy(),
        description="Profiles where missing/low ΔC results in DENY",
    )
    
    # Whether hysteresis_pending status should result in HOLD vs DENY
    treat_hysteresis_as_hold: bool = Field(
        default=True,
        description="If True, hysteresis_pending → HOLD; if False → DENY",
    )
    
    # Whether to deny on validator_untrusted status
    deny_on_untrusted_validator: bool = Field(
        default=True,
        description="If True, validator_untrusted → DENY for high-trust",
    )
    
    # Version metadata (propagated to receipts/GateReceipts)
    gate_version: Optional[str] = Field(
        default=None,
        description="Gatebook/gate version tag for lineage/pin checks.",
    )
    policy_version: Optional[str] = Field(
        default=None,
        description="Policy version tag if distinct from gate_version.",
    )


def _resolve_versions(
    gate_version: Optional[str],
    policy_version: Optional[str],
) -> tuple[Optional[str], Optional[str]]:
    """
    Resolve gate/policy version from explicit config or environment.
    
    Falls back to LOOM_GATEBOOK_VERSION / LOOM_POLICY_VERSION to avoid losing
    lineage when the caller does not pass explicit versions.
    """
    env_gate = os.getenv("LOOM_GATEBOOK_VERSION")
    env_policy = os.getenv("LOOM_POLICY_VERSION")
    return gate_version or env_gate, policy_version or env_policy


def _default_version_token(lambda_min: float, high_trust_profiles: Set[str]) -> str:
    """
    Deterministic version token derived from thresholds and profile set.
    
    Provides a stable hash when no explicit gatebook version is supplied.
    """
    payload = {
        "lambda_min": lambda_min,
        "high_trust_profiles": sorted(high_trust_profiles),
    }
    return hashlib.blake2b(canonicalize(payload), digest_size=16).hexdigest()


def get_gate_config_for_profile(
    profile: str,
    domain: Optional[str] = None,
    risk_band: Optional[str] = None,
    include_extended: bool = False,
    gate_version: Optional[str] = None,
    policy_version: Optional[str] = None,
) -> CoherenceGateConfig:
    """
    Get coherence gate configuration for a profile/domain/risk combination.
    
    This is the extension point for domain-specific thresholds.
    
    Args:
        profile: Profile name to get config for
        domain: Optional domain context (clinical, governance, etc.)
        risk_band: Optional risk level (trivial, low, medium, sensitive, critical)
        include_extended: If True, include EXTENDED_HIGH_TRUST_PROFILES in high-trust set
    
    Returns:
        CoherenceGateConfig with appropriate thresholds
    """
    # Start with profile-specific lambda_min
    lambda_min = DEFAULT_LAMBDA_MIN.get(profile, 0.0)
    
    # Tighten thresholds for critical risk bands
    if risk_band in ("critical", "sensitive"):
        lambda_min = max(lambda_min, 0.5)
    
    # Determine which profiles are high-trust for this config
    if include_extended:
        high_trust = CORE_HIGH_TRUST_PROFILES | EXTENDED_HIGH_TRUST_PROFILES
    else:
        high_trust = HIGH_TRUST_PROFILES
    
    # Clinical domain gets tighter defaults
    if domain == "clinical" and profile in high_trust:
        lambda_min = max(lambda_min, 0.5)
    
    resolved_gate_version, resolved_policy_version = _resolve_versions(gate_version, policy_version)
    if resolved_gate_version is None:
        resolved_gate_version = _default_version_token(lambda_min, high_trust)
    if resolved_policy_version is None:
        resolved_policy_version = resolved_gate_version
    
    return CoherenceGateConfig(
        lambda_min=lambda_min,
        high_trust_profiles=high_trust,
        gate_version=resolved_gate_version,
        policy_version=resolved_policy_version,
    )


# -----------------------------------------------------------------------------
# CoherenceGateReceipt
# -----------------------------------------------------------------------------

class CoherenceGateReceipt(BaseReceipt):
    """
    Receipt emitted for every coherence gate evaluation.
    
    This is the constitutional record of whether ΔC gating allowed or blocked
    an action. Even advisory evaluations emit receipts for audit.
    
    Fields:
        envelope_id: ID of the decision envelope being gated
        episode_id: Optional episode context
        delta_c: The computed ΔC value (None if unknown)
        evhd: Expected Value of Harm Delta (if computed)
        ece_worst: Worst-slice ECE (if computed)
        status: Gate decision (allow/hold/deny/advisory_only)
        profile: Profile that was evaluated
        domain: Domain of the action
        risk_band: Risk level of the action
        thresholds: Thresholds that were applied
        reason: Human-readable explanation of decision
        delta_c_status: Status from DeltaCResult if available
        violations: Any constitutional violations detected
    """
    
    receipt_type: str = "CoherenceGateReceipt"
    domain: str = Field(default=Domain.GOVERNANCE.value)
    
    # Core identification
    envelope_id: str = Field(description="ID of the decision envelope being gated")
    episode_id: Optional[str] = Field(default=None, description="Optional episode context")
    
    # ΔC metrics
    delta_c: Optional[float] = Field(
        default=None,
        ge=-1.0,
        le=1.0,
        description="Computed coherence delta [-1, 1]",
    )
    evhd: Optional[float] = Field(
        default=None,
        description="Expected Value of Harm Delta (if computed)",
    )
    ece_worst: Optional[float] = Field(
        default=None,
        ge=0.0,
        le=1.0,
        description="Worst-slice Expected Calibration Error",
    )
    
    # ΔC components (when available)
    components: Dict[str, float] = Field(
        default_factory=dict,
        description="ΔC component breakdown (phi/xi/chi if available)",
    )
    
    # Gate decision
    status: CoherenceGateStatus = Field(
        description="Gate decision: allow|hold|deny|advisory_only",
    )
    
    # Context
    profile: str = Field(description="Profile being evaluated")
    action_domain: Optional[str] = Field(
        default=None,
        description="Domain of the action (clinical, governance, etc.)",
    )
    risk_band: Optional[str] = Field(
        default=None,
        description="Risk level (trivial, low, medium, sensitive, critical)",
    )
    
    # Thresholds applied
    thresholds: Dict[str, float] = Field(
        default_factory=dict,
        description="Thresholds used for this evaluation (e.g., lambda_min)",
    )
    
    # Explanation
    reason: str = Field(
        default="",
        description="Human-readable explanation of gate decision",
    )
    
    # From DeltaCResult
    delta_c_status: Optional[str] = Field(
        default=None,
        description="Status from DeltaCResult (ok, degraded, validator_untrusted, etc.)",
    )
    violations: List[str] = Field(
        default_factory=list,
        description="Constitutional violations detected",
    )
    
    # Hysteresis state
    hysteresis_pending: bool = Field(
        default=False,
        description="Whether hysteresis is delaying enforcement",
    )
    
    # Version metadata
    gate_version: Optional[str] = Field(
        default=None,
        description="Gatebook/gate version tag for this evaluation.",
    )
    policy_version: Optional[str] = Field(
        default=None,
        description="Policy version tag if distinct from gate_version.",
    )
    
    @model_validator(mode="after")
    def _validate_status_consistency(self) -> "CoherenceGateReceipt":
        """Ensure status is consistent with profile and delta_c."""
        # High-trust profile with ALLOW should have delta_c
        if (
            self.status == CoherenceGateStatus.ALLOW
            and self.profile in HIGH_TRUST_PROFILES
            and self.delta_c is None
        ):
            logger.warning(
                f"CoherenceGateReceipt ALLOW for high-trust profile {self.profile} "
                f"but delta_c is None - this may indicate a bug"
            )
        return self
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        # Use Pydantic's model_dump (v2) or dict (v1) instead of super().to_dict()
        try:
            data = self.model_dump(mode="python")
        except AttributeError:
            data = self.dict()  # Pydantic v1 fallback
        data["status"] = self.status.value if isinstance(self.status, Enum) else self.status
        return data


# -----------------------------------------------------------------------------
# Re-exports
# -----------------------------------------------------------------------------

__all__ = [
    "CoherenceGateStatus",
    "CoherenceGateConfig",
    "CoherenceGateReceipt",
    "CORE_HIGH_TRUST_PROFILES",
    "EXTENDED_HIGH_TRUST_PROFILES",
    "HIGH_TRUST_PROFILES",
    "DEFAULT_LAMBDA_MIN",
    "get_gate_config_for_profile",
]
