"""Tests for RCA state persistence and transitions."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from llmdebug.rca_state import (
    advance_rca_attempt,
    build_failure_signature,
    derive_session_id,
    get_rca_history,
    get_rca_status,
)


def _sample_snapshot(
    *,
    exc_type: str = "ValueError",
    exc_message: str = "invalid literal",
    line: int = 42,
    function: str = "parse_input",
) -> dict[str, Any]:
    return {
        "name": "test_parse",
        "exception": {"type": exc_type, "message": exc_message},
        "frames": [
            {
                "file_rel": "app.py",
                "line": line,
                "function": function,
            }
        ],
        "crash_frame_index": 0,
        "pytest": {"nodeid": "tests/test_app.py::test_parse"},
    }


def test_build_failure_signature_is_stable_for_identical_snapshot() -> None:
    snapshot = _sample_snapshot()
    sig_a = build_failure_signature(snapshot)
    sig_b = build_failure_signature(snapshot)
    assert sig_a == sig_b
    assert sig_a.startswith("ValueError:")


def test_advance_rca_attempt_persists_and_updates_state(tmp_path: Path) -> None:
    out_dir = str(tmp_path)
    snapshot = _sample_snapshot()
    session_id = derive_session_id(snapshot, out_dir=out_dir, fallback="mcp")
    failure_signature = build_failure_signature(snapshot)

    first = advance_rca_attempt(
        out_dir=out_dir,
        session_id=session_id,
        action="bind_evidence",
        failure_signature=failure_signature,
        evidence_refs=["crash_frame:app.py:42:parse_input"],
        gate_mode="soft",
    )
    second = advance_rca_attempt(
        out_dir=out_dir,
        session_id=session_id,
        action="hypothesize",
        failure_signature=failure_signature,
        evidence_refs=["crash_frame:app.py:42:parse_input"],
        hypothesis="Non-digit input reaches int() conversion.",
        experiment_plan="Validate input before converting.",
        gate_mode="soft",
    )

    assert first["state"] == "evidence_bound"
    assert second["state"] == "hypothesized"
    status = get_rca_status(out_dir=out_dir, session_id=session_id)
    assert status is not None
    assert status["attempt_id"] == second["attempt_id"]
    history = get_rca_history(out_dir=out_dir, session_id=session_id, limit=10)
    assert len(history) == 2


def test_strict_gate_blocks_transition_when_requirements_missing(tmp_path: Path) -> None:
    out_dir = str(tmp_path)
    snapshot = _sample_snapshot()
    session_id = derive_session_id(snapshot, out_dir=out_dir, fallback="mcp")
    failure_signature = build_failure_signature(snapshot)

    record = advance_rca_attempt(
        out_dir=out_dir,
        session_id=session_id,
        action="plan",
        failure_signature=failure_signature,
        evidence_refs=[],
        hypothesis="",
        gate_mode="strict",
    )
    assert record["gate_feedback"]["allowed"] is False
    assert record["state"] == "captured"


def test_stalled_state_after_repeated_signature_without_hypothesis_change(tmp_path: Path) -> None:
    out_dir = str(tmp_path)
    snapshot = _sample_snapshot()
    session_id = derive_session_id(snapshot, out_dir=out_dir, fallback="mcp")
    failure_signature = build_failure_signature(snapshot)

    record: dict[str, Any] | None = None
    for _ in range(3):
        record = advance_rca_attempt(
            out_dir=out_dir,
            session_id=session_id,
            action="bind_evidence",
            failure_signature=failure_signature,
            evidence_refs=["crash_frame:app.py:42:parse_input"],
            gate_mode="soft",
            stall_threshold=3,
        )

    assert record is not None
    assert record["state"] == "stalled"
    assert any("stalled" in warning.lower() for warning in record["gate_warnings"])
