"""RCA verification gates for MCP-driven debugging workflows."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Literal, cast

GateMode = Literal["off", "soft", "strict"]
RCAState = Literal[
    "captured",
    "evidence_bound",
    "hypothesized",
    "planned",
    "changed",
    "verified",
    "resolved",
    "stalled",
]
GateSeverity = Literal["info", "warning", "error"]

_VALID_GATE_MODES = {"off", "soft", "strict"}
_DEFAULT_GATE_MODE: GateMode = "soft"


@dataclass(frozen=True)
class GateFeedback:
    """Result of evaluating gate requirements for a requested RCA action."""

    mode: GateMode
    allowed: bool
    severity: GateSeverity
    warnings: tuple[str, ...]
    next_required: tuple[str, ...]
    triggered_checks: tuple[str, ...]

    def to_dict(self) -> dict[str, Any]:
        return {
            "mode": self.mode,
            "allowed": self.allowed,
            "severity": self.severity,
            "warnings": list(self.warnings),
            "next_required": list(self.next_required),
            "triggered_checks": list(self.triggered_checks),
        }


def normalize_gate_mode(mode: str | None) -> GateMode:
    """Normalize gate mode; fallback to soft for invalid inputs."""
    if mode is None:
        return _DEFAULT_GATE_MODE
    lowered = mode.strip().lower()
    if lowered in _VALID_GATE_MODES:
        return cast(GateMode, lowered)
    import sys

    sys.stderr.write(
        f"llmdebug: warning: unrecognized gate mode {mode!r}, "
        f"defaulting to '{_DEFAULT_GATE_MODE}'. Valid: {sorted(_VALID_GATE_MODES)}\n"
    )
    return _DEFAULT_GATE_MODE


def evaluate_gate(
    *,
    mode: str | None,
    current_state: RCAState | str,
    action: str,
    payload: dict[str, Any],
    repeated_signature_count: int = 1,
    exploration_budget: int = 2,
) -> GateFeedback:
    """Evaluate whether an RCA transition/action should proceed.

    Gate policy:
    - off: do not gate transitions.
    - soft: allow transitions with warnings.
    - strict: block only when required RCA evidence is missing.
    """
    _ = current_state  # Kept for future state-aware policies.
    gate_mode = normalize_gate_mode(mode)
    if gate_mode == "off":
        return GateFeedback(
            mode="off",
            allowed=True,
            severity="info",
            warnings=(),
            next_required=(),
            triggered_checks=(),
        )

    has_evidence = bool(payload.get("evidence_refs"))
    has_hypothesis = bool(payload.get("hypothesis"))
    has_plan = bool(payload.get("experiment_plan"))
    verification_result = payload.get("verification_result")
    exploratory = bool(payload.get("exploratory"))
    override_reason = str(payload.get("override_reason") or "").strip()
    hypothesis_advanced = bool(payload.get("hypothesis_advanced"))

    warnings: list[str] = []
    next_required: list[str] = []
    triggered_checks: list[str] = []
    hard_requirements_failed = False

    def _add_issue(check_id: str, message: str, next_step: str, *, hard: bool = False) -> None:
        nonlocal hard_requirements_failed
        triggered_checks.append(check_id)
        warnings.append(message)
        if next_step not in next_required:
            next_required.append(next_step)
        hard_requirements_failed = hard_requirements_failed or hard

    if action in {"hypothesize", "plan", "change", "verify", "resolve"} and not has_evidence:
        _add_issue(
            "evidence_required",
            "Missing evidence references. Cite crash frame, traceback, or diff evidence first.",
            "Bind evidence with llmdebug_diagnose/llmdebug_frame before continuing.",
            hard=True,
        )

    if action in {"plan", "change", "verify", "resolve"} and not has_hypothesis:
        _add_issue(
            "hypothesis_required",
            "Missing root-cause hypothesis. Add a falsifiable causal claim before patching.",
            "Call llmdebug_hypothesize or provide a hypothesis statement.",
            hard=True,
        )

    if action in {"change", "verify", "resolve"} and not has_plan:
        _add_issue(
            "plan_required",
            "Missing experiment plan. Define minimal planned change and expected effect.",
            "Add a one-step experiment plan tied to your hypothesis.",
            hard=False,
        )

    if action == "resolve":
        status = ""
        if isinstance(verification_result, dict):
            status = str(verification_result.get("status") or "")
        if status != "pass":
            _add_issue(
                "verification_required",
                "Cannot mark resolved without a passing verification result.",
                "Run at least one verification check and report status=pass.",
                hard=True,
            )

    if (
        action in {"change", "verify", "resolve"}
        and repeated_signature_count > max(exploration_budget, 1)
        and not hypothesis_advanced
    ):
        _add_issue(
            "loop_protection",
            (
                "Failure signature repeated without hypothesis change. "
                "Exploration is still allowed, but RCA should be updated."
            ),
            "Revise hypothesis or gather new evidence before repeating equivalent patch attempts.",
            hard=False,
        )

    if gate_mode == "soft":
        severity: GateSeverity = "warning" if warnings else "info"
        return GateFeedback(
            mode=gate_mode,
            allowed=True,
            severity=severity,
            warnings=tuple(warnings),
            next_required=tuple(next_required),
            triggered_checks=tuple(triggered_checks),
        )

    # strict mode
    if hard_requirements_failed:
        if exploratory and override_reason:
            warnings.append(
                f"Strict gate override accepted for exploratory debugging: {override_reason}"
            )
            return GateFeedback(
                mode=gate_mode,
                allowed=True,
                severity="warning",
                warnings=tuple(warnings),
                next_required=tuple(next_required),
                triggered_checks=tuple(triggered_checks),
            )
        return GateFeedback(
            mode=gate_mode,
            allowed=False,
            severity="error",
            warnings=tuple(warnings),
            next_required=tuple(next_required),
            triggered_checks=tuple(triggered_checks),
        )

    severity = "warning" if warnings else "info"
    return GateFeedback(
        mode=gate_mode,
        allowed=True,
        severity=severity,
        warnings=tuple(warnings),
        next_required=tuple(next_required),
        triggered_checks=tuple(triggered_checks),
    )
