"""
StreamManager - 管理 sandbox 桌面的 VNC/noVNC 流

流启动方式：
  1. 通过 commands.run 在 sandbox 内启动 x11vnc（监听 5901）
  2. 通过 commands.run 在 sandbox 内启动 novnc_proxy（监听 6080）
  3. 通过 multiport 转发（X-Sandbox-Port: 6080）从外部访问 noVNC web 界面

noVNC web URL 格式（multiport 单域名模式）：
  基础：http://{domain}/sandboxes/{sid}/vnc.html
  带参数：.../vnc.html?autoconnect=true&resize=scale
  带认证：.../vnc.html?autoconnect=true&password=<auth_key>
"""

import secrets
import string
import time
from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
    from .sandbox import Sandbox

# noVNC 使用的端口
_VNC_PORT = 5901
_NOVNC_PORT = 6080


class StreamManager:
    """管理 sandbox 桌面 VNC/noVNC 流"""

    def __init__(self, sandbox: "Sandbox"):
        self._sandbox = sandbox
        self._novnc_handle = None
        self._vnc_port: int = _VNC_PORT
        self._novnc_port: int = _NOVNC_PORT
        self._password: Optional[str] = None
        self._require_auth: bool = False

    # ------------------------------------------------------------------
    # 内部工具
    # ------------------------------------------------------------------

    @staticmethod
    def _generate_password(length: int = 16) -> str:
        chars = string.ascii_letters + string.digits
        return "".join(secrets.choice(chars) for _ in range(length))

    def _is_x11vnc_running(self) -> bool:
        try:
            result = self._sandbox.commands.run("pgrep -x x11vnc", timeout=5)
            return result.exit_code == 0 and bool(result.stdout.strip())
        except Exception:
            return False

    def _wait_for_port(self, port: int, max_wait: int = 15) -> bool:
        """等待 sandbox 内某端口监听就绪"""
        elapsed = 0
        while elapsed < max_wait:
            result = self._sandbox.commands.run(
                f"sh -c 'nc -z localhost {port} 2>/dev/null && echo ok || echo fail'",
                timeout=5,
            )
            if result.stdout.strip() == "ok":
                return True
            time.sleep(1)
            elapsed += 1
        return False

    def _build_novnc_url(self, auth_key: Optional[str] = None,
                         auto_connect: bool = True,
                         view_only: bool = False,
                         resize: str = "scale") -> str:
        """构造外部访问 noVNC 的 URL（通过 multiport）"""
        base = self._sandbox._get_novnc_url()
        params = []
        if auto_connect:
            params.append("autoconnect=true")
        if view_only:
            params.append("view_only=true")
        if resize:
            params.append(f"resize={resize}")
        if auth_key:
            params.append(f"password={auth_key}")
        if params:
            return f"{base}?{'&'.join(params)}"
        return base

    # ------------------------------------------------------------------
    # 公开 API
    # ------------------------------------------------------------------

    def start(
        self,
        vnc_port: Optional[int] = None,
        port: Optional[int] = None,
        require_auth: bool = False,
        window_id: Optional[str] = None,
    ) -> None:
        """
        启动桌面流（x11vnc + noVNC）。

        :param vnc_port: x11vnc 监听端口，默认 5901
        :param port: noVNC HTTP 服务端口，默认 6080
        :param require_auth: 是否启用密码保护（自动生成密码）
        :param window_id: 指定仅流式传输某窗口，None 表示整个桌面
        :raises RuntimeError: 若流已在运行或无法启动
        """
        if self._is_x11vnc_running():
            raise RuntimeError("Stream is already running")

        self._vnc_port = vnc_port or _VNC_PORT
        self._novnc_port = port or _NOVNC_PORT
        self._require_auth = require_auth
        self._password = self._generate_password() if require_auth else None

        cmds = self._sandbox.commands
        display = self._sandbox._display

        # 设置 VNC 密码（若需要）
        pwd_flag = "-nopw"
        if require_auth:
            cmds.run("mkdir -p ~/.vnc", timeout=5)
            cmds.run(
                f"x11vnc -storepasswd {self._password} ~/.vnc/passwd",
                timeout=10,
            )
            pwd_flag = "-usepw"

        # 构造 x11vnc 命令
        window_flag = f"-id {window_id}" if window_id else ""
        vnc_cmd = (
            f"DISPLAY={display} x11vnc -bg -display {display} -forever -wait 50 -shared "
            f"-rfbport {self._vnc_port} {pwd_flag} 2>/tmp/x11vnc_stderr.log {window_flag}"
        )
        cmds.run(vnc_cmd, timeout=10)

        # 等待 x11vnc 端口就绪
        if not self._wait_for_port(self._vnc_port, max_wait=15):
            raise RuntimeError(f"x11vnc failed to start on port {self._vnc_port}")

        # 启动 noVNC（后台）
        novnc_cmd = (
            f"cd /opt/noVNC/utils && ./novnc_proxy --vnc localhost:{self._vnc_port} "
            f"--listen {self._novnc_port} --web /opt/noVNC > /tmp/novnc.log 2>&1"
        )
        self._novnc_handle = cmds.run(novnc_cmd, background=True, timeout=0)

        # 等待 noVNC 端口就绪
        if not self._wait_for_port(self._novnc_port, max_wait=20):
            raise RuntimeError(f"noVNC failed to start on port {self._novnc_port}")

    def stop(self) -> None:
        """停止流（关闭 x11vnc 和 noVNC）"""
        try:
            self._sandbox.commands.run("pkill x11vnc 2>/dev/null || true", timeout=10)
        except Exception:
            pass

        if self._novnc_handle is not None:
            try:
                self._novnc_handle.kill()
            except Exception:
                pass
            self._novnc_handle = None

        self._password = None

    def get_url(
        self,
        auth_key: Optional[str] = None,
        auto_connect: bool = True,
        view_only: bool = False,
        resize: str = "scale",
    ) -> str:
        """
        获取 noVNC 流 URL（可直接在浏览器中打开）。

        :param auth_key: 认证密钥（由 get_auth_key() 获取）
        :param auto_connect: 是否自动连接，默认 True
        :param view_only: 是否只读，默认 False
        :param resize: 缩放方式，默认 'scale'
        :return: noVNC URL 字符串
        """
        return self._build_novnc_url(
            auth_key=auth_key,
            auto_connect=auto_connect,
            view_only=view_only,
            resize=resize,
        )

    def get_auth_key(self) -> str:
        """
        获取认证密钥（仅在 start(require_auth=True) 后有效）。

        :return: 密码字符串
        :raises RuntimeError: 若未启用认证
        """
        if not self._password:
            raise RuntimeError(
                "Unable to retrieve stream auth key; check if require_auth is enabled"
            )
        return self._password
