import shutil
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
DEFAULT_DEPLOY_ROOT = Path("docs/standards/playbook")
CUSTOM_DEPLOY_ROOT = Path("custom/playbook")
REPO_COPY_IGNORE = shutil.ignore_patterns(
    ".git",
    ".venv",
    "node_modules",
    "__pycache__",
    ".pytest_cache",
    ".mypy_cache",
    ".ruff_cache",
)


def run_script(
    script: Path, *args: str, cwd: Path | None = None
) -> subprocess.CompletedProcess[str]:
    return subprocess.run(
        [sys.executable, str(script), *args],
        capture_output=True,
        text=True,
        cwd=cwd,
    )


def write_config(root: Path, name: str, body: str) -> Path:
    config_path = root / name
    config_path.write_text(body.strip() + "\n", encoding="utf-8")
    return config_path


def copy_repo(target: Path) -> Path:
    shutil.copytree(ROOT, target, ignore=REPO_COPY_IGNORE)
    return target


class DeploymentRoutesE2ETests(unittest.TestCase):
    def assert_core_project_files(self, project_root: Path) -> None:
        expected = [
            "AGENTS.md",
            "AGENT_RULES.md",
            "AGENT_RULES.local.md",
            "memory-bank/progress.md",
            "docs/prompts/system/agent-behavior.md",
            ".agents/index.md",
            ".agents/tsl/index.md",
            ".agents/markdown/index.md",
            ".gitattributes",
        ]
        for rel in expected:
            with self.subTest(path=rel):
                self.assertTrue((project_root / rel).exists(), f"missing: {rel}")
        self.assertFalse((project_root / "docs" / "workflows").exists())

    def assert_docs_prefix(self, project_root: Path, docs_prefix: str) -> None:
        agents_index = (project_root / ".agents" / "index.md").read_text(encoding="utf-8")
        self.assertIn("`.agents/tsl/index.md`", agents_index)
        self.assertIn("`.agents/markdown/index.md`", agents_index)
        self.assertIn(f"- {docs_prefix}", agents_index)

        tsl_index = (project_root / ".agents" / "tsl" / "index.md").read_text(
            encoding="utf-8"
        )
        self.assertIn(f"`{docs_prefix}/tsl/index.md`", tsl_index)
        self.assertNotIn("`docs/tsl/index.md`", tsl_index)

    def test_subtree_style_deployment_syncs_project_files(self):
        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_root = Path(tmp_dir)
            project_root = tmp_root / "project"
            playbook_root = project_root / DEFAULT_DEPLOY_ROOT
            project_root.mkdir()
            copy_repo(playbook_root)

            config_path = write_config(
                project_root,
                "playbook.toml",
                """
[playbook]
project_root = "."
playbook_root = "docs/standards/playbook"
install_mode = "subtree"

[sync_rules]
no_backup = true

[sync_memory_bank]
project_name = "Demo"

[sync_prompts]
no_backup = true

[sync_standards]
langs = ["tsl", "markdown"]
no_backup = true
""",
            )

            result = run_script(
                playbook_root / "scripts" / "playbook.py",
                "-config",
                str(config_path),
                cwd=project_root,
            )

            self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)
            self.assert_core_project_files(project_root)
            self.assert_docs_prefix(project_root, "docs/standards/playbook/docs")

            claude_md = project_root / "CLAUDE.md"
            self.assertTrue(claude_md.is_file())
            text = claude_md.read_text(encoding="utf-8")
            self.assertIn("@AGENTS.md", text)
            self.assertIn("@AGENT_RULES.md", text)
            self.assertIn("<!-- playbook:claude:start -->", text)

    def test_external_clone_deployment_installs_snapshot_and_updates_claude(self):
        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_root = Path(tmp_dir)
            external_clone = tmp_root / "playbook"
            project_root = tmp_root / "project"
            copy_repo(external_clone)
            project_root.mkdir()

            claude_md = project_root / ".claude" / "CLAUDE.md"
            claude_md.parent.mkdir()
            claude_md.write_text("# Existing Claude\n\nKeep this.\n", encoding="utf-8")

            config_path = write_config(
                project_root,
                "playbook.toml",
                f"""
[playbook]
project_root = "."
playbook_root = "{CUSTOM_DEPLOY_ROOT.as_posix()}"
install_mode = "snapshot"

[sync_rules]
no_backup = true

[sync_memory_bank]
project_name = "Demo"

[sync_prompts]
no_backup = true

[sync_standards]
langs = ["tsl", "markdown"]
no_backup = true
""",
            )

            result = run_script(
                external_clone / "scripts" / "playbook.py",
                "-config",
                str(config_path),
                cwd=project_root,
            )

            self.assertEqual(result.returncode, 0, msg=result.stdout + result.stderr)

            snapshot_root = project_root / CUSTOM_DEPLOY_ROOT
            self.assertTrue((snapshot_root / "SOURCE.md").is_file())
            self.assertTrue((snapshot_root / "scripts" / "playbook.py").is_file())
            self.assertTrue(
                (snapshot_root / "templates" / "AGENTS.template.md").is_file()
            )
            self.assertTrue(
                (snapshot_root / "templates" / "AGENT_RULES.template.md").is_file()
            )
            self.assertTrue((snapshot_root / "templates" / "README.md").is_file())
            self.assertTrue((snapshot_root / "templates" / "memory-bank").is_dir())
            self.assertTrue((snapshot_root / "templates" / "prompts").is_dir())
            self.assertTrue((snapshot_root / "skills").is_dir())
            self.assertFalse((snapshot_root / "codex").exists())

            self.assert_core_project_files(project_root)
            self.assert_docs_prefix(project_root, "custom/playbook/docs")

            rules_text = (project_root / "AGENT_RULES.md").read_text(encoding="utf-8")
            self.assertIn(
                "`custom/playbook/` 是 Playbook 模板/供应商目录",
                rules_text,
            )
            self.assertNotIn("{{PLAYBOOK_ROOT}}", rules_text)

            text = claude_md.read_text(encoding="utf-8")
            self.assertIn("Keep this.", text)
            self.assertIn("@../AGENTS.md", text)
            self.assertIn("@../AGENT_RULES.md", text)
            self.assertEqual(text.count("<!-- playbook:claude:start -->"), 1)
            self.assertFalse((project_root / "CLAUDE.md").exists())


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