#!/usr/bin/env python3
"""PRD master 的访谈、阶段与恢复协议静态回归测试。"""

from __future__ import annotations

import unittest
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
PRD_MASTER = ROOT / "prd-master"


def read(relative_path: str) -> str:
    return (PRD_MASTER / relative_path).read_text(encoding="utf-8")


SKILL = read("SKILL.md")
README = read("README.md")
LEAD_PM = read("agents/lead-pm.md")
GATE = read("references/提问收敛闸门.md")
STATE = read("docs/STATE-MANAGEMENT.md")
PROGRESS = read("docs/PROGRESS-CARD.md")
ARCHITECTURE = read("docs/ARCHITECTURE.md")
PITFALLS = read("docs/ENTREPRENEUR-PITFALLS.md")
PPT_PLANNER = read("references/PPT大纲规划师.md")
PPT_HTML = read("references/PPT-HTML开发师.md")
MODE_ROUTER = read("references/需求契约与模式路由.md")
TDD_WORKFLOW = read("docs/TDD-WORKFLOW.md")
PRD_DEV_TEMPLATE = read("templates/prd-dev.md")
RESEARCH_GUIDES = "\n".join(
    [
        LEAD_PM,
        read("playbooks/saas.md"),
        read("playbooks/mobile-app.md"),
        read("playbooks/ecommerce.md"),
        read("playbooks/generic.md"),
    ]
)

CONTRACT_GUIDES = "\n".join(
    [
        SKILL,
        README,
        LEAD_PM,
        GATE,
        STATE,
        PROGRESS,
        ARCHITECTURE,
        PPT_PLANNER,
        PITFALLS,
        read("docs/TEACHING-MOMENTS.md"),
        read("docs/INVITE-MEMBERS.md"),
        read("docs/STAGE5-COAUTHORING-PROTOCOL.md"),
        MODE_ROUTER,
        read("playbooks/generic.md"),
        read("agents/cross-chapter-reviewer.md"),
    ]
)


class InterviewContractTests(unittest.TestCase):
    def test_gate_is_the_only_normative_interview_source(self) -> None:
        for text in (SKILL, LEAD_PM):
            self.assertIn("references/提问收敛闸门.md", text)
        self.assertIn("唯一事实源", SKILL)
        self.assertIn("只提供领域素材", LEAD_PM)
        self.assertIn("不在其他文件复制第二套规则", SKILL)
        self.assertNotIn("停止闸门", SKILL)
        self.assertNotIn("停止闸门", LEAD_PM)

    def test_requires_dependency_driven_frontier_batching(self) -> None:
        for phrase in (
            "决策树",
            "决策树前沿",
            "前置依赖已经解决",
            "每轮默认询问整个前沿",
            "重新计算下一轮前沿",
            "同一前沿可以同时包含多个事实题和决策题",
            "必须留到后续轮次",
            "只让依赖该事实的节点等待",
            "其他前沿节点照常询问",
            "用户只回答其中一部分",
            "逐题做依赖预检",
            "推荐和选项",
            "每个编号只对应一个可独立关闭的节点",
            "不设机械上限",
            "不设轮数上限",
            "复杂需求可以问几十轮",
        ):
            self.assertIn(phrase, GATE)
        self.assertIn("用户明确要求逐个问或分批回答时", GATE)
        self.assertNotIn("一次只问一个问题", CONTRACT_GUIDES)
        self.assertNotIn("一轮最多 3", CONTRACT_GUIDES)
        self.assertNotIn("一轮只推进一个决策节点", CONTRACT_GUIDES)
        self.assertNotIn("一次回答一个关键问题", CONTRACT_GUIDES)
        self.assertNotIn("每次只问一个", CONTRACT_GUIDES)
        self.assertNotIn("每次只选一个最关键的问题", CONTRACT_GUIDES)
        self.assertNotIn('追问后立即给出"建议答案"', CONTRACT_GUIDES)
        self.assertIn("只给决策题建议答案", PITFALLS)
        self.assertIn("事实题保持开放", PITFALLS)
        self.assertIn("本轮另一题的答案可能改变推荐、选项或是否该问", GATE)

    def test_stage_one_frontier_can_resume_without_reconstruction_guesswork(self) -> None:
        for phrase in (
            "`open_questions` 是未决节点和依赖关系的唯一事实源",
            '"type": "stage1_frontier"',
            '"question_ids": ["OQ-7", "OQ-9"]',
            '"research_ids": ["OQ-8"]',
            "pending_user",
            "pending_research",
            "researching",
            "本轮实际前沿快照",
            "不得加入依赖仍未解决的节点",
        ):
            self.assertIn(phrase, STATE)
        self.assertIn("从 `open_questions` 重算后再继续", STATE)
        self.assertIn("不向用户重复已解决的问题", STATE)
        self.assertIn("`state.json.next_action`", GATE)
        self.assertIn("`references/提问收敛闸门.md`、`docs/STATE-MANAGEMENT.md`", SKILL)
        for phrase in ("evidence_needed", "acceptance_threshold", "due_by"):
            self.assertIn(phrase, STATE)
        for phrase in (
            '"kind": "fact"',
            '"owner": "企业 IT 数据负责人"',
            '"status": "pending_user"',
            "严格使用下面的字段名和值",
            "不得发明另一种类型",
        ):
            self.assertIn(phrase, STATE)
        self.assertIn("事实被确认“当前未知”后，不再换种说法重复询问", GATE)
        self.assertIn("不能只写一句“后续验证”", GATE)

    def test_uses_evidence_grounded_micro_interview_techniques(self) -> None:
        for phrase in (
            "行为证据优先",
            "最近一次具体发生了什么",
            "镜像模糊词",
            "精确追问链",
            "一次可证伪重定义",
            "只复述其中的关键词",
            "只在出现对应信号时使用",
            "问完即停",
        ):
            self.assertIn(phrase, GATE)
        self.assertIn("不继续套心理解释", GATE)

    def test_full_progress_card_is_reserved_for_milestones(self) -> None:
        for phrase in (
            "启动新流程或恢复上次流程",
            "阶段切换",
            "用户要求暂停",
            "最终完成",
            "用户主动询问进度",
            "普通访谈轮次**不追加完整行动卡**",
        ):
            self.assertIn(phrase, PROGRESS)
        self.assertIn("问题必须是该轮最后内容", PROGRESS)
        self.assertIn("阶段 1 普通访谈轮次不追加行动卡", SKILL)
        self.assertNotIn("每次给企业家的输出末尾", PROGRESS)
        self.assertNotIn("每次输出末尾必须带", SKILL)

    def test_bounded_delegation_is_supported(self) -> None:
        for phrase in ("授权范围", "低/中影响决定", "不再逐题打扰"):
            self.assertIn(phrase, GATE)
        self.assertNotIn("不构成全局授权", CONTRACT_GUIDES)

    def test_fact_lookup_is_scoped_to_the_current_blocker(self) -> None:
        self.assertIn("定点查证", GATE)
        self.assertIn("系统性研究留到阶段 3", GATE)
        self.assertIn("阶段 1 不提前展开整套调研", GATE)

    def test_depth_checks_apply_to_branches_not_every_answer(self) -> None:
        self.assertIn("不是每个回答都必须机械满足全部四项", GATE)
        self.assertIn("简单事实得到可信答案即可关闭", GATE)
        self.assertNotIn("回答必须同时经得起", CONTRACT_GUIDES)

    def test_exit_requires_open_questions_cleared_and_user_confirmation(self) -> None:
        self.assertIn("state.json.open_questions", GATE)
        self.assertIn("没有阻塞阶段 2 的 P0/P1 未决节点", GATE)
        self.assertIn("用户明确确认没有漏掉关键问题", GATE)
        self.assertIn("用户要求暂停时", GATE)
        self.assertIn("不能标记阶段 1 已完成", GATE)
        for phrase in ("三种 Feature 模式", "都强制确认"):
            self.assertIn(phrase, SKILL)
        self.assertIn("闸门证据表", GATE)
        self.assertIn("不能因为 Agent 没把问题写进 `open_questions`", GATE)

    def test_roles_prerequisites_and_external_configuration_are_explicit_product_decisions(self) -> None:
        for phrase in (
            "单角色 + 不拆理由",
            "多角色 + 各角色边界",
            "先创建“方案前置能力”事实节点",
            "只向用户询问现有资源、申请意愿、负责人、预算和时间等内部事实",
            "没有短信通道不等于自动改成账号密码",
            "开发者账号、签名/证书和发布责任是否具备",
            "凭据由平台、客户、最终用户还是内部技术团队提供",
            "部署密钥、首次引导、普通设置页还是管理后台",
            "`deployment-secret` 是内部统一部署时的合法答案",
            "环境变量不是替代方案",
            "不要因为“用了外部服务”就默认建设管理后台",
        ):
            self.assertIn(phrase, GATE)
        for phrase in (
            "已明确单角色/多角色结论",
            "状态、负责人、证据或截止时间、替代方案、阶段和阻塞需求",
            "凭据归属、配置角色、入口、作用域、生命周期和所属阶段",
        ):
            self.assertIn(phrase, GATE)
        self.assertIn("**方案前置能力与外部配置责任**", SKILL)
        self.assertIn("Capability prerequisites", SKILL)
        self.assertIn("External capability configuration", SKILL)

    def test_anti_stall_rule_cannot_skip_the_interview_gate(self) -> None:
        self.assertIn("不适用于阶段 1 的用户访谈", SKILL)
        self.assertIn("不能用来清除 P0/P1 未决问题", SKILL)
        self.assertIn("必做环节最多标 `[PARTIAL]` 并保持未完成", SKILL)

    def test_runtime_guides_contain_no_old_early_stop_rules(self) -> None:
        forbidden = (
            "挑 Top 5 关键问题",
            "最多 5 轮深挖",
            "已经问够 5 轮",
            "最多 5 轮追问",
            "追问最多 2 轮",
            "每轮最多 1-2 个问题",
            "问你 5-8 个关键问题",
            "3-5 轮深挖后",
        )
        for phrase in forbidden:
            with self.subTest(phrase=phrase):
                self.assertNotIn(phrase, CONTRACT_GUIDES)

    def test_all_guides_use_the_v05_stage_map(self) -> None:
        canonical = (
            "阶段 0 · 自由倾诉",
            "阶段 1 · 决策树深挖",
            "阶段 2 · 价值论证",
            "阶段 3 · 自动调研",
            "阶段 4 · 方案 V0",
            "阶段 5 · PRD 终稿",
            "阶段 6 · 方案 PPT",
        )
        for text in (README, ARCHITECTURE):
            for phrase in canonical:
                with self.subTest(document=text[:20], phrase=phrase):
                    self.assertIn(phrase, text)

        stale = (
            "六阶段",
            "6 阶段",
            "N/6",
            "/6 阶段",
            "阶段 2 · 自动调研",
            "阶段 3 · 方案 V0",
            "阶段 4 · 交互验证",
            "| 2 自动调研",
            "| 3 V0+辩论",
            "| 4 Demo",
            "阶段 3.5 · 方案 V1",
            "回到 3.1 出 V0",
        )
        for phrase in stale:
            with self.subTest(phrase=phrase):
                self.assertNotIn(phrase, CONTRACT_GUIDES)

    def test_resume_state_cannot_skip_stage_four(self) -> None:
        self.assertIn('"state_schema_version": 3', STATE)
        self.assertIn('"workflow_version": "0.6"', STATE)
        self.assertIn('"current_stage": "4.5"', STATE)
        self.assertIn('"skipped_stages": []', STATE)
        self.assertNotIn('"skipped_stages": ["4"]', STATE)
        self.assertIn('proposal-v1.md exists: state.current_stage = "4.5"', STATE)
        self.assertIn("旧阶段 2（调研）映射到新阶段 3", STATE)
        self.assertIn("legacy_completed_without_value_stage", STATE)
        self.assertIn("从 v0.5 或更早状态升级到 v0.6", STATE)
        self.assertIn('"ppt_outline": null', STATE)
        self.assertIn("every page p01..pNN declared in ppt.md exists", STATE)
        self.assertIn('output/ppt.md exists: state.current_stage = "6.2"', STATE)
        self.assertIn('scene-anchor.md contains "价值论证（阶段 2 已确认）"', STATE)
        self.assertIn('"author_calls": 0', STATE)
        self.assertIn('"drafts": []', STATE)

    def test_value_proof_is_persisted_before_research(self) -> None:
        self.assertIn("价值论证（阶段 2 已确认）", SKILL)
        self.assertIn("追加到 `scene-anchor.md`", SKILL)
        self.assertIn("不代表已经实测", SKILL)
        self.assertIn("每题只确认一个变量", SKILL)
        self.assertIn("每题**只推进一个范围决定**", SKILL)

    def test_progress_finishes_only_after_ppt(self) -> None:
        self.assertIn("阶段 5.6 · PRD 终稿交付（已完成 6/7 阶段）", PROGRESS)
        self.assertIn("阶段 6.3 · 最终交付（已完成 7/7 阶段", PROGRESS)
        self.assertIn("### 6.3 最终交付", SKILL)
        self.assertIn("不要把整个工作流标记完成", SKILL)

    def test_ppt_outline_has_one_authoritative_structure(self) -> None:
        self.assertIn("唯一事实源", SKILL)
        self.assertIn("# 工作流程：8 段固定结构", PPT_PLANNER)
        self.assertIn("总页数通常 10-12 页", PPT_PLANNER)
        self.assertNotIn("上限 7 页", CONTRACT_GUIDES)

    def test_ppt_html_does_not_require_a_future_design_system(self) -> None:
        self.assertIn("共享视觉契约", PPT_HTML)
        self.assertIn("PPT 不依赖 `DESIGN.md` 或 `tokens.css`", PPT_HTML)
        self.assertIn("不得向用户索要设计文件", PPT_HTML)
        self.assertNotIn("请先读取 ppt.md、DESIGN.md 和 tokens.css", PPT_HTML)

    def test_quick_mode_reduces_only_nonessential_confirmations(self) -> None:
        self.assertIn("阶段 1 共识、价值接受、MVP 边界", MODE_ROUTER)
        self.assertIn("quick 默认快速档", SKILL)
        self.assertIn("不做形式化确认", SKILL)
        self.assertIn("直接进入 4.6 的强制 MVP 边界确认", SKILL)
        self.assertIn("只升级受影响环节", MODE_ROUTER)
        self.assertIn("阶段 5/6 不加形式化确认", MODE_ROUTER)
        self.assertNotIn("每阶段确认", MODE_ROUTER)
        self.assertNotIn("自动回 standard", MODE_ROUTER)

    def test_blocking_unknown_cannot_be_closed_as_tbd(self) -> None:
        self.assertIn("阻塞产品方向的未知不能靠 `_TBD_`", GATE)
        self.assertIn("阻塞决定不能用 `_TBD_` 收尾", LEAD_PM)

    def test_tdd_handoff_uses_current_output_contract(self) -> None:
        for path in (
            "output/PRD-dev.md",
            "output/requirements.md",
            "output/PRD详细版.md",
        ):
            self.assertIn(path, TDD_WORKFLOW)
        self.assertNotIn("把 PRD.md 复制到项目根目录", TDD_WORKFLOW)
        self.assertNotIn("**完整 PRD**: PRD.md", PRD_DEV_TEMPLATE)

    def test_primary_validator_commands_are_cross_platform(self) -> None:
        self.assertIn("macOS / Linux 用 `python3`", SKILL)
        self.assertIn("Windows 用 `py -X utf8`", SKILL)
        self.assertNotIn("\npy prd-master/validators/", SKILL)

    def test_research_is_not_locked_to_one_agents_tool_names(self) -> None:
        self.assertIn("当前环境可用的联网搜索", RESEARCH_GUIDES)
        self.assertNotIn("WebSearch", RESEARCH_GUIDES)
        self.assertNotIn("WebFetch", RESEARCH_GUIDES)
        self.assertIn("{工作区根目录}/evidence-cache", SKILL)


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