"""Regression: class-based tests must reconcile from JUnit XML.

Pytest's JUnit output omits the ``file`` attribute by default and reports
class-based tests with a dotted ``classname`` (``tests.test_x.TestY``) +
a method ``name``. The old reconstruction turned that into
``tests/test_x/TestY.py::...`` — which never matched the collected
node-id, so every class-based test was flagged ``MISSING_FROM_XML`` and
silently auto-quarantined (a passing suite reported as not-run). This pins
the ``(classname, name)`` keying that fixes it.

Discovered during the federation test-hardening pass (mgf-auth's
class-based suite was being quarantined wholesale while pytest passed).
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from mgf.test_supervisor.supervisor import (
    RunConfig,
    Supervisor,
    _node_id_to_classkey,
    _split_nodeid,
)

if TYPE_CHECKING:
    from pathlib import Path

# A JUnit document shaped exactly like pytest's: NO `file` attribute, a
# dotted classname for the class-based case, and a bare module classname
# for the module-level case.
_JUNIT_XML = """<?xml version="1.0" encoding="utf-8"?>
<testsuites><testsuite name="pytest" tests="3">
  <testcase classname="tests.test_clerk.TestVerifySession" name="test_empty_token_raises" time="0.01" />
  <testcase classname="tests.test_clerk.TestVerifySession" name="test_expired[case0]" time="0.02" />
  <testcase classname="tests.test_smoke" name="test_imports" time="0.00" />
</testsuite></testsuites>
"""


class TestNodeIdToClassKey:
    def test_class_based(self) -> None:
        assert _node_id_to_classkey(
            "tests/test_clerk.py::TestVerifySession::test_empty_token_raises"
        ) == ("tests.test_clerk.TestVerifySession", "test_empty_token_raises")

    def test_module_level(self) -> None:
        assert _node_id_to_classkey("tests/test_smoke.py::test_imports") == (
            "tests.test_smoke",
            "test_imports",
        )

    def test_parametrised_suffix_stays_in_name(self) -> None:
        assert _node_id_to_classkey(
            "tests/test_clerk.py::TestVerifySession::test_expired[case0]"
        ) == ("tests.test_clerk.TestVerifySession", "test_expired[case0]")

    def test_nested_classes_join_with_dot(self) -> None:
        assert _node_id_to_classkey("tests/test_x.py::TestOuter::TestInner::test_y") == (
            "tests.test_x.TestOuter.TestInner",
            "test_y",
        )

    def test_ipv6_param_module_level(self) -> None:
        # The `::` inside the param must NOT be treated as a node-id
        # separator. Naive split("::") produced ("tests.test_net.test_v6[",
        # "1]") here and false-reded the gate.
        assert _node_id_to_classkey("tests/test_net.py::test_v6[::1]") == (
            "tests.test_net",
            "test_v6[::1]",
        )

    def test_ipv6_param_class_based(self) -> None:
        assert _node_id_to_classkey(
            "tests/test_net.py::TestConn::test_v6[2001:db8::1]"
        ) == ("tests.test_net.TestConn", "test_v6[2001:db8::1]")

    def test_ipv6_param_with_zone_and_brackets(self) -> None:
        # Full IPv6 literal with a zone id, wrapped as pytest ids often are.
        assert _node_id_to_classkey(
            "tests/test_net.py::test_bind[[fe80::1%eth0]:8080]"
        ) == ("tests.test_net", "test_bind[[fe80::1%eth0]:8080]")


class TestSplitNodeId:
    def test_plain_separators(self) -> None:
        assert _split_nodeid("a/b.py::C::test_d") == ["a/b.py", "C", "test_d"]

    def test_double_colon_inside_param_is_preserved(self) -> None:
        assert _split_nodeid("a/b.py::test_v6[::1]") == ["a/b.py", "test_v6[::1]"]

    def test_nested_brackets_balance(self) -> None:
        assert _split_nodeid("a/b.py::t[[x::y]::z]") == ["a/b.py", "t[[x::y]::z]"]


class TestJunitReconciliation:
    def test_class_based_tests_reconcile(self, tmp_path: Path) -> None:
        xml = tmp_path / "junit.xml"
        xml.write_text(_JUNIT_XML, encoding="utf-8")
        cfg = RunConfig(
            repo_root=tmp_path,
            gates_dir=tmp_path / ".gates",
            test_ids=[],
            test_markers={},
            run_id="test",
            pytest_cmd=["python", "-m", "pytest"],
        )
        parsed = Supervisor(cfg)._parse_junit(xml)

        # Each collected node-id must resolve to its testcase — the exact
        # failure mode of the bug was these lookups returning None.
        for node_id in (
            "tests/test_clerk.py::TestVerifySession::test_empty_token_raises",
            "tests/test_clerk.py::TestVerifySession::test_expired[case0]",
            "tests/test_smoke.py::test_imports",
        ):
            assert parsed.get(_node_id_to_classkey(node_id)) is not None, node_id
            assert parsed[_node_id_to_classkey(node_id)].outcome == "passed"
