"""PAPER-77 part 3 — self-check surfaces pytest stderr on CRASHED.

The canonical scenario: an adopter wires the TS-23 timeout config
in pyproject without installing `pytest-timeout`. pytest rejects
`timeout = 30` as an unknown config option AT STARTUP; every
supervisor chunk crashes; every canned scenario reports CRASHED;
the consumer sees `[mtest --self-check] FAIL` with no hint why.

These tests drive `_run_canned` directly with synthesized JSONL
records and capture the printed diagnostic. The expectation: the
"Unknown config option" hint + a PAPER-77 cross-reference appear
EVEN WITHOUT --verbose, because first-contact adopters won't know
the flag exists.
"""

from __future__ import annotations

import io
import json
from contextlib import redirect_stdout
from typing import TYPE_CHECKING

from mgf.test_supervisor import self_check as sc_module

if TYPE_CHECKING:
    from pathlib import Path


def _seed_supervisor_state(
    repo_root: Path,
    jsonl_record: dict | None,
) -> None:
    """Pre-create the .gates/last-run/run.jsonl that _run_canned reads.

    By monkeypatching Supervisor.run to a no-op, we can drive the
    self-check's assertion logic with any synthesized JSONL we want.
    """
    gates = repo_root / ".gates" / "last-run"
    gates.mkdir(parents=True, exist_ok=True)
    if jsonl_record is None:
        # Touch the file but leave it empty.
        (gates / "run.jsonl").write_text("", encoding="utf-8")
    else:
        (gates / "run.jsonl").write_text(
            json.dumps(jsonl_record) + "\n",
            encoding="utf-8",
        )


class TestSelfCheckSurfacesStderrOnCrashed:
    def test_crashed_outcome_surfaces_captured_stderr(
        self,
        tmp_path: Path,
        monkeypatch,
    ) -> None:
        # Build a fake project structure for the self-check to copy
        # pyproject from.
        (tmp_path / "pyproject.toml").write_text(
            '[project]\nname="x"\nversion="0.1.0"\n',
            encoding="utf-8",
        )

        # Monkeypatch the Supervisor.run to be a no-op (we control
        # the JSONL ourselves).
        def _fake_run(self):
            _seed_supervisor_state(
                self.cfg.repo_root,
                {
                    "outcome": "crashed",
                    "code": "CHUNK_EXIT_4",
                    "summary": "chunk exited with 4",
                    "captured_stderr": (
                        "ERROR: file or directory not found: nonexistent\n"
                        "pytest.PytestConfigWarning: Unknown config option: timeout\n"
                    ),
                },
            )
            return 1

        # Also stub collect_tests so it returns ≥1 fake test
        # (otherwise _run_canned bails before reaching the assertion
        # branch).
        from mgf.test_supervisor import supervisor as sup_module

        monkeypatch.setattr(sup_module.Supervisor, "run", _fake_run)
        monkeypatch.setattr(
            sup_module,
            "collect_tests",
            lambda _cmd, _root: [("tests/canned.py::test_x", ())],
        )

        # Drive _run_canned expecting `passed` but getting `crashed`.
        buf = io.StringIO()
        with redirect_stdout(buf):
            ok = sc_module._run_canned(
                tmp_path,
                ["python", "-m", "pytest"],
                "test_canned.py",
                "def test_x(): pass",
                expected_outcome="passed",
                expected_in_summary=None,
                verbose=False,  # <-- the key: non-verbose mode
            )
        assert ok is False
        output = buf.getvalue()
        # The outcome mismatch must surface even without --verbose.
        assert "outcome was 'crashed'" in output
        # And the captured stderr appears, NOT gated behind verbose.
        assert "Unknown config option: timeout" in output
        # The PAPER-77 hint specifically calls out the pytest-timeout
        # remediation.
        assert "PAPER-77" in output or "pytest-timeout" in output

    def test_non_crashed_mismatch_surfaces_outcome_diff_only(
        self,
        tmp_path: Path,
        monkeypatch,
    ) -> None:
        # A "passed" expected but got "failed" mismatch — surface
        # the mismatch, but DON'T print stderr (only crashed/error
        # outcomes trigger the stderr dump; failed has its own
        # traceback in the record's detail).
        (tmp_path / "pyproject.toml").write_text(
            '[project]\nname="x"\nversion="0.1.0"\n',
            encoding="utf-8",
        )

        def _fake_run(self):
            _seed_supervisor_state(
                self.cfg.repo_root,
                {
                    "outcome": "failed",
                    "summary": "AssertionError: oops",
                    "captured_stderr": "some unrelated chatter\n",
                },
            )
            return 1

        from mgf.test_supervisor import supervisor as sup_module

        monkeypatch.setattr(sup_module.Supervisor, "run", _fake_run)
        monkeypatch.setattr(
            sup_module,
            "collect_tests",
            lambda _cmd, _root: [("tests/canned.py::test_x", ())],
        )

        buf = io.StringIO()
        with redirect_stdout(buf):
            ok = sc_module._run_canned(
                tmp_path,
                ["python", "-m", "pytest"],
                "test_canned.py",
                "def test_x(): pass",
                expected_outcome="passed",
                expected_in_summary=None,
                verbose=False,
            )
        assert ok is False
        output = buf.getvalue()
        # Mismatch surfaced.
        assert "outcome was 'failed'" in output
        # But stderr NOT dumped (only for crashed/error).
        assert "some unrelated chatter" not in output

    def test_no_jsonl_records_surfaces_clear_message(
        self,
        tmp_path: Path,
        monkeypatch,
    ) -> None:
        # Supervisor.run produces no JSONL → pytest probably failed
        # to start at all (the exact PAPER-77 silent-CRASHED shape).
        # Surface diagnostic ALWAYS — adopters won't know to use
        # --verbose on first contact.
        (tmp_path / "pyproject.toml").write_text(
            '[project]\nname="x"\nversion="0.1.0"\n',
            encoding="utf-8",
        )

        def _fake_run(self):
            _seed_supervisor_state(self.cfg.repo_root, None)  # empty jsonl
            return 1

        from mgf.test_supervisor import supervisor as sup_module

        monkeypatch.setattr(sup_module.Supervisor, "run", _fake_run)
        monkeypatch.setattr(
            sup_module,
            "collect_tests",
            lambda _cmd, _root: [("tests/canned.py::test_x", ())],
        )

        buf = io.StringIO()
        with redirect_stdout(buf):
            ok = sc_module._run_canned(
                tmp_path,
                ["python", "-m", "pytest"],
                "test_canned.py",
                "def test_x(): pass",
                expected_outcome="passed",
                expected_in_summary=None,
                verbose=False,
            )
        assert ok is False
        output = buf.getvalue()
        assert "no JSONL records written" in output
        assert "pytest may have failed before running" in output
