"""ORCH-075 (ORCH-52b) — структурные проверки стандарта документов конвейера.

Docs-only задача: проверяется НАЛИЧИЕ и СТРУКТУРА новых файлов-стандартов/шаблонов
(`docs/_standards/PIPELINE_DOCS.md`, `docs/_templates/*`) и обновление точек-ссылок
(`CLAUDE.md`, `docs/architecture/README.md`, `CHANGELOG.md`). Тесты НЕ меняют
`QG_CHECKS`/`STAGE_TRANSITIONS` и не вводят новый гейт (это ORCH-52c).

Покрытие тест-плана 04-test-plan.yaml: TC-01…TC-20 (TC-21 — полный регресс `pytest tests/`).
"""

from pathlib import Path

import yaml

REPO_ROOT = Path(__file__).resolve().parents[1]
STANDARDS = REPO_ROOT / "docs" / "_standards"
TEMPLATES = REPO_ROOT / "docs" / "_templates"
MANIFEST = STANDARDS / "PIPELINE_DOCS.md"

# Все номерные доки реального набора (TRZ §FR-1, AC-1).
NUMBERED_DOCS = ["00", "01", "02", "03", "04", "06", "07", "08", "10",
                 "12", "13", "14", "15", "16", "17"]

# Шаблоны required/when-applicable доков (TRZ §2, AC-2).
TEMPLATE_FILES = [
    "00-business-request.md",
    "01-brd.md",
    "02-trz.md",
    "03-acceptance-criteria.md",
    "04-test-plan.yaml",
    "06-adr-ADR-NNN-slug.md",
    "07-infra-requirements.md",
    "08-data-requirements.md",
    "10-tech-risks.md",
    "12-review.md",
    "13-test-report.md",
    "14-deploy-log.md",
    "15-staging-log.md",
    "16-post-deploy-log.md",
    "17-security-report.md",
]


def _read(path: Path) -> str:
    return path.read_text(encoding="utf-8")


# --- TC-01 -----------------------------------------------------------------
def test_tc01_manifest_exists_and_nonempty():
    """docs/_standards/PIPELINE_DOCS.md существует и непустой."""
    assert MANIFEST.is_file(), f"манифест не найден: {MANIFEST}"
    assert len(_read(MANIFEST).strip()) > 0, "манифест пустой"


# --- TC-02 -----------------------------------------------------------------
def test_tc02_manifest_mentions_all_numbered_docs():
    """Манифест упоминает все номерные доки набора."""
    content = _read(MANIFEST)
    missing = []
    for num in NUMBERED_DOCS:
        # Документ упоминается как `NN-...` или `NN-adr` — ищем по префиксу `NN-`.
        if f"{num}-" not in content:
            missing.append(num)
    assert not missing, f"в манифесте не упомянуты доки: {missing}"


# --- TC-03 -----------------------------------------------------------------
def test_tc03_manifest_lists_owner_agents():
    """Манифест указывает владельцев-агентов конвейера (TC-03: analyst/architect/
    reviewer/tester/deployer/система). `developer` не владеет номерным доком — пишет код+PR."""
    content = _read(MANIFEST).lower()
    for agent in ["analyst", "architect", "reviewer", "tester", "deployer"]:
        assert agent in content, f"в манифесте нет владельца-агента: {agent}"
    assert "систем" in content, "в манифесте нет системного владельца (Plane webhook)"


# --- TC-04 -----------------------------------------------------------------
def test_tc04_manifest_has_categories():
    """Манифест содержит категории required / when-applicable / optional."""
    content = _read(MANIFEST)
    for category in ["required", "when-applicable", "optional"]:
        assert category in content, f"в манифесте нет категории: {category}"


# --- TC-05 -----------------------------------------------------------------
def test_tc05_templates_dir_has_all_templates():
    """docs/_templates/ существует и содержит шаблоны для всех required/when-applicable доков."""
    assert TEMPLATES.is_dir(), f"каталог шаблонов не найден: {TEMPLATES}"
    missing = [name for name in TEMPLATE_FILES if not (TEMPLATES / name).is_file()]
    assert not missing, f"отсутствуют шаблоны: {missing}"


# --- TC-06..TC-11 — frontmatter machine-keys -------------------------------
def _assert_frontmatter_key(template_name: str, key: str):
    content = _read(TEMPLATES / template_name)
    assert content.lstrip().startswith("---"), f"{template_name}: нет YAML-frontmatter"
    head = content.split("---", 2)
    assert len(head) >= 3, f"{template_name}: frontmatter не закрыт"
    assert f"{key}:" in head[1], f"{template_name}: нет machine-key `{key}:` во frontmatter"


def test_tc06_review_template_has_verdict():
    """Шаблон 12-review содержит frontmatter-ключ verdict:."""
    _assert_frontmatter_key("12-review.md", "verdict")


def test_tc07_test_report_template_has_result():
    """Шаблон 13-test-report содержит frontmatter-ключ result:."""
    _assert_frontmatter_key("13-test-report.md", "result")


def test_tc08_deploy_log_template_has_deploy_status():
    """Шаблон 14-deploy-log содержит frontmatter-ключ deploy_status:."""
    _assert_frontmatter_key("14-deploy-log.md", "deploy_status")


def test_tc09_staging_log_template_has_staging_status():
    """Шаблон 15-staging-log содержит frontmatter-ключ staging_status:."""
    _assert_frontmatter_key("15-staging-log.md", "staging_status")


def test_tc10_security_report_template_has_security_status():
    """Шаблон 17-security-report содержит frontmatter-ключ security_status:."""
    _assert_frontmatter_key("17-security-report.md", "security_status")


def test_tc11_post_deploy_template_has_post_deploy_status():
    """Шаблон 16-post-deploy-log содержит frontmatter-ключ post_deploy_status:."""
    _assert_frontmatter_key("16-post-deploy-log.md", "post_deploy_status")


# --- TC-12 -----------------------------------------------------------------
def test_tc12_brd_template_has_mandatory_sections():
    """Шаблон 01-brd содержит обязательные секции (TRZ §FR-2.1)."""
    content = _read(TEMPLATES / "01-brd.md")
    for section in ["Бизнес-контекст", "Объём", "Бизнес-требования", "Нефункциональные требования"]:
        assert section in content, f"01-brd: нет секции `{section}`"


# --- TC-13 -----------------------------------------------------------------
def test_tc13_trz_template_has_mandatory_sections():
    """Шаблон 02-trz содержит обязательные секции (TRZ §FR-2.1)."""
    content = _read(TEMPLATES / "02-trz.md")
    for section in ["Задействованные модули", "Изменения API", "Изменения схемы БД",
                    "QG checks"]:
        assert section in content, f"02-trz: нет секции `{section}`"


# --- TC-14 -----------------------------------------------------------------
def test_tc14_ac_template_has_pass_fail_block():
    """Шаблон 03-acceptance-criteria содержит блок AC-N с метками PASS и FAIL."""
    content = _read(TEMPLATES / "03-acceptance-criteria.md")
    assert "AC-1" in content, "03-ac: нет блока AC-N"
    assert "**PASS:**" in content, "03-ac: нет метки PASS"
    assert "**FAIL:**" in content, "03-ac: нет метки FAIL"


# --- TC-15 -----------------------------------------------------------------
def test_tc15_test_plan_template_is_valid_yaml():
    """Шаблон 04-test-plan.yaml — валидный YAML с work_item и списком tests."""
    data = yaml.safe_load(_read(TEMPLATES / "04-test-plan.yaml"))
    assert isinstance(data, dict), "04-test-plan: корень не словарь"
    assert "work_item" in data, "04-test-plan: нет ключа work_item"
    assert isinstance(data.get("tests"), list) and data["tests"], "04-test-plan: tests не список"
    first = data["tests"][0]
    for key in ["id", "type", "description", "module", "expected"]:
        assert key in first, f"04-test-plan: в элементе tests нет ключа `{key}`"


# --- TC-16 -----------------------------------------------------------------
def test_tc16_adr_naming_section_present():
    """Раздел ADR-naming фиксирует формат ADR-NNN-<slug>.md с нумерацией с 001 и kebab-slug."""
    content = _read(MANIFEST)
    assert "ADR-NNN-" in content, "нет формата ADR-NNN-<slug>.md"
    assert "06-adr" in content, "нет пути 06-adr/"
    assert "001" in content, "не зафиксирована нумерация с 001"
    assert "kebab" in content.lower(), "нет правила kebab-case для slug"


# --- TC-17 -----------------------------------------------------------------
def test_tc17_adr_naming_matches_real_repo():
    """ADR-naming совпадает с реальными ADR в репо (пример из манифеста существует)."""
    # Манифест приводит ORCH-088 как пример; реальный файл должен существовать.
    adr_dir = REPO_ROOT / "docs" / "work-items" / "ORCH-088" / "06-adr"
    real = list(adr_dir.glob("ADR-001-*.md"))
    assert real, f"реальный ADR-001-*.md не найден в {adr_dir}"
    # И глобальный реестр следует 4-значной конвенции.
    global_adr = REPO_ROOT / "docs" / "architecture" / "adr"
    assert list(global_adr.glob("adr-0019-*.md")), "глобальный adr-0019-*.md не найден"


# --- TC-18 -----------------------------------------------------------------
def test_tc18_claude_md_links_standard():
    """CLAUDE.md содержит ссылку на docs/_standards/PIPELINE_DOCS.md."""
    content = _read(REPO_ROOT / "CLAUDE.md")
    assert "docs/_standards/PIPELINE_DOCS.md" in content


# --- TC-19 -----------------------------------------------------------------
def test_tc19_architecture_readme_links_standard():
    """docs/architecture/README.md содержит ссылку на стандарт документов."""
    content = _read(REPO_ROOT / "docs" / "architecture" / "README.md")
    assert "PIPELINE_DOCS.md" in content


# --- TC-20 -----------------------------------------------------------------
def test_tc20_changelog_mentions_task():
    """CHANGELOG.md содержит запись об ORCH-52b/ORCH-075 в разделе Unreleased."""
    content = _read(REPO_ROOT / "CHANGELOG.md")
    unreleased = content.split("## [Unreleased]", 1)
    assert len(unreleased) == 2, "нет раздела ## [Unreleased]"
    # Берём срез до следующего релизного заголовка, если он есть.
    body = unreleased[1].split("\n## ", 1)[0]
    assert "ORCH-075" in body or "ORCH-52b" in body, "в Unreleased нет записи ORCH-075/ORCH-52b"
