"""
EVHDGateReceipt - Constitutional EVHD Monotonicity Gating for High-Trust Profiles

Implements the third constitutional glyph (EVHD) as a hard gate:
- High-trust profiles MUST have EVHD monotonicity (harm increases with confidence)
- Missing EVHD → DENY for high-trust, ADVISORY for others
- Low Spearman ρ → DENY for high-trust (harm doesn't correlate with risk)
- Insufficient support → HOLD (not enough data to decide)
- Receipt emitted for every gate evaluation (audit trail)

The EVHD monotonicity gate ensures the system's harm estimates are trustworthy:
if we say something is high-risk, actual harm should be higher.
"""
from __future__ import annotations

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

from pydantic import Field

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

logger = logging.getLogger(__name__)


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

class EVHDGateStatus(str, Enum):
    """
    Status of the EVHD gate evaluation.
    
    ALLOW: EVHD monotonicity holds (ρ above threshold); proceed with action
    HOLD: Insufficient support to evaluate monotonicity; action deferred
    DENY: EVHD monotonicity violated 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 (shared with other gates)
# -----------------------------------------------------------------------------

# Core profiles that REQUIRE EVHD monotonicity to act (constitutional minimum)
CORE_HIGH_TRUST_PROFILES: Set[str] = {
    "clinical_control",
    "clinical_assist", 
    "governance_control",
}

# Extended high-trust profiles (spine-level enforcement)
EXTENDED_HIGH_TRUST_PROFILES: Set[str] = {
    "court-grade",
    "clinical-grade",
    "regulated",
    "strict",
}

# Default set used by EVHD gate
HIGH_TRUST_PROFILES: Set[str] = CORE_HIGH_TRUST_PROFILES.copy()

# Environment override to enable extended profiles
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
DEFAULT_RHO_MIN: Dict[str, float] = {
    "clinical_control": 0.70,   # Strict: require strong monotonicity
    "clinical_assist": 0.60,    # Moderate: allow some deviation
    "governance_control": 0.65, # Middle ground
    "court-grade": 0.80,        # Court-grade needs highest confidence
    "clinical-grade": 0.70,     # Same as clinical_control
    "regulated": 0.65,          # Regulated domains
    "strict": 0.70,             # Strict mode
    "regular": 0.0,             # No minimum for regular profile
    "clinical_shadow": 0.0,     # Shadow mode is observation only
}

# Minimum support (number of bins/samples) required to evaluate EVHD
DEFAULT_MIN_SUPPORT: int = 200


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

class EVHDGateConfig(BaseModel):
    """Configuration for EVHD gate thresholds and behavior."""
    
    model_config = ConfigDictLike(frozen=True)
    
    # Minimum Spearman ρ required for monotonicity
    rho_min: float = Field(
        default=0.60,
        ge=-1.0,
        le=1.0,
        description="Minimum Spearman ρ threshold for ALLOW status",
    )
    
    # Minimum support (sample count) required to evaluate
    min_support: int = Field(
        default=200,
        ge=0,
        description="Minimum samples required to evaluate EVHD",
    )
    
    # Whether to use CI lower bound instead of point estimate
    use_ci_lower_bound: bool = Field(
        default=True,
        description="If True, use lower bound of ρ CI for conservative check",
    )
    
    # Profiles that require hard gating (DENY on failure)
    high_trust_profiles: Set[str] = Field(
        default_factory=lambda: HIGH_TRUST_PROFILES.copy(),
        description="Profiles where EVHD failure results in DENY",
    )
    
    # Whether insufficient support should result in HOLD vs ADVISORY
    treat_low_support_as_hold: bool = Field(
        default=True,
        description="If True, low support → HOLD; if False → ADVISORY",
    )
    
    # 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(
    rho_min: float,
    min_support: int,
    high_trust_profiles: Set[str],
    use_ci_lower_bound: bool,
) -> str:
    """
    Deterministic version token derived from thresholds/profile set when no
    explicit gatebook/policy version is provided.
    """
    payload = {
        "rho_min": rho_min,
        "min_support": min_support,
        "use_ci_lower_bound": use_ci_lower_bound,
        "high_trust_profiles": sorted(high_trust_profiles),
    }
    return hashlib.blake2b(canonicalize(payload), digest_size=16).hexdigest()


def get_evhd_gate_config(
    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,
) -> EVHDGateConfig:
    """
    Get EVHD gate configuration for a profile/domain/risk combination.
    
    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:
        EVHDGateConfig with appropriate thresholds
    """
    # Start with profile-specific rho_min
    rho_min = DEFAULT_RHO_MIN.get(profile, 0.50)
    
    # Tighten thresholds for critical risk bands
    if risk_band in ("critical", "sensitive"):
        rho_min = max(rho_min, 0.70)
    
    # 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:
        rho_min = max(rho_min, 0.70)
    
    resolved_gate_version, resolved_policy_version = _resolve_versions(gate_version, policy_version)
    if resolved_gate_version is None:
        resolved_gate_version = _default_version_token(
            rho_min,
            min_support=DEFAULT_MIN_SUPPORT,
            high_trust_profiles=high_trust,
            use_ci_lower_bound=True,
        )
    if resolved_policy_version is None:
        resolved_policy_version = resolved_gate_version
    
    return EVHDGateConfig(
        rho_min=rho_min,
        high_trust_profiles=high_trust,
        gate_version=resolved_gate_version,
        policy_version=resolved_policy_version,
    )


# -----------------------------------------------------------------------------
# EVHDGateReceipt
# -----------------------------------------------------------------------------

class EVHDGateReceipt(BaseReceipt):
    """
    Receipt emitted for every EVHD gate evaluation.
    
    This is the constitutional record of whether EVHD monotonicity gating
    allowed or blocked an action. Even advisory evaluations emit receipts.
    
    Fields:
        envelope_id: ID of the decision envelope being gated
        episode_id: Optional episode context
        rho: Spearman correlation coefficient (monotonicity measure)
        rho_ci: Confidence interval for rho [lower, upper]
        support: Number of samples/bins used for computation
        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
    """
    
    receipt_type: str = "EVHDGateReceipt"
    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")
    
    # EVHD metrics
    rho: Optional[float] = Field(
        default=None,
        ge=-1.0,
        le=1.0,
        description="Spearman correlation coefficient for EVHD monotonicity",
    )
    rho_ci: Optional[Tuple[float, float]] = Field(
        default=None,
        description="95% confidence interval for rho [lower, upper]",
    )
    support: Optional[int] = Field(
        default=None,
        ge=0,
        description="Number of samples/bins used for EVHD computation",
    )
    
    # Gate decision
    status: EVHDGateStatus = 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)",
    )
    
    # 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.",
    )
    
    # Thresholds applied
    thresholds: Dict[str, Any] = Field(
        default_factory=dict,
        description="Thresholds used for this evaluation",
    )
    
    # Explanation
    reason: str = Field(
        default="",
        description="Human-readable explanation of gate decision",
    )
    
    # Whether CI lower bound was used
    used_ci_lower_bound: bool = Field(
        default=False,
        description="Whether the CI lower bound was used for conservative check",
    )
    
    @model_validator(mode="after")
    def _validate_status_consistency(self) -> "EVHDGateReceipt":
        """Ensure status is consistent with profile and metrics."""
        if (
            self.status == EVHDGateStatus.ALLOW
            and self.profile in HIGH_TRUST_PROFILES
            and self.rho is None
        ):
            logger.warning(
                f"EVHDGateReceipt ALLOW for high-trust profile {self.profile} "
                f"but rho is None - this may indicate a bug"
            )
        return self
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for serialization."""
        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
        # Convert tuple to list for JSON serialization
        if data.get("rho_ci") is not None:
            data["rho_ci"] = list(data["rho_ci"])
        return data


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

__all__ = [
    "EVHDGateStatus",
    "EVHDGateConfig",
    "EVHDGateReceipt",
    "CORE_HIGH_TRUST_PROFILES",
    "EXTENDED_HIGH_TRUST_PROFILES",
    "HIGH_TRUST_PROFILES",
    "DEFAULT_RHO_MIN",
    "DEFAULT_MIN_SUPPORT",
    "get_evhd_gate_config",
]
