"""Outcome-vocabulary fidelity (M2-P1 §1.6) — the supervisor's full vocabulary,
pinned end-to-end.

The warden used to flatten ``crashed``/``hung`` → ``error`` and ``quarantined``
→ ``skipped`` at the runner seam, so the console could never tell a hang from an
assertion error. These tests pin the fixed contract:

* every outcome the *installed* supervisor can emit has an explicit mapping —
  a new upstream vocabulary word (e.g. a future supervisor release) reds this
  suite instead of silently rendering as ``error`` (feature-detect, no version
  pin);
* an outcome the build has no row for is reported **loudly** as ``unknown``
  (an ``error`` event names it), never silently as ``error``;
* the durable run-record projects onto the fedres 0.1 enum but preserves the
  verbatim outcome on its audit trail, and the console renders that fidelity
  as distinct chips (hung/crashed = error-class, quarantined = warn-class).
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import pytest
from mgf.fedres import TEST_STATUS_VALUES, validate_payload

from mgf.test_web.warden import RunRequest
from mgf.test_web.warden.outcomes import (
    DISPLAY_ORDER,
    OUTCOME_TO_RESULT,
    RESULT_TO_RECORD_STATUS,
    RESULT_VALUES,
    UNKNOWN_RESULT,
    record_status,
)
from mgf.test_web.warden.recorder import LiveRecorder
from mgf.test_web.warden.runner import _result_from_run

_APP_CSS = Path(__file__).parent.parent / "src" / "mgf" / "test_web" / "web" / "static" / "app.css"


# ------------------------------------------------- the upstream vocabulary pin --
@pytest.mark.contract
def test_every_installed_supervisor_outcome_has_an_explicit_mapping() -> None:
    """The 0.1.18-proofing pin: iterate the INSTALLED package's vocabulary.

    When a supervisor release extends ``OUTCOMES`` (e.g. a new record type),
    this test goes red until ``OUTCOME_TO_RESULT`` gains an explicit row —
    meanwhile runtime falls back loudly to ``unknown``, never silently to
    ``error``. Feature-detection by the installed enum, not a version pin.
    """
    from mgf.test_supervisor.records import OUTCOMES

    unmapped = set(OUTCOMES) - set(OUTCOME_TO_RESULT)
    assert not unmapped, (
        f"supervisor outcomes without an explicit warden rendering: {sorted(unmapped)} — "
        "add rows to mgf.test_web.warden.outcomes.OUTCOME_TO_RESULT"
    )


@pytest.mark.contract
def test_mgf_common_outcome_enum_agrees_with_the_mapping() -> None:
    """mgf-common's testing schema speaks the same vocabulary (when installed —
    mgf-common is the [standards] extra, not a runtime dep)."""
    testing = pytest.importorskip("mgf.common.testing")
    for member in testing.Outcome:
        assert member.value in OUTCOME_TO_RESULT, (
            f"mgf.common.testing.Outcome.{member.name} ({member.value!r}) has no explicit "
            "warden rendering"
        )


@pytest.mark.regression
def test_hung_crashed_quarantined_are_not_flattened() -> None:
    """Regression pin for the M2-P1 §1.6 fix (this commit): the runner used to
    collapse crashed/hung → error and quarantined → skipped."""
    assert OUTCOME_TO_RESULT["hung"] == "hung"
    assert OUTCOME_TO_RESULT["crashed"] == "crashed"
    assert OUTCOME_TO_RESULT["quarantined"] == "quarantined"
    # And the plain outcomes still normalize as before.
    assert OUTCOME_TO_RESULT["passed"] == "pass"
    assert OUTCOME_TO_RESULT["failed"] == "fail"
    assert OUTCOME_TO_RESULT["error"] == "error"
    assert OUTCOME_TO_RESULT["skipped"] == "skipped"


@pytest.mark.contract
def test_the_projection_is_total_and_lands_in_the_fedres_enum() -> None:
    # Every warden result has an explicit durable projection…
    assert set(RESULT_TO_RECORD_STATUS) == set(RESULT_VALUES)
    # …every projection is a value the run-record schema accepts…
    assert set(RESULT_TO_RECORD_STATUS.values()) <= set(TEST_STATUS_VALUES)
    # …fidelity is projected onto the nearest honest verdict…
    assert record_status("hung") == "error"
    assert record_status("crashed") == "error"
    assert record_status("quarantined") == "skipped"
    # …and anything unmapped is honest `unknown`, never a silent `error`.
    assert record_status("some-future-result") == UNKNOWN_RESULT
    # The console's chip/filter vocabulary covers exactly the outcome results.
    assert set(DISPLAY_ORDER) == set(OUTCOME_TO_RESULT.values()) | {UNKNOWN_RESULT}


# ------------------------------------------------------- the loud runner seam --
def _write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text("".join(json.dumps(r) + "\n" for r in rows), encoding="utf-8")


@pytest.mark.contract
def test_unmapped_outcome_falls_back_loudly_to_unknown(tmp_path: Path) -> None:
    run_jsonl = tmp_path / ".gates" / "last-run" / "run.jsonl"
    _write_jsonl(run_jsonl, [{"test_id": "t.py::a", "outcome": "exploded", "duration_ms": 1}])
    events: list[dict[str, Any]] = []
    result = _result_from_run(run_jsonl, "t.py::a", events.append)
    assert result == UNKNOWN_RESULT  # not a silent 'error'
    errors = [e for e in events if e.get("kind") == "error"]
    assert errors and "exploded" in errors[-1]["msg"]  # the fallback names the outcome


def test_outcomeless_rows_do_not_shadow_the_test_record(tmp_path: Path) -> None:
    """Feature-detect by shape: a future run.jsonl may interleave other record
    types (e.g. the 0.1.18 quarantine record) — only outcome-bearing rows are
    test records; a trailing outcome-less row must not eat the result."""
    run_jsonl = tmp_path / ".gates" / "last-run" / "run.jsonl"
    _write_jsonl(
        run_jsonl,
        [
            {"test_id": "t.py::a", "outcome": "hung", "duration_ms": 5},
            {"test_id": "t.py::a", "kind": "some_future_annotation"},  # no outcome
        ],
    )
    events: list[dict[str, Any]] = []
    assert _result_from_run(run_jsonl, "t.py::a", events.append) == "hung"


# ------------------------------------------- the durable record keeps fidelity --
def test_recorder_projects_but_preserves_the_verbatim_outcome(tmp_path: Path) -> None:
    rec = LiveRecorder(tmp_path, project="proj")
    record = rec.completion_record(RunRequest("run_h", "t.py::a"), None, "hung")
    assert record["result"] == "error"  # the fedres 0.1 enum clamp
    notes = [a for a in record["audit"] if a["event"] == "outcome"]
    assert [a["note"] for a in notes] == ["hung"]  # verbatim fidelity, schema-legal
    assert validate_payload(record) == []

    record = rec.completion_record(RunRequest("run_q", "t.py::a"), None, "quarantined")
    assert record["result"] == "skipped"
    assert [a["note"] for a in record["audit"] if a["event"] == "outcome"] == ["quarantined"]
    assert validate_payload(record) == []

    # A lossless result gains no outcome event (old records stay byte-shaped).
    record = rec.completion_record(RunRequest("run_p", "t.py::a"), None, "pass")
    assert record["result"] == "pass"
    assert [a["event"] for a in record["audit"]] == ["lease_acquired", "lease_released"]
    assert validate_payload(record) == []

    # A result outside the vocabulary is durable as honest `unknown` + verbatim note.
    record = rec.completion_record(RunRequest("run_u", "t.py::a"), None, "some-future-result")
    assert record["result"] == "unknown"
    assert [a["note"] for a in record["audit"] if a["event"] == "outcome"] == ["some-future-result"]
    assert validate_payload(record) == []


# ----------------------------------------------------- the console renders it --
def test_history_renders_distinct_chips_for_the_full_vocabulary(tmp_path: Path) -> None:
    from fastapi.testclient import TestClient

    from mgf.test_web.web import create_app

    rec = LiveRecorder(tmp_path, project=tmp_path.name)
    rec.on_complete(RunRequest("run_hung", "t.py::hung", origin="web"), None, "hung")
    rec.on_complete(RunRequest("run_crash", "t.py::crash", origin="web"), None, "crashed")
    rec.on_complete(RunRequest("run_quar", "t.py::quar", origin="web"), None, "quarantined")
    rec.on_complete(RunRequest("run_pass", "t.py::ok", origin="ci"), None, "pass")

    client = TestClient(create_app(roots=[tmp_path]))
    page = client.get(f"/p/{tmp_path.name}/history")
    assert page.status_code == 200
    # Distinct chips + labels — never a blurred generic `error`.
    assert "status-chip--hung" in page.text and ">hung</span>" in page.text
    assert "status-chip--crashed" in page.text and ">crashed</span>" in page.text
    assert "status-chip--quarantined" in page.text and ">quarantined</span>" in page.text
    assert "status-chip--pass" in page.text  # the fallback path (no audit note)

    # The filter pills speak the full vocabulary; `hung` narrows to the fact…
    hung_only = client.get(f"/p/{tmp_path.name}/history?result=hung")
    assert "run_hung" in hung_only.text and "run_crash" not in hung_only.text
    # …while `error` still finds the error-class runs via the clamped record result.
    errorish = client.get(f"/p/{tmp_path.name}/history?result=error")
    assert "run_hung" in errorish.text and "run_crash" in errorish.text
    assert "run_pass" not in errorish.text

    # The CI lens renders the same fidelity for ci-origin records.
    rec.on_complete(RunRequest("run_cih", "t.py::ci", origin="ci"), None, "hung")
    ci = client.get(f"/p/{tmp_path.name}/ci")
    assert ci.status_code == 200
    assert "status-chip--hung" in ci.text


def test_app_css_styles_every_fidelity_chip_with_brand_tokens_only() -> None:
    """The chip classes the console can now emit must exist in app.css (a class
    with no style renders as an unlabeled default), colored by brand semantic
    tokens — status colors only, no new hex literals."""
    css = _APP_CSS.read_text(encoding="utf-8")
    for chip in ("hung", "crashed", "quarantined"):
        assert f".status-chip--{chip}" in css, f"app.css lacks .status-chip--{chip}"
    block = css[css.index(".status-chip--hung") :]
    assert "#" not in block.replace("#fff", "").replace("#000", ""), (
        "new hex literals in the outcome-chip styles — use the --mgf-* / --color-* tokens"
    )
    for token in ("--mgf-error", "--mgf-warning", "--color-error-tint", "--color-warning-tint"):
        assert token in block
