#!/usr/bin/env python3
"""四个 Master 的跨平台安装后运行契约静态回归测试。"""

from __future__ import annotations

import re
import unittest
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
MASTER_NAMES = ("prd-master", "design-master", "tdd-master", "git-master")


def read_skill(name: str) -> str:
    return (ROOT / name / "SKILL.md").read_text(encoding="utf-8")


SKILLS = {name: read_skill(name) for name in MASTER_NAMES}


class RuntimePathContractTests(unittest.TestCase):
    def test_validator_skills_resolve_bundled_files_from_skill_root(self) -> None:
        for name in ("prd-master", "design-master", "tdd-master"):
            with self.subTest(skill=name):
                text = SKILLS[name]
                self.assertIn("SKILL_ROOT", text)
                self.assertIn('"<SKILL_ROOT>/validators/', text)
                self.assertIn("不要假设用户工作区", text)

    def test_validator_commands_do_not_depend_on_source_checkout_or_bare_py(self) -> None:
        combined = "\n".join(
            path.read_text(encoding="utf-8")
            for name in MASTER_NAMES
            for path in (ROOT / name).rglob("*.md")
        )
        for command in (
            "python3 prd-master/validators/",
            "py prd-master/validators/",
            "py design-master/validators/",
            "py tdd-master/validators/",
        ):
            with self.subTest(command=command):
                self.assertNotIn(command, combined)

    def test_tdd_validator_commands_use_the_selected_python_launcher(self) -> None:
        skill = SKILLS["tdd-master"]
        specification = (ROOT / "tdd-master/references/TDD验收契约规范.md").read_text(
            encoding="utf-8"
        )
        self.assertIn(
            '<PYTHON3> "<SKILL_ROOT>/validators/check_delivery_contract.py" "{项目名}/output"',
            skill,
        )
        self.assertIn(
            '<PYTHON3> "<SKILL_ROOT>/validators/check_delivery_contract.py" --result',
            specification,
        )


class InteractionContractTests(unittest.TestCase):
    def test_question_tool_is_product_neutral_and_has_plain_text_fallback(self) -> None:
        for name, text in SKILLS.items():
            with self.subTest(skill=name):
                self.assertIn("当前环境的结构化提问能力", text)
                self.assertIn("工具不可用时", text)

    def test_choice_contract_fits_three_option_tools(self) -> None:
        for name in ("design-master", "tdd-master", "git-master"):
            with self.subTest(skill=name):
                self.assertNotIn("2-4 个", SKILLS[name])
        self.assertNotIn("multiSelect", SKILLS["tdd-master"])


class ConcurrencyContractTests(unittest.TestCase):
    def test_content_skills_have_bounded_concurrency_fallback(self) -> None:
        for name in ("prd-master", "design-master", "tdd-master"):
            with self.subTest(skill=name):
                text = SKILLS[name]
                self.assertIn("最大并发", text)
                self.assertIn("没有子 Agent", text)
                self.assertRegex(text, r"(?:不得省略|不能因为降级而删掉)")

    def test_page_and_test_unit_guides_do_not_forbid_capability_fallback(self) -> None:
        runtime_guides = "\n".join(
            path.read_text(encoding="utf-8")
            for path in (
                ROOT / "prd-master/references/PPT-HTML开发师.md",
                ROOT / "design-master/references/页面文档撰写师.md",
                ROOT / "design-master/references/页面HTML开发师.md",
                ROOT / "tdd-master/SKILL.md",
            )
        )
        for phrase in ("必须并发调用子代理", "必须并发子代理", "均视为违规"):
            with self.subTest(phrase=phrase):
                self.assertNotIn(phrase, runtime_guides)


class DesignScopeContractTests(unittest.TestCase):
    def test_design_maps_all_stages_but_only_deep_designs_mvp(self) -> None:
        text = SKILLS["design-master"]
        self.assertIn("全产品定骨架，MVP 出施工图", text)
        self.assertIn("页面清单覆盖所有阶段", text)
        self.assertIn("详细页面文档、高保真 HTML 和可执行设计默认只覆盖 MVP", text)
        self.assertNotIn("默认全跑所有页面", text)

    def test_future_pages_do_not_generate_placeholder_artifacts(self) -> None:
        page_writer = (ROOT / "design-master/references/页面文档撰写师.md").read_text(encoding="utf-8")
        html_writer = (ROOT / "design-master/references/页面HTML开发师.md").read_text(encoding="utf-8")
        self.assertIn("后续阶段行本轮不运行", page_writer)
        self.assertIn("不得为后续 `planned` 页面创建空壳 HTML", html_writer)

    def test_design_returns_missing_prerequisites_or_external_configuration_to_prd(self) -> None:
        text = SKILLS["design-master"]
        for phrase in (
            "前置能力与外部配置回退门禁",
            "`Capability prerequisites`",
            "立即退回 prd-master",
            "责任表仍写 `none`",
            "禁止在设计阶段假设资源已经具备、改选账号密码、环境变量或管理后台",
            "`onboarding/settings/admin` 必须有关联 REQ",
            "`deployment-secret` 只进入技术方案，不生成配置页",
            "不能在设计阶段补产品范围",
        ):
            self.assertIn(phrase, text)

    def test_page_template_does_not_invent_authentication_methods(self) -> None:
        template = (ROOT / "design-master/references/页面模板.md").read_text(encoding="utf-8")
        page_list = (ROOT / "design-master/references/页面清单规划师.md").read_text(encoding="utf-8")
        page_writer = (ROOT / "design-master/references/页面文档撰写师.md").read_text(encoding="utf-8")
        for phrase in (
            "需求防增生铁律",
            "没有对应 REQ/AC，不得增加注册",
            "认证方式或其方案前置能力未在上游确认时",
            "停止生成并退回 prd-master",
            "只列对应 REQ/AC 需要的字段和动作",
        ):
            self.assertIn(phrase, template)
        for default in (
            "手机号 / 邮箱 / 验证码 / 第三方账号",
            "登录方式切换（密码登录 / 验证码登录）",
            "6 位数字短信验证码",
            "手机号 + 验证码",
        ):
            self.assertNotIn(default, template)
        self.assertIn("认证门禁（条件触发）", page_list)
        self.assertIn("不得因为产品“有用户”就自动补齐整套账号页面", page_list)
        self.assertNotIn("**基础门禁**：登录、注册、找回密码、引导页", page_list)
        self.assertIn("不得从模板示例增加短信/邮件验证码", page_writer)


class DesignDecisionQualityContractTests(unittest.TestCase):
    def test_design_preserves_named_roles_and_socratic_method(self) -> None:
        skill = SKILLS["design-master"]
        specification = (
            ROOT / "design-master/references/技术方案架构.md"
        ).read_text(encoding="utf-8")
        for phrase in (
            "首席设计官 + 信息架构师 + 顶级 UI/UX 前端 + 软件系统架构师 + CTO",
            "苏格拉底多轮互动决策",
            "跟人吵",
            "这就是苏格拉底",
        ):
            self.assertIn(phrase, skill)
        self.assertNotIn("本 skill 的灵魂", skill)
        self.assertIn("首席技术官 (CTO) & 技术选型决策架构师", specification)

    def test_technical_selection_scales_document_depth_with_decision_impact(self) -> None:
        specification = (
            ROOT / "design-master/references/技术方案架构.md"
        ).read_text(encoding="utf-8")
        for phrase in (
            "直接默认",
            "需要对比",
            "不生成评分矩阵",
            "不引入新的基础设施或外部服务",
            "为凑数量制造候选即不合格",
        ):
            self.assertIn(phrase, specification)

    def test_single_instance_persistent_storage_defaults_to_local_filesystem(self) -> None:
        specification = (
            ROOT / "design-master/references/技术方案架构.md"
        ).read_text(encoding="utf-8")
        for phrase in (
            "服务器本地持久化目录",
            "数据库文件元数据",
            "单实例还是多实例",
            "本地磁盘是否持久化",
            "客户端直传或 CDN",
            "RPO/RTO",
            "多实例共享",
        ):
            self.assertIn(phrase, specification)
        self.assertIn(
            "单实例持久化磁盘足够时推荐服务器本地持久化目录",
            specification,
        )

    def test_stage_two_cannot_silently_add_external_dependencies(self) -> None:
        skill = SKILLS["design-master"]
        specification = (
            ROOT / "design-master/references/技术方案架构.md"
        ).read_text(encoding="utf-8")
        for phrase in (
            "外部服务、付费资源、平台账号或部署依赖",
            "必须退回阶段 I",
            "不得新增蓝图未确认",
        ):
            self.assertIn(phrase, skill)
        self.assertIn("不得在阶段 II 静默增加依赖", specification)

    def test_overseas_storage_services_are_in_post_generation_check(self) -> None:
        specification = (
            ROOT / "design-master/references/技术方案架构.md"
        ).read_text(encoding="utf-8")
        self.assertIn(
            "AWS S3 / Cloudflare R2 / Vercel Blob 等海外托管服务名",
            specification,
        )


class ArtifactHandoffContractTests(unittest.TestCase):
    def test_design_html_uses_same_stem_and_stable_page_id(self) -> None:
        html_writer = (ROOT / "design-master/references/页面HTML开发师.md").read_text(encoding="utf-8")
        self.assertIn("主文件名完全一致", html_writer)
        self.assertIn("不得增加 `html/` 子目录", html_writer)
        self.assertIn('meta name="page-id"', html_writer)

    def test_page_list_does_not_claim_stage_is_first_column(self) -> None:
        page_list = (ROOT / "design-master/references/页面清单规划师.md").read_text(encoding="utf-8")
        self.assertNotIn("阶段为表格**第一列**", page_list)
        self.assertIn("所属阶段列填写规则", page_list)

    def test_tdd_design_row_records_real_md_and_html_paths(self) -> None:
        template = (ROOT / "tdd-master/references/TDD模板.md").read_text(encoding="utf-8")
        self.assertIn("pages/实际页面文档.md", template)
        self.assertIn("pages/实际高保真.html", template)
        self.assertNotIn("PAGE-Fxx-xx + pages/*.md + 高保真 HTML", template)

    def test_tdd_contract_forbids_passive_read_and_backfilled_results(self) -> None:
        template = (ROOT / "tdd-master/references/TDD模板.md").read_text(encoding="utf-8")
        specification = (ROOT / "tdd-master/references/TDD验收契约规范.md").read_text(
            encoding="utf-8"
        )
        self.assertIn("TDD验收结果.md", template)
        self.assertIn("- contract_sha256: sha256:<64位hex>", template)
        self.assertIn("- run_started_at: <ISO 8601>", template)
        self.assertIn("未建台账先写代码", template)
        self.assertIn("写完后倒填", template)
        self.assertIn("contract_sha256", specification)
        self.assertIn("pending=0", specification)
        self.assertIn("fail=0", specification)
        self.assertIn("pass=总数", specification)

    def test_goal_uses_content_identity_fallback_without_ambiguous_name_placeholder(self) -> None:
        html = (ROOT / "goal.html").read_text(encoding="utf-8")
        start = '<script type="text/plain" id="goal-payload">\n'
        payload = html.split(start, 1)[1].split("\n</script>", 1)[0].strip()
        self.assertNotIn("pages/<名>", payload)
        self.assertNotIn('"阶段一(MVP)"', payload)
        self.assertIn("唯一且一致则记警告继续", payload)
        self.assertIn("缺失/多匹配/冲突或任一上游契约缺失则停", payload)
        self.assertLessEqual(len(payload), 4000)

    def test_goal_turns_every_tdd_row_into_a_hard_execution_gate(self) -> None:
        html = (ROOT / "goal.html").read_text(encoding="utf-8")
        start = '<script type="text/plain" id="goal-payload">\n'
        payload = html.split(start, 1)[1].split("\n</script>", 1)[0].strip()
        self.assertIn("SMOKE/FLOW/DESIGN/RULE/BUG-*", payload)
        self.assertIn("tests/TDD验收结果.md", payload)
        self.assertIn("`contract_sha256`、`run_started_at`", payload)
        self.assertIn("每个 ID 一行并全置 pending", payload)
        self.assertIn("严禁写完代码后倒填表格冒充执行", payload)
        self.assertIn("仅本轮命令+退出码/API结果/截图可记 pass", payload)
        self.assertIn("用脚本/命令机械比对", payload)
        self.assertIn("ID 集合完全相等且无重复", payload)
        self.assertIn("pending=0、fail=0、pass=总数", payload)
        self.assertNotIn("按契约 FLOW/RULE 组织", payload)
        self.assertLess(payload.index("TDD 开工门禁"), payload.index("纵向闭环"))

    def test_goal_cannot_treat_ui_automation_as_visual_acceptance(self) -> None:
        html = (ROOT / "goal.html").read_text(encoding="utf-8")
        start = '<script type="text/plain" id="goal-payload">\n'
        payload = html.split(start, 1)[1].split("\n</script>", 1)[0].strip()
        self.assertIn("UI 自动化通过≠UI 验收通过", payload)
        self.assertIn("tests/visual/<PAGE>-baseline.png", payload)
        self.assertIn("tests/visual/<PAGE>-actual.png", payload)
        self.assertIn("独立视觉验收官必须实际查看 baseline+actual", payload)
        self.assertIn("不能独立看图即阻塞，严禁自签", payload)
        self.assertIn("reviewer=independent-visual", payload)
        self.assertIn("blocking=0", payload)
        self.assertIn("先一致再优化", payload)
        self.assertIn("禁借“超过原型”自由改版", payload)

    def test_tdd_design_rows_require_concrete_visual_anchors_and_screenshot_pairs(self) -> None:
        template = (ROOT / "tdd-master/references/TDD模板.md").read_text(encoding="utf-8")
        specification = (ROOT / "tdd-master/references/TDD验收契约规范.md").read_text(
            encoding="utf-8"
        )
        for anchor in ("结构=", "组件=", "内容=", "交互=", "视觉=", "原生适配="):
            self.assertIn(anchor, template)
        self.assertIn("同状态集原型/成品 PNG 截图对 + 独立视觉复核", template)
        self.assertIn("UI 自动化不能代替", template)
        self.assertIn("reviewer=independent-visual", specification)
        self.assertIn("只看代码、DOM、测试日志或 UI 自动化结果都不算视觉验收", specification)


class GitMasterContractTests(unittest.TestCase):
    def test_gh_is_optional_for_local_generation(self) -> None:
        text = SKILLS["git-master"]
        self.assertIn("继续本地模板生成", text)
        self.assertIn("不阻塞本地模板生成", text)
        self.assertNotIn("没 gh 这个 skill 跑不了", text)
        self.assertNotIn("装好 + 登录好，才进阶段 0", text)

    def test_overwrite_policy_is_decided_before_silent_generation(self) -> None:
        text = SKILLS["git-master"]
        phase_one = text.index("## 阶段 I · 轻量交互")
        overwrite_choice = text.index("已有配置怎么处理")
        phase_two = text.index("## 阶段 II · 静默生成")
        self.assertLess(phase_one, overwrite_choice)
        self.assertLess(overwrite_choice, phase_two)
        self.assertIn("阶段 II 不再询问", text)
        self.assertNotIn("再询问用户是否覆盖", text)


class BundledReferenceTests(unittest.TestCase):
    REFERENCE = re.compile(
        r"`((?:docs|references|agents|validators)/[^`\n]+?\.(?:md|py))`"
    )

    def test_explicit_bundled_references_exist(self) -> None:
        for name in MASTER_NAMES:
            root = ROOT / name
            for markdown in root.rglob("*.md"):
                text = markdown.read_text(encoding="utf-8")
                for relative in self.REFERENCE.findall(text):
                    with self.subTest(skill=name, document=str(markdown), path=relative):
                        if any(character in relative for character in "*?["):
                            self.assertTrue(list(root.glob(relative)), relative)
                        else:
                            self.assertTrue((root / relative).is_file(), relative)


if __name__ == "__main__":
    unittest.main()
