"""
Daemon Server - Persistent background service for QQ Browser Skill.

Hosts two servers:
  1. WebSocket Server (port 8765) — browser extension connects here (long-lived).
  2. HTTP RPC Server (port 8766) — CLI processes connect here to send skill requests.

Usage:
    qqbrowser-skill serve                     # foreground mode
    qqbrowser-skill serve --daemon            # background (daemon) mode
    qqbrowser-skill serve --ws-port 8765 --rpc-port 8766
"""

import asyncio
import json
import logging
import os
import signal
import subprocess
import sys
from http import HTTPStatus
from typing import Optional, Tuple

from .skill_registry import get_executor, SKILL_MAP
from . import vnc_util
from .vnc_proxy import get_vnc_proxy_url
from .constants import DEFAULT_LOG_DIR


def _get_version() -> str:
    """获取当前包版本号。优先从 importlib.metadata 读取，失败则返回 unknown。"""
    try:
        from importlib.metadata import version
        return version("qqbrowser-skill")
    except Exception:
        return "unknown"

logger = logging.getLogger(__name__)

# Default ports
DEFAULT_WS_PORT = 8765
DEFAULT_RPC_PORT = 8766
DEFAULT_RPC_HOST = "127.0.0.1"

# PID / state file location
_STATE_DIR: Optional[str] = None


def _get_state_dir() -> str:
    """Return the directory used for PID/state files.

    On Windows: %LOCALAPPDATA%/qqbrowser-skill  (or %TEMP%)
    On Linux/macOS: ~/.qqbrowser-skill
    """
    global _STATE_DIR
    if _STATE_DIR is not None:
        return _STATE_DIR

    if sys.platform == "win32":
        base = os.environ.get("LOCALAPPDATA", os.environ.get("TEMP", "."))
        _STATE_DIR = os.path.join(base, "qqbrowser-skill")
    else:
        _STATE_DIR = os.path.expanduser("~/.qqbrowser-skill")

    os.makedirs(_STATE_DIR, exist_ok=True)
    return _STATE_DIR


def _state_file_path() -> str:
    return os.path.join(_get_state_dir(), "server.json")


def write_state_file(pid: int, ws_port: int, rpc_port: int):
    """Write the daemon state (PID + ports) to a JSON file."""
    state = {"pid": pid, "ws_port": ws_port, "rpc_port": rpc_port}
    path = _state_file_path()
    try:
        with open(path, "w") as f:
            json.dump(state, f)
        logger.info(f"State file written: {path} -> {state}")
    except Exception as e:
        logger.error(f"Failed to write state file {path}: {e}")


def read_state_file() -> Optional[dict]:
    """Read the daemon state file. Returns None if not found or invalid."""
    path = _state_file_path()
    if not os.path.exists(path):
        return None
    try:
        with open(path, "r") as f:
            return json.load(f)
    except (json.JSONDecodeError, OSError) as e:
        logger.warning(f"Failed to read state file {path}: {e}")
        return None


def remove_state_file():
    """Remove the daemon state file."""
    path = _state_file_path()
    try:
        os.remove(path)
    except OSError as e:
        logger.warning(f"Failed to remove state file {path}: {e}")


def is_daemon_running() -> bool:
    """Check if the daemon is currently running.

    检测顺序：
    1. 优先通过状态文件 + PID 验活判断
    2. 状态文件不存在时，回退到进程扫描（通过命令行特征匹配）
    3. 进程扫描也找不到时，回退到端口探测（默认 RPC 端口）
    """
    # 路径1：状态文件 + PID 验活
    state = read_state_file()
    if state is not None:
        pid = state.get("pid")
        if pid is not None:
            if sys.platform == "win32":
                if _is_pid_alive_windows(pid):
                    return True
            else:
                if _is_pid_alive_unix(pid):
                    return True

    # 路径2：进程扫描兜底
    found_pid, _ = _find_daemon_process()
    if found_pid is not None:
        return True

    # 路径3：端口探测兜底
    if _is_port_listening(DEFAULT_RPC_PORT):
        return True

    return False


def get_daemon_info() -> Optional[dict]:
    """获取 daemon 进程信息，综合状态文件和进程扫描。

    返回 dict 包含 pid, ws_port, rpc_port，或 None（未运行）。
    """
    # 优先从状态文件获取
    state = read_state_file()
    if state is not None:
        pid = state.get("pid")
        if pid is not None:
            alive = _is_pid_alive_windows(pid) if sys.platform == "win32" else _is_pid_alive_unix(pid)
            if alive:
                return state

    # 回退：进程扫描
    found_pid, cmdline = _find_daemon_process()
    if found_pid is not None:
        info = {"pid": found_pid, "ws_port": DEFAULT_WS_PORT, "rpc_port": DEFAULT_RPC_PORT}
        # 尝试从命令行参数中解析端口
        info.update(_parse_ports_from_cmdline(cmdline))
        return info

    # 回退：端口探测
    if _is_port_listening(DEFAULT_RPC_PORT):
        return {"pid": "?", "ws_port": DEFAULT_WS_PORT, "rpc_port": DEFAULT_RPC_PORT}

    return None


def _is_pid_alive_unix(pid: int) -> bool:
    try:
        os.kill(pid, 0)
        return True
    except (OSError, ProcessLookupError):
        return False

def _is_pid_alive_windows(pid: int) -> bool:
    try:
        import ctypes
        kernel32 = ctypes.windll.kernel32
        SYNCHRONIZE = 0x00100000
        handle = kernel32.OpenProcess(SYNCHRONIZE, False, pid)
        if handle:
            kernel32.CloseHandle(handle)
            return True
        return False
    except Exception:
        return False


# ---------------------------------------------------------------------------
# 进程扫描：通过命令行特征查找 daemon 进程
# ---------------------------------------------------------------------------

_DAEMON_CMDLINE_MARKER = "x5use.daemon_server"


def _find_daemon_process() -> Tuple[Optional[int], str]:
    """扫描进程列表，查找正在运行的 daemon 进程。

    通过匹配命令行中的 'x5use.daemon_server' 特征来识别。
    返回 (pid, cmdline_str) 或 (None, "")。
    """
    try:
        if sys.platform == "win32":
            return _find_daemon_process_windows()
        else:
            return _find_daemon_process_unix()
    except Exception as e:
        logger.debug(f"进程扫描失败: {e}")
        return None, ""


def _find_daemon_process_unix() -> Tuple[Optional[int], str]:
    """Unix/macOS：使用 ps 命令扫描进程。"""
    try:
        result = subprocess.run(
            ["ps", "ax", "-o", "pid,command"],
            capture_output=True, text=True, timeout=5
        )
        if result.returncode != 0:
            return None, ""

        my_pid = os.getpid()
        for line in result.stdout.strip().split("\n"):
            line = line.strip()
            if _DAEMON_CMDLINE_MARKER not in line:
                continue
            # 排除 grep 自身和当前进程
            if "grep" in line:
                continue
            parts = line.split(None, 1)
            if len(parts) < 2:
                continue
            try:
                pid = int(parts[0])
            except ValueError:
                continue
            if pid == my_pid:
                continue
            return pid, parts[1]
    except Exception as e:
        logger.debug(f"Unix 进程扫描失败: {e}")
    return None, ""


def _find_daemon_process_windows() -> Tuple[Optional[int], str]:
    """Windows：使用 WMIC 命令扫描进程。"""
    try:
        result = subprocess.run(
            ["wmic", "process", "where",
             f"commandline like '%{_DAEMON_CMDLINE_MARKER}%'",
             "get", "processid,commandline", "/format:csv"],
            capture_output=True, text=True, timeout=10,
            creationflags=0x08000000  # CREATE_NO_WINDOW
        )
        if result.returncode != 0:
            return None, ""

        my_pid = os.getpid()
        for line in result.stdout.strip().split("\n"):
            line = line.strip()
            if not line or _DAEMON_CMDLINE_MARKER not in line:
                continue
            # CSV 格式: Node,CommandLine,ProcessId
            parts = line.split(",")
            if len(parts) < 3:
                continue
            try:
                pid = int(parts[-1].strip())
            except ValueError:
                continue
            if pid == my_pid:
                continue
            cmdline = ",".join(parts[1:-1])  # CommandLine 可能包含逗号
            return pid, cmdline
    except Exception as e:
        logger.debug(f"Windows 进程扫描失败: {e}")
    return None, ""


def _parse_ports_from_cmdline(cmdline: str) -> dict:
    """从命令行字符串中解析 --ws-port 和 --rpc-port 参数。"""
    info = {}
    parts = cmdline.split()
    for i, part in enumerate(parts):
        if part == "--ws-port" and i + 1 < len(parts):
            try:
                info["ws_port"] = int(parts[i + 1])
            except ValueError:
                pass
        elif part == "--rpc-port" and i + 1 < len(parts):
            try:
                info["rpc_port"] = int(parts[i + 1])
            except ValueError:
                pass
    return info


def _is_port_listening(port: int, host: str = "127.0.0.1", timeout: float = 2.0) -> bool:
    """检测指定端口是否有服务在监听。"""
    import socket
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(timeout)
            s.connect((host, port))
            return True
    except (ConnectionRefusedError, OSError, socket.timeout):
        return False


# ---------------------------------------------------------------------------
# HTTP RPC Server (for CLI -> Daemon communication)
# ---------------------------------------------------------------------------

class SimpleHTTPRPCServer:
    """A minimal async HTTP server for receiving skill execution requests from CLI.

    Uses only Python stdlib (asyncio streams) to avoid extra dependencies.
    Supports:
      POST /execute  — execute a skill
      GET  /health   — health check
      GET  /status   — daemon status (connected clients, etc.)
    """

    def __init__(self, host: str, port: int, executor_func):
        self._host = host
        self._port = port
        self._executor_func = executor_func
        self._server: Optional[asyncio.AbstractServer] = None

    async def start(self):
        self._server = await asyncio.start_server(
            self._handle_connection, self._host, self._port
        )
        logger.info(f"HTTP RPC Server listening on http://{self._host}:{self._port}")

    async def stop(self):
        if self._server:
            self._server.close()
            await self._server.wait_closed()
            logger.info("HTTP RPC Server stopped")

    async def _handle_connection(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
        """Handle a single HTTP connection."""
        try:
            # Read the request line and headers
            request_line = await asyncio.wait_for(reader.readline(), timeout=30)
            if not request_line:
                writer.close()
                return

            request_str = request_line.decode("utf-8", errors="replace").strip()
            parts = request_str.split(" ")
            if len(parts) < 2:
                await self._send_response(writer, 400, {"error": "Bad request"})
                return

            method = parts[0].upper()
            path = parts[1]

            # Read headers
            headers = {}
            while True:
                line = await asyncio.wait_for(reader.readline(), timeout=10)
                line_str = line.decode("utf-8", errors="replace").strip()
                if not line_str:
                    break
                if ":" in line_str:
                    key, _, value = line_str.partition(":")
                    headers[key.strip().lower()] = value.strip()

            # Read body if Content-Length is present
            body = b""
            content_length = int(headers.get("content-length", 0))
            if content_length > 0:
                body = await asyncio.wait_for(reader.readexactly(content_length), timeout=300)

            # Route
            if method == "GET" and path == "/health":
                await self._handle_health(writer)
            elif method == "GET" and path == "/status":
                await self._handle_status(writer)
            elif method == "POST" and path == "/execute":
                await self._handle_execute(writer, body)
            else:
                await self._send_response(writer, 404, {"error": "Not found"})

        except asyncio.TimeoutError:
            logger.warning("HTTP RPC: connection timed out")
            try:
                await self._send_response(writer, 408, {"error": "Request timeout"})
            except Exception:
                pass
        except Exception as e:
            logger.error(f"HTTP RPC: error handling connection: {e}")
            try:
                await self._send_response(writer, 500, {"error": str(e)})
            except Exception:
                pass
        finally:
            try:
                writer.close()
                await writer.wait_closed()
            except Exception:
                pass

    async def _handle_health(self, writer: asyncio.StreamWriter):
        """Health check endpoint."""
        await self._send_response(writer, 200, {"status": "ok"})

    async def _handle_status(self, writer: asyncio.StreamWriter):
        """Status endpoint — returns daemon info."""
        executor = self._executor_func()
        ws_mgr = executor.ws_manager
        status = {
            "status": "running",
            "pid": os.getpid(),
            "ws_server_started": ws_mgr.is_server_started(),
            "connected_clients": len(ws_mgr._connected_clients),
        }
        await self._send_response(writer, 200, status)

    async def _handle_execute(self, writer: asyncio.StreamWriter, body: bytes):
        """Execute a skill request from CLI."""
        try:
            request = json.loads(body.decode("utf-8"))
        except (json.JSONDecodeError, UnicodeDecodeError) as e:
            await self._send_response(writer, 400, {"error": f"Invalid JSON: {e}"})
            return

        skill_name = request.get("skill_name")
        args = request.get("args", {})
        log_dir = request.get("log_dir", DEFAULT_LOG_DIR)

        if not skill_name:
            await self._send_response(writer, 400, {"error": "Missing 'skill_name'"})
            return

        if skill_name not in SKILL_MAP:
            await self._send_response(writer, 400, {"error": f"Unknown skill: {skill_name}"})
            return

        logger.info(f"RPC execute: skill={skill_name}, args={args}")

        try:
            # Set browser log dir for this request
            vnc_util.set_browser_log_dir(log_dir)

            executor = self._executor_func()
            result = await executor.execute(skill_name, **args)
            await self._send_response(writer, 200, {
                "success": True,
                "result": result.to_dict(),
            })
        except Exception as e:
            logger.error(f"RPC execute error: {e}")
            await self._send_response(writer, 500, {
                "success": False,
                "error": str(e),
            })

    async def _send_response(self, writer: asyncio.StreamWriter, status_code: int, body: dict):
        """Send an HTTP response with JSON body."""
        body_bytes = json.dumps(body, ensure_ascii=False).encode("utf-8")
        status_phrase = HTTPStatus(status_code).phrase
        response = (
            f"HTTP/1.1 {status_code} {status_phrase}\r\n"
            f"Content-Type: application/json; charset=utf-8\r\n"
            f"Content-Length: {len(body_bytes)}\r\n"
            f"Connection: close\r\n"
            f"\r\n"
        ).encode("utf-8") + body_bytes
        try:
            writer.write(response)
            await writer.drain()
        finally:
            writer.close()
            try:
                await writer.wait_closed()
            except Exception:
                pass


# ---------------------------------------------------------------------------
# Daemon entry point
# ---------------------------------------------------------------------------

async def run_daemon(ws_port: int = DEFAULT_WS_PORT,
                     rpc_port: int = DEFAULT_RPC_PORT,
                     log_dir: str = DEFAULT_LOG_DIR):
    """Start the persistent daemon service (foreground, blocking).

    This coroutine starts both the WebSocket server and the HTTP RPC server,
    then blocks forever until interrupted.
    """
    # 确保 daemon 运行期间日志写入文件
    _ensure_daemon_file_logging(log_dir)

    executor = get_executor()
    rpc_server = None

    # 1. Start WebSocket server for browser extension
    ws_mgr = executor.ws_manager
    try:
        await _start_ws_server_on_port(ws_mgr, ws_port)
    except Exception as e:
        logger.error(f"WebSocket 服务器启动失败，daemon 无法运行: {e}")
        # 启动失败，不写状态文件，直接清理退出
        await executor.cleanup()
        return

    # 2. Start HTTP RPC server for CLI
    try:
        rpc_server = SimpleHTTPRPCServer(DEFAULT_RPC_HOST, rpc_port, get_executor)
        await rpc_server.start()
    except Exception as e:
        logger.error(f"HTTP RPC 服务器启动失败，daemon 无法运行: {e}")
        await executor.cleanup()
        return

    # 3. Write state file
    write_state_file(pid=os.getpid(), ws_port=ws_port, rpc_port=rpc_port)

    logger.info("=" * 60)
    logger.info(f"QQ Browser Skill Daemon is running")
    logger.info(f"  Version          : {_get_version()}")
    logger.info(f"  WebSocket Server : ws://127.0.0.1:{ws_port}  (browser extension)")
    logger.info(f"  HTTP RPC Server  : http://127.0.0.1:{rpc_port}  (CLI requests)")
    logger.info(f"  PID              : {os.getpid()}")
    logger.info(f"  State file       : {_state_file_path()}")
    logger.info("=" * 60)

    # 4. Block forever
    stop_event = asyncio.Event()

    def _signal_handler(sig_num: int):
        sig_name = signal.Signals(sig_num).name
        logger.info(f"Received shutdown signal {sig_name} (signal={sig_num}), stopping daemon...")
        # 立即设置 shutting_down 标志，让 send_message 中的等待循环快速退出
        # 这比等到 cleanup() 被调用时再设置更早，避免 send_message 阻塞 cleanup
        ws_mgr._shutting_down = True
        stop_event.set()

    loop = asyncio.get_running_loop()

    if sys.platform != "win32":
        # Unix: use loop.add_signal_handler
        for sig in (signal.SIGINT, signal.SIGTERM):
            loop.add_signal_handler(sig, _signal_handler, sig)
    else:
        # Windows: signal handlers in asyncio are limited;
        # KeyboardInterrupt (Ctrl+C) will be caught by the caller.
        pass

    try:
        await stop_event.wait()
    except (KeyboardInterrupt, asyncio.CancelledError):
        pass
    finally:
        logger.info("Shutting down daemon...")
        # 先关闭 RPC 服务器，拒绝新的请求
        if rpc_server:
            await rpc_server.stop()
        # 再清理 WebSocket 服务器和所有客户端连接
        # executor.cleanup() 内部会调用 ws_manager.cleanup()，
        # 关闭 WS server 并断开所有客户端，从而使 send_message 循环立即退出
        await executor.cleanup()
        remove_state_file()
        logger.info("Daemon stopped")


async def _start_ws_server_on_port(ws_mgr, port: int):
    """Start the WebSocket server on the specified port.

    Delegates to WebSocketManager.start_server(port) which handles
    duplicate-start protection, IP allow-list, and all internal state.
    """
    await ws_mgr.start_server(port=port)


def _check_already_running(ws_port: int, rpc_port: int) -> bool:
    """Check if a daemon is already running. If so, print info and return True."""
    info = get_daemon_info()
    if info is None:
        # 没有 daemon 在运行，清理可能残留的状态文件
        state = read_state_file()
        if state is not None:
            logger.info("Removing stale state file (process is dead)")
            remove_state_file()
        return False

    pid = info.get("pid", "?")
    existing_ws = info.get("ws_port", "?")
    existing_rpc = info.get("rpc_port", "?")
    print(f"⚠️  Daemon is already running (PID: {pid})")
    print(f"   WebSocket Server : ws://127.0.0.1:{existing_ws}")
    print(f"   HTTP RPC Server  : http://127.0.0.1:{existing_rpc}")
    print(f"   To stop it first: qqbrowser-skill stop")
    return True


def start_daemon_foreground(ws_port: int = DEFAULT_WS_PORT,
                            rpc_port: int = DEFAULT_RPC_PORT,
                            log_dir: str = DEFAULT_LOG_DIR):
    """Start the daemon in the foreground (blocking). Called from CLI."""
    if _check_already_running(ws_port, rpc_port):
        return
    asyncio.run(run_daemon(ws_port=ws_port, rpc_port=rpc_port, log_dir=log_dir))


def start_daemon_background(ws_port: int = DEFAULT_WS_PORT,
                            rpc_port: int = DEFAULT_RPC_PORT,
                            log_dir: str = DEFAULT_LOG_DIR):
    """Start the daemon as a detached background process.

    Works cross-platform:
      - Windows: uses CREATE_NO_WINDOW + DETACHED_PROCESS
      - Unix: uses double-fork or nohup
    """
    import subprocess

    if _check_already_running(ws_port, rpc_port):
        return

    # Build the command to run the daemon using module execution (-m).
    # This ensures relative imports work correctly.
    # We use a simple entry point that calls the daemon's main function.
    cmd = [
        sys.executable,
        "-m", "x5use.daemon_server",
        "--ws-port", str(ws_port),
        "--rpc-port", str(rpc_port),
        "--log-dir", log_dir,
    ]

    # 将后台进程的 stdout/stderr 重定向到日志文件，而非 /dev/null，
    # 避免文件 handler 配置失败时所有日志全部丢失。
    daemon_stdout = subprocess.DEVNULL
    daemon_stderr = subprocess.DEVNULL
    _daemon_log_fh = None
    try:
        os.makedirs(log_dir, exist_ok=True)
        _daemon_log_path = os.path.join(log_dir, "daemon_stdout.log")
        _daemon_log_fh = open(_daemon_log_path, "a", encoding="utf-8")
        daemon_stdout = _daemon_log_fh
        daemon_stderr = _daemon_log_fh
    except Exception as e:
        logger.warning(f"无法创建 daemon stdout 日志文件: {e}，回退到 /dev/null")

    if sys.platform == "win32":
        # Windows: detached process
        # 注意：DETACHED_PROCESS 不会继承当前工作目录，需要显式指定 cwd
        CREATE_NO_WINDOW = 0x08000000
        DETACHED_PROCESS = 0x00000008
        proc = subprocess.Popen(
            cmd,
            creationflags=DETACHED_PROCESS | CREATE_NO_WINDOW,
            close_fds=True,
            stdout=daemon_stdout,
            stderr=daemon_stderr,
            cwd=os.getcwd(),  # 确保 daemon 在正确的工作目录中启动
            env={**os.environ, "PYTHONPATH": os.getcwd()},  # 确保模块导入正常工作
        )
    else:
        # Unix: use nohup-like detachment
        proc = subprocess.Popen(
            cmd,
            start_new_session=True,
            stdout=daemon_stdout,
            stderr=daemon_stderr,
            close_fds=True,
            env={**os.environ, "PYTHONPATH": os.getcwd()},
        )

    # 启动后关闭父进程持有的日志文件句柄（子进程已继承 fd）
    if _daemon_log_fh is not None:
        _daemon_log_fh.close()

    logger.info(f"Daemon started in background (PID: {proc.pid})")
    print(f"✅ Daemon started in background (PID: {proc.pid})")
    print(f"   WebSocket Server : ws://127.0.0.1:{ws_port}")
    print(f"   HTTP RPC Server  : http://127.0.0.1:{rpc_port}")


def stop_daemon():
    """Stop a running daemon.

    优先从状态文件获取 PID，如果状态文件不存在则通过进程扫描查找。
    """
    # 综合获取 daemon 信息（状态文件 + 进程扫描 + 端口探测）
    info = get_daemon_info()
    if info is None:
        print("No daemon is running.")
        return False

    pid = info.get("pid")
    if pid is None or pid == "?":
        # 只通过端口探测到了服务，但无法获取 PID
        print("⚠️  Detected a service on the default RPC port, but cannot determine PID.")
        print(f"   Please manually check port {DEFAULT_RPC_PORT} and stop the process.")
        remove_state_file()
        return False

    if sys.platform == "win32":
        try:
            import ctypes
            kernel32 = ctypes.windll.kernel32
            PROCESS_TERMINATE = 0x0001
            handle = kernel32.OpenProcess(PROCESS_TERMINATE, False, pid)
            if handle:
                kernel32.TerminateProcess(handle, 0)
                kernel32.CloseHandle(handle)
                print(f"✅ Daemon (PID: {pid}) stopped.")
            else:
                print(f"Could not open process {pid}.")
        except Exception as e:
            print(f"Failed to stop daemon: {e}")
    else:
        try:
            os.kill(pid, signal.SIGTERM)
            print(f"✅ Daemon (PID: {pid}) stopped.")
        except ProcessLookupError:
            print(f"Daemon (PID: {pid}) is not running.")
        except PermissionError:
            print(f"Permission denied to stop daemon (PID: {pid}).")

    remove_state_file()
    return True


# ---------------------------------------------------------------------------
# Module entry point (for background process spawning)
# ---------------------------------------------------------------------------

def _ensure_daemon_file_logging(log_dir: str):
    """确保 daemon 运行期间日志写入文件。

    使用显式 handler 配置，避免 logging.basicConfig() 在 root logger
    已有 handler 时静默不生效的问题。
    """
    root = logging.getLogger()
    fmt = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")

    # 检查是否已有 daemon.log 的文件 handler，避免重复添加
    for h in root.handlers:
        if isinstance(h, logging.FileHandler) and h.baseFilename.endswith("daemon.log"):
            return

    try:
        os.makedirs(log_dir, exist_ok=True)
        fh = logging.FileHandler(
            os.path.join(log_dir, "daemon.log"), encoding="utf-8"
        )
        fh.setFormatter(fmt)
        root.addHandler(fh)
        logger.info(f"Daemon file logging configured: {os.path.join(log_dir, 'daemon.log')}")
    except Exception as e:
        # 非致命错误：输出到 stderr 并继续，确保错误不被静默吞掉
        print(f"⚠️  Could not create daemon log file in {log_dir}: {e}", file=sys.stderr)
        logger.warning(f"Failed to create daemon file handler at {log_dir}: {e}")


def _module_main():
    """Entry point when invoked as `python -m x5use.daemon_server`."""
    import argparse

    # 在 Windows 上强制将 stdout/stderr 设置为 UTF-8 编码，避免中文输出乱码
    if sys.platform == "win32":
        import io
        if hasattr(sys.stdout, "buffer"):
            sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
        if hasattr(sys.stderr, "buffer"):
            sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")

    parser = argparse.ArgumentParser(description="QQ Browser Skill Daemon Server")
    parser.add_argument("--ws-port", type=int, default=DEFAULT_WS_PORT)
    parser.add_argument("--rpc-port", type=int, default=DEFAULT_RPC_PORT)
    parser.add_argument("--log-dir", type=str, default=DEFAULT_LOG_DIR)
    args = parser.parse_args()

    # 使用显式 handler 配置日志，避免 basicConfig 在 root logger 已有 handler 时不生效
    root = logging.getLogger()
    root.setLevel(logging.INFO)
    fmt = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")

    # 如果 root logger 还没有 handler，添加一个 stderr handler 作为兜底
    if not root.handlers:
        stderr_handler = logging.StreamHandler(sys.stderr)
        stderr_handler.setFormatter(fmt)
        root.addHandler(stderr_handler)

    logger.info(f"qqbrowser-skill daemon version: {_get_version()}")

    # 添加文件日志 handler
    _ensure_daemon_file_logging(args.log_dir)

    start_daemon_foreground(
        ws_port=args.ws_port,
        rpc_port=args.rpc_port,
        log_dir=args.log_dir,
    )


if __name__ == "__main__":
    _module_main()
