"""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 _collect_gate_issues(
    action: str,
    payload: dict[str, Any],
    repeated_signature_count: int,
    exploration_budget: int,
) -> tuple[list[str], list[str], list[str], bool]:
    """Evaluate all gate checks and return (warnings, next_required, triggered_checks, hard_failed)."""
    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")
    hypothesis_advanced = bool(payload.get("hypothesis_advanced"))

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

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

    if action in {"hypothesize", "plan", "change", "verify", "resolve"} and not has_evidence:
        _add(
            "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(
            "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(
            "plan_required",
            "Missing experiment plan. Define minimal planned change and expected effect.",
            "Add a one-step experiment plan tied to your hypothesis.",
        )
    if action == "resolve":
        status = (
            str(cast(dict[str, Any], verification_result).get("status") or "")
            if isinstance(verification_result, dict)
            else ""
        )
        if status != "pass":
            _add(
                "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(
            "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.",
        )

    return warnings, next_required, triggered_checks, hard_failed


def _make_feedback(
    gate_mode: GateMode,
    warnings: list[str],
    next_required: list[str],
    triggered_checks: list[str],
    *,
    allowed: bool,
    severity: GateSeverity,
) -> GateFeedback:
    return GateFeedback(
        mode=gate_mode,
        allowed=allowed,
        severity=severity,
        warnings=tuple(warnings),
        next_required=tuple(next_required),
        triggered_checks=tuple(triggered_checks),
    )


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 _make_feedback(gate_mode, [], [], [], allowed=True, severity="info")

    warnings, next_required, triggered_checks, hard_failed = _collect_gate_issues(
        action, payload, repeated_signature_count, exploration_budget
    )

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

    # strict mode
    if hard_failed:
        exploratory = bool(payload.get("exploratory"))
        override_reason = str(payload.get("override_reason") or "").strip()
        if exploratory and override_reason:
            warnings.append(
                f"Strict gate override accepted for exploratory debugging: {override_reason}"
            )
            return _make_feedback(
                gate_mode,
                warnings,
                next_required,
                triggered_checks,
                allowed=True,
                severity="warning",
            )
        return _make_feedback(
            gate_mode, warnings, next_required, triggered_checks, allowed=False, severity="error"
        )

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