"""ORCH-009 TC-21: pipeline invariants are untouched by the onboarding capability.

The onboarding kit/CLI lives entirely OUTSIDE the runtime (NFR-1): `src/**` is
byte-for-byte untouched. These tests pin that contract:

* a literal snapshot of ``STAGE_TRANSITIONS`` (the stage machine) and of the
  ``QG_CHECKS`` registry — any drift fails loudly;
* ``src/**`` never references the onboarding tree (no runtime coupling);
* the CLI's read-only imports from ``src`` stay within the CLOSED list of
  ADR-001 D4 (ORCH-009) — extending the list requires an ADR update;
* kit prompt templates name only real quality gates (no phantom ``check_*``).
"""
import ast
import os
import re

from src.qg.checks import QG_CHECKS
from src.stages import STAGE_TRANSITIONS

_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_SCRIPT_PATH = os.path.join(_REPO_ROOT, "scripts", "onboard_project.py")
_KIT_AGENTS = os.path.join(_REPO_ROOT, "onboarding", "repo-skeleton", ".openclaw", "agents")

# Literal snapshot of the stage machine (src/stages.py). Byte-exact NFR-1 pin:
# the onboarding work item must not move a single edge/agent/gate.
_EXPECTED_TRANSITIONS = {
    "created": {"next": "analysis", "agent": "analyst", "qg": None},
    "analysis": {"next": "architecture", "agent": "architect", "qg": "check_analysis_approved"},
    "architecture": {"next": "development", "agent": "developer", "qg": "check_architecture_done"},
    "development": {"next": "review", "agent": "reviewer", "qg": "check_ci_green"},
    "review": {"next": "testing", "agent": "tester", "qg": "check_reviewer_verdict"},
    "testing": {"next": "deploy-staging", "agent": "deployer", "qg": "check_tests_passed"},
    "deploy-staging": {"next": "deploy", "agent": "deployer", "qg": "check_staging_status"},
    "deploy": {"next": "done", "agent": None, "qg": "check_deploy_status"},
    "done": {"next": None, "agent": None, "qg": None},
    "cancelled": {"next": None, "agent": None, "qg": None},
}

# Snapshot of the QG registry KEYS (src/qg/checks.py::QG_CHECKS).
_EXPECTED_QG_KEYS = {
    "check_analysis_approved",
    "check_analysis_complete",
    "check_architecture_done",
    "check_ci_green",
    "check_review_approved",
    "check_tests_passed",
    "check_reviewer_verdict",
    "check_tests_local",
    "check_deploy_status",
    "check_staging_status",
    "check_branch_mergeable",
    "check_staging_image_fresh",
    "check_security_gate",
    "check_coverage_gate",
}

# Closed read-only import list of the onboarding CLI (ADR-001 D4 ORCH-009).
_ALLOWED_SRC_IMPORTS = {"src.config", "src.plane_sync", "src.projects"}


def test_tc21_stage_transitions_snapshot():
    assert STAGE_TRANSITIONS == _EXPECTED_TRANSITIONS, (
        "STAGE_TRANSITIONS drifted — ORCH-009 must not touch the stage machine (NFR-1)"
    )


def test_tc21_qg_checks_registry_snapshot():
    assert set(QG_CHECKS) == _EXPECTED_QG_KEYS, (
        "QG_CHECKS registry drifted — ORCH-009 must not touch the gates (NFR-1)"
    )


def test_tc21_src_never_references_onboarding():
    """No runtime coupling: src/** must not import/reference the onboarding tree."""
    offenders = []
    for root, _dirs, files in os.walk(os.path.join(_REPO_ROOT, "src")):
        for name in files:
            if not name.endswith(".py"):
                continue
            path = os.path.join(root, name)
            with open(path, encoding="utf-8") as f:
                if "onboard" in f.read().lower():
                    offenders.append(os.path.relpath(path, _REPO_ROOT))
    assert not offenders, f"src/** references onboarding: {offenders}"


def test_tc21_cli_src_imports_stay_in_closed_list():
    """ADR-001 D4: the CLI may import ONLY src.config / src.plane_sync / src.projects."""
    with open(_SCRIPT_PATH, encoding="utf-8") as f:
        tree = ast.parse(f.read())
    found = set()
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom) and node.module and node.module.startswith("src"):
            found.add(node.module)
        elif isinstance(node, ast.Import):
            for alias in node.names:
                if alias.name.startswith("src"):
                    found.add(alias.name)
    assert found, "the CLI must use the closed src imports (round-trip via real parser)"
    assert found <= _ALLOWED_SRC_IMPORTS, (
        f"onboard_project.py imports outside the closed ADR D4 list: "
        f"{sorted(found - _ALLOWED_SRC_IMPORTS)} — extend ONLY via an ADR update"
    )


def test_tc21_kit_prompts_name_only_real_gates():
    """A kit prompt naming a phantom gate would mislead every onboarded project."""
    pattern = re.compile(r"check_[a-z_]+")
    for name in sorted(os.listdir(_KIT_AGENTS)):
        path = os.path.join(_KIT_AGENTS, name)
        with open(path, encoding="utf-8") as f:
            text = f.read()
        for gate in sorted(set(pattern.findall(text))):
            assert gate in QG_CHECKS, (
                f"kit prompt {name} references gate {gate!r} absent from QG_CHECKS"
            )
