"""
Hermes Agent 沙箱补丁

强制将 hermes-agent 的所有文件操作限制在 _SESSION_CWD 指定的目录内，
防止 LLM 通过绝对路径或 ../ 路径遍历逃逸到工作目录之外。

设计原理：
- _SESSION_CWD 是 hermes-agent 内部的 contextvar（线程隔离），
  hermes_bridge.py 在每次 /run 时通过 _SESSION_CWD.set(working_dir) 注入。
- 本模块在 hermes-agent import 后 monkey-patch 三个关键函数，
  使其在 _SESSION_CWD 已设置时强制路径边界检查。
- 未设置 _SESSION_CWD 时（如 hermes-agent 独立运行），patch 函数透传原逻辑，
  不影响 hermes-agent 原生行为。

三层防御：
1. tools.file_tools._resolve_path_for_task —— 解析后强制路径必须在 _SESSION_CWD 内
2. tools.file_tools._resolve_base_dir     —— 相对路径基点直接锚定到 _SESSION_CWD
3. agent.coding_context._git_root         —— 隔离目录内不向上递归找 .git
   （否则 system_prompt 会注入外层项目根，主动误导 LLM 写到隔离目录之外）
"""

import logging
from pathlib import Path
from typing import Optional, Union

logger = logging.getLogger("hermes-bridge.sandbox")

_installed = False


def install_sandbox_patches() -> bool:
    """安装 hermes-agent 沙箱补丁。返回是否成功安装。

    幂等：重复调用只安装一次。
    """
    global _installed
    if _installed:
        return True

    try:
        from agent.runtime_cwd import _SESSION_CWD
        from tools import file_tools
        from agent import coding_context
    except ImportError as e:
        logger.warning("沙箱补丁安装失败（hermes-agent 未就绪）: %s", e)
        return False

    # ---- 保存原函数 ----
    _orig_resolve_path = file_tools._resolve_path_for_task
    _orig_resolve_base = file_tools._resolve_base_dir
    _orig_git_root = coding_context._git_root

    def _session_cwd_or_none() -> Optional[Path]:
        """读取当前线程的 _SESSION_CWD，未设置或非法时返回 None。"""
        try:
            raw = _SESSION_CWD.get()
        except LookupError:
            return None
        # hermes-agent 用 _UNSET 哨兵作为 default，类型不是 str/Path 时视为未设置
        if not isinstance(raw, (str, Path)):
            return None
        try:
            p = Path(raw).expanduser().resolve()
        except (OSError, ValueError):
            return None
        if not p.is_absolute():
            return None
        return p

    def _sandbox_resolve_path(filepath: str, task_id: str = "default") -> Path:
        """路径解析后强制必须在 _SESSION_CWD 之内。"""
        resolved = _orig_resolve_path(filepath, task_id)
        sandbox = _session_cwd_or_none()
        if sandbox is None:
            return resolved  # 未启用沙箱，透传原逻辑

        # 强制 resolved 必须等于 sandbox 或位于 sandbox 之内
        try:
            resolved.relative_to(sandbox)
        except ValueError:
            raise PermissionError(
                f"沙箱拒绝访问：路径 {filepath!r} 解析为 {resolved}，"
                f"超出了当前运行目录 {sandbox}。"
            )
        return resolved

    def _sandbox_resolve_base(task_id: str = "default") -> Path:
        """相对路径基点直接锚定到 _SESSION_CWD（不再依赖 TERMINAL_CWD / os.getcwd）。"""
        sandbox = _session_cwd_or_none()
        if sandbox is not None:
            return sandbox
        return _orig_resolve_base(task_id)

    def _sandbox_git_root(cwd: Path) -> Optional[Path]:
        """隔离模式下不识别 .git（防止 system_prompt 注入外层项目根）。"""
        if _session_cwd_or_none() is not None:
            return None
        return _orig_git_root(cwd)

    # ---- 替换 ----
    file_tools._resolve_path_for_task = _sandbox_resolve_path
    file_tools._resolve_base_dir = _sandbox_resolve_base
    coding_context._git_root = _sandbox_git_root

    _installed = True
    logger.info(
        "已安装 hermes-agent 沙箱补丁："
        "_resolve_path_for_task / _resolve_base_dir / _git_root "
        "已强制锚定到 _SESSION_CWD"
    )
    return True
