"""Agent 资产分阶段发布的纯策略归一化与质量门禁计算。"""

from __future__ import annotations

from dataclasses import dataclass
from math import isfinite
from typing import Any, Literal

ReleaseStage = Literal["shadow", "canary", "active", "rolled_back"]


@dataclass(frozen=True)
class ReleaseGuardPolicy:
    shadow_min_samples: int = 20
    canary_min_samples: int = 100
    max_error_rate: float = 0.02
    min_precision: float = 0.98
    max_precision_drop: float = 0.02
    canary_traffic_percent: int = 5
    reviewer_quorum: int = 1
    recall_gate_enabled: bool = True
    negative_sample_percent: int = 20
    negative_min_reviewed: int = 5
    min_recall: float = 0.95
    recall_confidence_level: float = 0.95

    def to_dict(self) -> dict[str, Any]:
        return {
            "shadow_min_samples": max(1, _safe_int(self.shadow_min_samples, 20)),
            "canary_min_samples": max(1, _safe_int(self.canary_min_samples, 100)),
            "max_error_rate": _clamp01(_safe_float(self.max_error_rate, 0.02)),
            "min_precision": _clamp01(_safe_float(self.min_precision, 0.98)),
            "max_precision_drop": _clamp01(_safe_float(self.max_precision_drop, 0.02)),
            "canary_traffic_percent": max(
                1,
                min(50, _safe_int(self.canary_traffic_percent, 5)),
            ),
            "reviewer_quorum": max(1, min(2, _safe_int(self.reviewer_quorum, 1))),
            "recall_gate_enabled": _safe_bool(self.recall_gate_enabled, True),
            "negative_sample_percent": max(
                1,
                min(100, _safe_int(self.negative_sample_percent, 20)),
            ),
            "negative_min_reviewed": max(
                1,
                min(10_000, _safe_int(self.negative_min_reviewed, 5)),
            ),
            "min_recall": _clamp01(_safe_float(self.min_recall, 0.95)),
            "recall_confidence_level": _confidence_level(
                self.recall_confidence_level
            ),
        }


@dataclass(frozen=True)
class ReleaseEvaluationInput:
    total: int
    failure_count: int
    precision: float | None
    baseline_precision: float | None = None
    details: dict[str, Any] | None = None


def normalize_release_policy(value: Any) -> dict[str, Any]:
    source = value if isinstance(value, dict) else {}
    return ReleaseGuardPolicy(
        shadow_min_samples=_safe_int(source.get("shadow_min_samples"), 20),
        canary_min_samples=_safe_int(source.get("canary_min_samples"), 100),
        max_error_rate=_safe_float(source.get("max_error_rate"), 0.02),
        min_precision=_safe_float(source.get("min_precision"), 0.98),
        max_precision_drop=_safe_float(source.get("max_precision_drop"), 0.02),
        canary_traffic_percent=_safe_int(source.get("canary_traffic_percent"), 5),
        reviewer_quorum=_safe_int(source.get("reviewer_quorum"), 1),
        recall_gate_enabled=_safe_bool(source.get("recall_gate_enabled"), True),
        negative_sample_percent=_safe_int(source.get("negative_sample_percent"), 20),
        negative_min_reviewed=_safe_int(source.get("negative_min_reviewed"), 5),
        min_recall=_safe_float(source.get("min_recall"), 0.95),
        recall_confidence_level=_safe_float(
            source.get("recall_confidence_level"),
            0.95,
        ),
    ).to_dict()


def evaluate_release(
    stage: str,
    evaluation: ReleaseEvaluationInput,
    policy: dict[str, Any],
) -> dict[str, Any]:
    reasons: list[str] = []
    total = _optional_int(evaluation.total)
    failures = _optional_int(evaluation.failure_count)
    precision = _optional_probability(evaluation.precision)
    baseline = _optional_probability(evaluation.baseline_precision)
    invalid = total is None or failures is None
    normalized_total = total if total is not None else 0
    normalized_failures = failures if failures is not None else 0
    if not invalid:
        invalid = (
            normalized_total < 0
            or normalized_failures < 0
            or normalized_failures > normalized_total
        )
    if evaluation.precision is not None and precision is None:
        invalid = True
    if evaluation.baseline_precision is not None and baseline is None:
        invalid = True
    if invalid:
        reasons.append("invalid_evaluation_metrics")
        return _evaluation_result("failed", normalized_total, 1.0, precision, reasons)
    if normalized_total == 0:
        return _evaluation_result(
            "collecting",
            normalized_total,
            0.0,
            precision,
            ["no_evaluation_samples"],
        )
    error_rate = normalized_failures / normalized_total
    if error_rate > float(policy["max_error_rate"]):
        reasons.append("error_rate_exceeded")
    if precision is not None and precision < float(policy["min_precision"]):
        reasons.append("precision_below_threshold")
    if baseline is not None and precision is not None:
        if baseline - precision > float(policy["max_precision_drop"]):
            reasons.append("precision_regression_exceeded")
    details = evaluation.details if isinstance(evaluation.details, dict) else {}
    pending_release_labels = (
        details.get("metric_source") == "release_runtime_telemetry"
        and _safe_int(details.get("candidate_pending_label_count"), 0) > 0
    )
    if precision is None and not (pending_release_labels and not reasons):
        reasons.append("precision_metric_missing")
    recall_collecting_reason = ""
    if policy.get("recall_gate_enabled") is True:
        recall_status = str(details.get("negative_ground_truth_status") or "").strip()
        recall_lower_raw = details.get("recall_lower_bound")
        recall_lower = _optional_probability(recall_lower_raw)
        if details.get("metric_source") != "release_runtime_telemetry":
            recall_collecting_reason = "negative_ground_truth_evidence_missing"
        elif recall_lower_raw is not None and recall_lower is None:
            reasons.append("invalid_recall_metric")
        elif not recall_status.startswith("available") or recall_lower is None:
            recall_collecting_reason = recall_status or "recall_metric_missing"
        elif recall_lower < float(policy["min_recall"]):
            reasons.append("recall_lower_bound_below_threshold")
    if reasons:
        return _evaluation_result("failed", normalized_total, error_rate, precision, reasons)
    if precision is None:
        return _evaluation_result(
            "collecting",
            normalized_total,
            error_rate,
            precision,
            ["precision_metric_missing"],
        )
    if recall_collecting_reason:
        return _evaluation_result(
            "collecting",
            normalized_total,
            error_rate,
            precision,
            [recall_collecting_reason],
        )

    minimum = 1
    if stage == "shadow":
        minimum = int(policy["shadow_min_samples"])
    elif stage == "canary":
        minimum = int(policy["canary_min_samples"])
    if normalized_total < minimum:
        return _evaluation_result(
            "collecting",
            normalized_total,
            error_rate,
            precision,
            ["minimum_sample_not_reached"],
        )
    return _evaluation_result(
        "passed",
        normalized_total,
        error_rate,
        precision,
        ["release_quality_gate_passed"],
    )


def _evaluation_result(
    status: str,
    total: int,
    error_rate: float,
    precision: float | None,
    reasons: list[str],
) -> dict[str, Any]:
    return {
        "status": status,
        "summary": f"release evaluation {status}: {', '.join(reasons)}",
        "sample_count": max(0, total),
        "error_rate": round(error_rate, 6),
        "precision": precision,
        "reasons": reasons,
    }


def _clamp01(value: float) -> float:
    try:
        parsed = float(value)
    except (TypeError, ValueError):
        return 0.0
    if not isfinite(parsed):
        return 0.0
    return max(0.0, min(1.0, parsed))


def _optional_probability(value: float | None) -> float | None:
    if value is None:
        return None
    try:
        parsed = float(value)
    except (TypeError, ValueError):
        return None
    if not isfinite(parsed) or parsed < 0 or parsed > 1:
        return None
    return parsed


def _optional_int(value: Any) -> int | None:
    if isinstance(value, bool):
        return None
    try:
        return int(value)
    except (TypeError, ValueError, OverflowError):
        return None


def _safe_int(value: Any, default: int) -> int:
    try:
        return int(value)
    except (TypeError, ValueError, OverflowError):
        return default


def _safe_bool(value: Any, default: bool) -> bool:
    return value if isinstance(value, bool) else default


def _confidence_level(value: Any) -> float:
    parsed = _safe_float(value, 0.95)
    return parsed if parsed in {0.9, 0.95, 0.99} else 0.95


def _safe_float(value: Any, default: float) -> float:
    try:
        parsed = float(value)
    except (TypeError, ValueError, OverflowError):
        return default
    return parsed if isfinite(parsed) else default
