"""Docker 隔离沙箱：Code Interpreter 在一次性容器里执行不可信代码。

隔离要点（安全默认）：
- network_disabled：禁网（无外联 / 不能回连）。
- user=65534(nobody) + cap_drop=ALL + no-new-privileges：非 root、丢全部能力、禁提权。
- read_only 根文件系统 + 仅 /tmp 给小块 tmpfs：不能写镜像、不留痕。
- mem_limit + memswap=mem（禁 swap）+ nano_cpus + pids_limit：限内存/CPU/进程数。
- python -I（隔离模式，忽略环境与用户 site）+ wait 超时即 kill + 一次性 remove。
Docker 不可用时优雅降级（available()=False），不阻断服务。
"""

from __future__ import annotations

import asyncio
import logging

log = logging.getLogger("mcp_py")


class CodeInterpreter:
    """Docker 隔离沙箱 · Code Interpreter。"""

    def __init__(self) -> None:
        self._client = None
        self._init_err = "未初始化"
        try:
            import docker  # 延迟导入：未装 docker SDK 时不影响其它工具

            self._client = docker.from_env()
            self._client.ping()
            self._init_err = ""
            log.info("[interpreter] Docker 就绪")
        except Exception as e:  # noqa: BLE001
            self._init_err = str(e)
            log.warning("[interpreter] Docker 不可用，代码执行降级: %s", e)

    def available(self) -> bool:
        return self._client is not None

    async def execute(
        self,
        code: str,
        *,
        image: str = "python:3.11-slim",
        mem: str = "256m",
        cpu: float = 0.5,
        timeout: int = 10,
        pids: int = 64,
    ) -> dict:
        """在一次性隔离容器中执行代码，返回 {ok,stdout,stderr,exit,degraded}。"""
        if self._client is None:
            return {"ok": False, "stdout": "", "stderr": f"Docker 不可用：{self._init_err}", "exit": -1, "degraded": True}
        # Docker SDK 为阻塞式，丢线程池避免卡事件循环。
        return await asyncio.to_thread(self._run_blocking, code, image, mem, cpu, timeout, pids)

    def _run_blocking(self, code: str, image: str, mem: str, cpu: float, timeout: int, pids: int) -> dict:
        container = None
        try:
            container = self._client.containers.run(
                image,
                ["python", "-I", "-c", code],
                detach=True,
                network_disabled=True,
                mem_limit=mem,
                memswap_limit=mem,  # = mem → 禁用 swap
                nano_cpus=int(cpu * 1e9),
                pids_limit=pids,
                read_only=True,
                tmpfs={"/tmp": "size=16m"},
                cap_drop=["ALL"],
                security_opt=["no-new-privileges"],
                user="65534:65534",  # nobody
                stdin_open=False,
                tty=False,
            )
            timed_out = False
            try:
                res = container.wait(timeout=timeout)
                exit_code = int(res.get("StatusCode", -1))
            except Exception:  # noqa: BLE001  容器 wait 超时（requests ReadTimeout）
                try:
                    container.kill()
                except Exception:  # noqa: BLE001
                    pass
                exit_code, timed_out = -1, True
            stdout = container.logs(stdout=True, stderr=False).decode("utf-8", "replace")
            stderr = container.logs(stdout=False, stderr=True).decode("utf-8", "replace")
            if timed_out:
                stderr = (stderr + f"\n[超时 {timeout}s，已终止]").strip()
            return {"ok": exit_code == 0 and not timed_out, "stdout": stdout, "stderr": stderr, "exit": exit_code, "degraded": False}
        except Exception as e:  # noqa: BLE001
            return {"ok": False, "stdout": "", "stderr": f"执行失败：{e}", "exit": -1, "degraded": False}
        finally:
            if container is not None:
                try:
                    container.remove(force=True)
                except Exception:  # noqa: BLE001
                    pass
