"""Contract tests pinning `mgf.test_supervisor` ↔ `mgf.common.testing` alignment.

Per TS-23 the supervisor (`mgf.test_supervisor`, published as the
sibling library `mgf-test-supervisor`) MUST be stdlib-only — so it
cannot import from `mgf.common.testing` at runtime (mgf-common is
the project under test; the supervisor must run even when
mgf-common is broken). The two surfaces therefore carry parallel
copies of the schema:

  - `mgf.common.testing.TestRecord` / `SCHEMA_VERSION` /
    `MARKER_TIER` — the importable library shape consumers use
    to read `run.jsonl`.
  - `mgf.test_supervisor.records.TestRecord` / `SCHEMA_VERSION`
    and `mgf.test_supervisor.tiers.MARKER_TIER` — the
    supervisor's local stdlib-only copies.

These tests assert the two stay aligned. If a field is renamed in
one place without the other, this file's tests fail and the
mismatch surfaces immediately — exactly where it can be fixed in
one edit. The federation contract is "the wire formats match"; the
implementation is "the field sets match."

(The alternative — having the supervisor import from
`mgf.common.testing` — would violate TS-23: a broken mgf-common
runtime would break the test runner that's trying to diagnose
why mgf-common is broken. The duplication is the lesser cost; the
contract test is the alignment guarantee.)

Defense in depth: the SAME alignment contract also runs from the
mgf-test-supervisor side at
`mgf-test-supervisor/tests/unit/test_alignment_with_mgf_common.py`.
Field renames need to land in both copies in the same commit; the
two-sided enforcement catches the case where one repo's CI hasn't
run yet.

Pre-2026-05-26 versions of this file imported from `scripts._mtest`
(the in-tree copy). The supervisor was extracted to the published
sibling `mgf-test-supervisor` v0.1.0 on 2026-05-26; this file was
updated to import from `mgf.test_supervisor` in the same migration.
"""

from __future__ import annotations

from mgf.test_supervisor import records as supervisor_records
from mgf.test_supervisor import tiers as supervisor_tiers

from mgf.common import testing as public


class TestSchemaVersionAligned:
    def test_schema_versions_match(self) -> None:
        # If you bumped one, bump the other in the same commit.
        assert public.SCHEMA_VERSION == supervisor_records.SCHEMA_VERSION, (
            f"Schema-version drift: public {public.SCHEMA_VERSION!r} "
            f"vs supervisor {supervisor_records.SCHEMA_VERSION!r}. "
            f"Update both in the same commit per AP-04 cycle."
        )


class TestOutcomesAligned:
    def test_supervisor_outcomes_match_public_enum(self) -> None:
        public_outcomes = {o.value for o in public.Outcome}
        supervisor_outcomes = set(supervisor_records.OUTCOMES)
        assert public_outcomes == supervisor_outcomes, (
            f"Outcome-set drift: public {sorted(public_outcomes)} vs "
            f"supervisor {sorted(supervisor_outcomes)}. Add/remove in both."
        )


class TestSeveritiesAligned:
    def test_supervisor_severities_match_public_enum(self) -> None:
        public_sevs = {s.value for s in public.Severity}
        supervisor_sevs = set(supervisor_records.SEVERITIES)
        assert public_sevs == supervisor_sevs, (
            f"Severity-set drift: public {sorted(public_sevs)} vs "
            f"supervisor {sorted(supervisor_sevs)}. Add/remove in both."
        )


class TestMarkerTierAligned:
    def test_marker_tier_mappings_match(self) -> None:
        assert public.MARKER_TIER == supervisor_tiers.MARKER_TIER, (
            f"MARKER_TIER drift between public + supervisor copies:\n"
            f"  public:     {dict(public.MARKER_TIER)}\n"
            f"  supervisor: {dict(supervisor_tiers.MARKER_TIER)}\n"
            f"Update both in the same commit so consumers reading "
            f"run.jsonl resolve tiers consistently with how the "
            f"supervisor selected the tests."
        )

    def test_default_tier_matches(self) -> None:
        assert public.DEFAULT_TIER == supervisor_tiers.DEFAULT_TIER


class TestTierBudgetsAligned:
    def test_tier_count(self) -> None:
        assert len(public.TIERS) == len(supervisor_tiers.TIERS) == 4

    def test_per_test_budgets_match(self) -> None:
        public_budgets = [t.per_test_seconds for t in public.TIERS]
        # Supervisor uses NamedTuple; field name differs but value
        # MUST match.
        supervisor_budgets = [t.typical_test_s for t in supervisor_tiers.TIERS]
        assert public_budgets == supervisor_budgets

    def test_suite_budgets_match(self) -> None:
        public_budgets = [t.suite_seconds for t in public.TIERS]
        supervisor_budgets = [t.total_budget_s for t in supervisor_tiers.TIERS]
        assert public_budgets == supervisor_budgets

    def test_tier_names_match(self) -> None:
        public_names = [t.name for t in public.TIERS]
        supervisor_names = [t.name for t in supervisor_tiers.TIERS]
        assert public_names == supervisor_names


class TestRecordFieldsAligned:
    """The DATACLASS shapes differ deliberately (public is frozen +
    no slots for legibility; supervisor is frozen + slots for
    write-path performance). The FIELD SETS MUST match so JSONL
    written by the supervisor parses cleanly via the public reader.
    """

    def test_field_names_match(self) -> None:
        import dataclasses

        public_fields = {f.name for f in dataclasses.fields(public.TestRecord)}
        supervisor_fields = {f.name for f in dataclasses.fields(supervisor_records.TestRecord)}
        assert public_fields == supervisor_fields, (
            f"TestRecord field-set drift:\n"
            f"  public-only:     {public_fields - supervisor_fields}\n"
            f"  supervisor-only: {supervisor_fields - public_fields}"
        )

    def test_jsonl_round_trip_through_public_reader(self) -> None:
        # Build a supervisor-shape record, serialise, parse via the
        # public reader — this proves the wire formats interop.
        sup_rec = supervisor_records.TestRecord(
            test_id="tests/unit/foo.py::test_bar",
            outcome="passed",
            severity="OK",
            duration_ms=12,
            tier=1,
            markers=("contract",),
            run_id="run-id",
            ts_iso="2026-05-26T01:11:37.000Z",
        )
        line = sup_rec.to_json()
        public_rec = public.TestRecord.from_jsonl_line(line)
        assert public_rec.test_id == sup_rec.test_id
        assert public_rec.outcome is public.Outcome.PASSED
        assert public_rec.tier == sup_rec.tier
        assert public_rec.markers == list(sup_rec.markers)

    def test_perf_field_round_trips(self) -> None:
        # TS-28: a supervisor record carrying perf attribution must
        # survive serialise → public-reader parse with the perf payload
        # intact (and absence → None).
        perf_payload = {
            "schema_version": "1.0.0",
            "test_id": "tests/unit/foo.py::test_bar",
            "total_ms": 12.5,
            "functions": [{"func": "a.py:1(f)", "ncalls": 2, "tottime_ms": 3.0, "cumtime_ms": 4.0}],
            "spans": [{"label": "load", "ms": 5.0, "depth": 0}],
        }
        sup_rec = supervisor_records.TestRecord(
            test_id="tests/unit/foo.py::test_bar",
            outcome="passed",
            severity="OK",
            duration_ms=12,
            tier=1,
            run_id="run-id",
            ts_iso="2026-05-29T00:00:00.000Z",
            perf=perf_payload,
        )
        public_rec = public.TestRecord.from_jsonl_line(sup_rec.to_json())
        assert public_rec.perf == perf_payload
        # Absent perf → None on both sides.
        sup_none = supervisor_records.TestRecord(
            test_id="t::t",
            outcome="passed",
            severity="OK",
            duration_ms=0,
            tier=1,
            run_id="r",
            ts_iso="t",
        )
        assert public.TestRecord.from_jsonl_line(sup_none.to_json()).perf is None
