"""回归:docker backend 下 system prompt 必须注入容器路径,绝不泄漏宿主绝对路径 / uid。

背景(2026-06-03 修复 + 06-09 复核):fs/shell/run_python 在 docker backend 跑在容器里
(`<workspace>/users/<uid>` bind 到 `/workspace`)。早期 prompt 焊死宿主绝对路径
(`/home/ubuntu/zcbot/workspace/users/<uid>/<wd>`),LLM 据此 glob,容器内无此路径 →
`[Error] base path not found` → 重试风暴(实测单 task 51 次)。本测试锁住修复:docker 下
prompt 只出现 `/workspace/<wd>`,不出现宿主路径 / uid。host 下仍用本地绝对路径(不回归)。
"""
from __future__ import annotations

import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from uuid import UUID

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from core.agent_builder import _build_system_prompt, load_config, user_root  # noqa: E402
from core.paths import ROOT  # noqa: E402
from core.skills import SkillRegistry  # noqa: E402

_UID = UUID("6b14d2ab-7a6e-4d0b-8562-ea62a801e32c")
_TID = UUID("ab063233-7e90-4f57-9afe-3f7e47b13522")
_HOST_CWD = Path("/home/ubuntu/zcbot")  # 模拟 prod 宿主启动目录


def _build(backend: str, *, allow_rename: bool = True) -> tuple[str, Path, Path]:
    cfg = load_config()
    skills = SkillRegistry(ROOT / cfg.get("skills_dir", "skills"))
    tmp = tempfile.mkdtemp()
    ws = Path(tmp) / "workspace"
    wd = user_root(ws, _UID) / "数据资源展示"
    wd.mkdir(parents=True, exist_ok=True)
    with patch.dict("os.environ", {"ZCBOT_SANDBOX_BACKEND": backend}, clear=False):
        prompt = _build_system_prompt(
            cfg, skills, ws, _HOST_CWD, wd, _UID, _TID, "数据资源展示", "documents",
            allow_working_dir_rename=allow_rename,
            office_to_pdf_available=True,
        )
    return prompt, ws, wd


class TestSystemPromptPaths(unittest.TestCase):
    def test_coding_skill_routes_new_projects_without_forcing_python_work(self):
        skill = (ROOT / "skills" / "coding" / "SKILL.md").read_text(encoding="utf-8")
        self.assertIn("新建 Web 或通用软件项目默认优先 Node.js + TypeScript", skill)
        self.assertIn("新建前端默认使用 Vite", skill)
        self.assertIn("已有项目沿用其语言、框架和包管理器", skill)
        self.assertIn("材料研发及 Python 生态占优的任务优先 Python", skill)

    def test_current_time_rules_are_at_prompt_start(self):
        prompt, _, _ = _build("docker")
        self.assertTrue(prompt.startswith("## 当前时间与时效\n"))
        self.assertIn("Asia/Shanghai", prompt)
        self.assertIn("用户未指定时间范围时，搜索词保持无年份", prompt)
        self.assertIn("搜索工具的 freshness", prompt)

    def test_docker_prompt_uses_container_path_no_host_leak(self):
        prompt, ws, wd = _build("docker")
        # 容器路径在
        self.assertIn("/workspace/数据资源展示", prompt)
        # 宿主路径 / tmp / uid 一律不泄漏
        self.assertNotIn("/home/ubuntu/zcbot", prompt)
        self.assertNotIn(str(ws), prompt)
        self.assertNotIn(str(wd), prompt)
        self.assertNotIn(str(_UID), prompt)
        # docker 下不应再出现「cwd: <宿主路径>」那一行
        self.assertNotIn(f"`{_HOST_CWD}`", prompt)

    def test_host_prompt_keeps_local_absolute_path(self):
        prompt, ws, wd = _build("host")
        # host backend 用本地绝对 task_dir(Windows dev / dogfood),不容器化
        self.assertIn(str(wd), prompt)
        self.assertNotIn("/workspace/数据资源展示", prompt)

    def test_rename_guard_only_when_tool_is_available(self):
        interactive, _, _ = _build("docker", allow_rename=True)
        scheduled, _, _ = _build("docker", allow_rename=False)
        self.assertIn("必须调用 `rename_working_dir`", interactive)
        self.assertNotIn("必须调用 `rename_working_dir`", scheduled)
        self.assertIn("`publish_web_preview`", interactive)
        self.assertNotIn("`publish_web_preview`", scheduled)

    def test_interactive_coding_stack_routing(self):
        interactive, _, _ = _build("docker", allow_rename=True)
        self.assertIn("Node.js + TypeScript", interactive)
        self.assertIn("前端默认 Vite", interactive)
        self.assertIn("已有项目沿用原技术栈", interactive)
        self.assertIn("材料研发任务在 Python 生态更合适时", interactive)

    def test_report_pdf_uses_platform_renderer_directly(self):
        prompt, _, _ = _build("docker")
        self.assertIn(
            "python /sandbox/rendering/render.py --profile report --format pdf",
            prompt,
        )
        self.assertIn("不先生成 DOCX", prompt)
        self.assertIn("`office_to_pdf`", prompt)
        self.assertIn("LibreOffice 在 backend host", prompt)

    def test_existing_pdf_has_one_platform_ingest_route(self):
        prompt, _, _ = _build("docker")
        self.assertIn("## 读取已有 PDF", prompt)
        self.assertIn("**唯一入口**", prompt)
        self.assertIn("先用 `markitdown` CLI", prompt)
        self.assertIn("转换结果为空 / 近空", prompt)
        self.assertIn("本轮有 `read_document` 时调用它", prompt)
        self.assertIn("依赖与解析器由平台镜像统一提供", prompt)
        self.assertNotIn("pypdf", prompt.lower())

    def test_web_search_schema_reinforces_time_rules(self):
        from tools.web_search import WebSearchTool

        query_desc = WebSearchTool.parameters["properties"]["query"]["description"]
        freshness_desc = WebSearchTool.parameters["properties"]["freshness"]["description"]
        self.assertIn("omit years", query_desc)
        self.assertIn("current date/year", query_desc)
        self.assertIn("narrowest window", freshness_desc)


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