"""Documentation acceptance for the web console architecture ADR (#632 / epic #631).

Enforces the acceptance criteria of issue #632:

* AC1 — the ADR exists and covers layers, authority, phases, API versioning,
  and a page map.
* AC2 — every #631 child (#632–#651) maps to at least one architectural
  component.
* AC3 — the closed MVP (#425–#436) is stated as foundation, not recreated.
* AC4 — forbidden paths are explicit: raw provider incidents as work,
  browser-held tokens, process-kill recovery.
* AC5 — a controller can approve the document without reading chat history.

Plus the linkage requirement: ``docs/webui-local-dev.md`` cross-links the ADR.
"""
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent
ADR = (
    REPO_ROOT
    / "docs"
    / "architecture"
    / "webui-control-plane-console-architecture-adr.md"
)
ADR_BASENAME = "webui-control-plane-console-architecture-adr.md"
LOCAL_DEV = REPO_ROOT / "docs" / "webui-local-dev.md"

# Epic #631 children, phases 1-4 (twenty capability areas).
EPIC_CHILDREN = tuple(f"#{number}" for number in range(632, 652))


def _read(path: Path) -> str:
    assert path.is_file(), f"missing {path.relative_to(REPO_ROOT)}"
    return path.read_text(encoding="utf-8")


def test_ac1_adr_exists_with_required_sections():
    text = _read(ADR)
    lower = text.lower()
    assert text.lstrip().startswith("#"), "ADR lacks a title"
    assert "#631" in text and "#632" in text
    for heading in (
        "## 2. Decision summary",
        "## 4. Authority boundaries",
        "## 5. Request flow and the redaction boundary",
        "## 6. API naming and versioning",
        "## 7. Page map",
        "## 8. Component ownership",
        "## 9. Phase gates",
        "## 11. Forbidden paths",
    ):
        assert heading in text, f"ADR must contain section {heading!r}"
    assert "browser ui" in lower and "domain loader" in lower
    assert "control-plane db" in lower and "capability gate" in lower


def test_ac1_api_versioning_is_decided_including_legacy_routes():
    text = _read(ADR)
    assert "/api/v1/" in text, "ADR must decide the versioned API prefix"
    assert "/api/v2/" in text, "ADR must state how breaking changes are handled"
    lower = text.lower()
    assert "compatibility alias" in lower, (
        "ADR must say what happens to the existing unversioned MVP exports"
    )


def test_ac1_page_map_covers_mvp_routes():
    text = _read(ADR)
    for route in ("`/`", "`/health`", "`/projects`", "`/prompts`", "`/runtime`",
                  "`/audit`", "`/actions`"):
        assert route in text, f"page map must account for MVP route {route}"


def test_ac2_every_epic_child_maps_to_a_component():
    text = _read(ADR)
    ownership = text.split("## 8. Component ownership", 1)[-1].split("## 9.", 1)[0]
    missing = [child for child in EPIC_CHILDREN if child not in ownership]
    assert not missing, (
        f"epic #631 children without an architectural component: {missing}"
    )


def test_ac2_every_child_row_declares_a_phase():
    text = _read(ADR)
    ownership = text.split("## 8. Component ownership", 1)[-1].split("## 9.", 1)[0]
    for child in EPIC_CHILDREN:
        row = next(
            (line for line in ownership.splitlines() if line.startswith(f"| {child} ")),
            None,
        )
        assert row is not None, f"no ownership row for {child}"
        assert row.rstrip().endswith(("| 1 |", "| 2 |", "| 3 |", "| 4 |")), (
            f"ownership row for {child} must end with its phase: {row!r}"
        )


def test_ac3_mvp_is_foundation_not_recreated():
    text = _read(ADR)
    assert "#425" in text and "#436" in text
    lower = text.lower()
    assert "do not recreate" in lower or "recreating mvp scope" in lower
    assert "retained and evolved" in lower


def test_ac4_forbidden_paths_are_explicit():
    text = _read(ADR)
    forbidden = text.split("## 11. Forbidden paths", 1)[-1].split("## 12.", 1)[0]
    lower = forbidden.lower()
    assert "raw provider incidents" in lower and "#612" in forbidden
    assert "browser-held tokens" in lower
    assert "process-kill recovery" in lower and "#630" in forbidden
    assert "ungated browser mutations" in lower


def test_ac5_approval_checklist_is_self_contained():
    text = _read(ADR)
    assert "## 12. Approval checklist" in text
    checklist = text.split("## 12. Approval checklist", 1)[-1].split("## 13.", 1)[0]
    for marker in ("1.", "2.", "3.", "4.", "5.", "6."):
        assert marker in checklist, f"approval checklist missing item {marker}"


def test_adr_states_the_two_boundary_invariants():
    text = _read(ADR)
    lower = text.lower()
    assert "no secrets to the browser" in lower
    assert "no ungated mutations" in lower


def test_open_questions_are_recorded_not_implied():
    text = _read(ADR)
    assert "## 13. Open questions and follow-ups" in text
    section = text.split("## 13. Open questions and follow-ups", 1)[-1]
    assert "#633" in section, "deferred authorization work must name its issue"


def test_local_dev_doc_cross_links_the_adr():
    text = _read(LOCAL_DEV)
    assert ADR_BASENAME in text, (
        "docs/webui-local-dev.md must cross-link the console architecture ADR "
        "(issue #632 scope)"
    )


def test_docs_do_not_embed_secrets():
    for path in (ADR, LOCAL_DEV):
        text = _read(path)
        for marker in ("ghp_", "BEGIN PRIVATE KEY", "Authorization: Bearer"):
            assert marker not in text, f"{path.name} contains {marker!r}"
