"""Safety-critical field definitions for v3 promotion gates.

Fields marked as safety-critical MUST pass their quality gates before pipeline
outputs are allowed to influence production aggregation or trading decisions.
A single safety-critical failure blocks promotion for the affected document type.

Schema version: 1.0.0
"""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    pass


class SafetyCriticalField(str, Enum):
    """Fields whose incorrect extraction can materially affect trading decisions."""

    # Company identity — wrong ticker attribution can cause trades on the wrong security
    COMPANY_IDENTITY = "company_identity"

    # Event classification — misclassifying earnings_beat as earnings_miss inverts signals
    EVENT_CLASS = "event_class"

    # Sentiment direction — wrong sentiment directly affects position direction
    SENTIMENT_DIRECTION = "sentiment_direction"

    # Numeric fact accuracy — wrong EPS or revenue magnitude affects impact estimation
    NUMERIC_FACT_VALUE = "numeric_fact_value"

    # Direct effect attribution — attributing an event to the wrong company creates false signals
    DIRECT_EFFECT_ATTRIBUTION = "direct_effect_attribution"

    # Evidence support — claims without valid evidence spans are unverifiable
    EVIDENCE_SUPPORT = "evidence_support"

    # Confidence calibration — overconfident scores bypass appropriate review thresholds
    CONFIDENCE_CALIBRATION = "confidence_calibration"


# Map each safety-critical field to its minimum required quality metric for promotion
SAFETY_CRITICAL_FIELDS: dict[SafetyCriticalField, dict[str, float]] = {
    SafetyCriticalField.COMPANY_IDENTITY: {
        "precision": 0.95,
        "recall": 0.90,
        "f1": 0.92,
    },
    SafetyCriticalField.EVENT_CLASS: {
        "macro_f1": 0.85,
        "per_class_min_f1": 0.70,
    },
    SafetyCriticalField.SENTIMENT_DIRECTION: {
        "macro_f1": 0.85,
        "direction_accuracy": 0.90,
    },
    SafetyCriticalField.NUMERIC_FACT_VALUE: {
        "exact_match": 0.80,
        "tolerance_match_5pct": 0.92,
    },
    SafetyCriticalField.DIRECT_EFFECT_ATTRIBUTION: {
        "precision": 0.93,
        "recall": 0.88,
    },
    SafetyCriticalField.EVIDENCE_SUPPORT: {
        "support_rate": 0.95,
        "offset_validity": 0.98,
    },
    SafetyCriticalField.CONFIDENCE_CALIBRATION: {
        "ece": 0.05,  # Expected Calibration Error — lower is better
        "brier_score": 0.15,  # Lower is better
    },
}


@dataclass
class SafetyGateResult:
    """Result of checking a single safety-critical field against its gate."""

    field: SafetyCriticalField
    passed: bool
    metric_name: str
    required_value: float
    actual_value: float
    is_lower_better: bool = False

    @property
    def margin(self) -> float:
        """How far above (or below for lower-is-better) the threshold."""
        if self.is_lower_better:
            return self.required_value - self.actual_value
        return self.actual_value - self.required_value


def check_safety_gates(
    metrics: dict[SafetyCriticalField, dict[str, float]],
) -> list[SafetyGateResult]:
    """Check all safety-critical fields against their promotion thresholds.

    Args:
        metrics: Measured quality metrics per safety-critical field.
                 Keys match SafetyCriticalField, values are metric_name -> value dicts.

    Returns:
        List of SafetyGateResult for each check performed.
        Any result with passed=False blocks promotion.
    """
    lower_is_better = {"ece", "brier_score"}
    results: list[SafetyGateResult] = []

    for field, thresholds in SAFETY_CRITICAL_FIELDS.items():
        measured = metrics.get(field, {})
        for metric_name, required in thresholds.items():
            actual = measured.get(metric_name)
            if actual is None:
                # Missing metric fails the gate
                results.append(
                    SafetyGateResult(
                        field=field,
                        passed=False,
                        metric_name=metric_name,
                        required_value=required,
                        actual_value=float("nan"),
                        is_lower_better=metric_name in lower_is_better,
                    )
                )
                continue

            is_lower = metric_name in lower_is_better
            if is_lower:
                passed = actual <= required
            else:
                passed = actual >= required

            results.append(
                SafetyGateResult(
                    field=field,
                    passed=passed,
                    metric_name=metric_name,
                    required_value=required,
                    actual_value=actual,
                    is_lower_better=is_lower,
                )
            )

    return results
