# -*- coding: utf-8 -*-
"""Background workers and command controllers."""

from util.runtime import *

class CommandRunner(QThread):
    """命令执行线程——攒批输出以避免信号风暴卡死 GUI"""
    output_signal = pyqtSignal(str)
    finished_signal = pyqtSignal(int)

    _FLUSH_INTERVAL = 0.15   # 最多每 150ms 向主线程发一次
    _FLUSH_LINES = 80        # 或攒够 80 行发一次

    def __init__(self, command, cwd=None, shell=True, start_new_window=False):
        super().__init__()
        self.command = command
        self.cwd = cwd
        self.shell = shell
        self.start_new_window = start_new_window
        self.process = None

    def run(self):
        """执行命令"""
        import time
        try:
            self.output_signal.emit(f"[{datetime.now().strftime('%H:%M:%S')}] 执行命令: {self.command}\n")
            self.output_signal.emit(f"[{datetime.now().strftime('%H:%M:%S')}] 工作目录: {self.cwd}\n")

            if self.start_new_window:
                subprocess.Popen(
                    f'start cmd /k {self.command}',
                    shell=True,
                    cwd=self.cwd
                )
                self.output_signal.emit(f"[{datetime.now().strftime('%H:%M:%S')}] 已在新窗口中启动命令\n")
                self.finished_signal.emit(0)
                return

            self.process = subprocess.Popen(
                self.command,
                shell=self.shell,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                cwd=self.cwd,
                text=True,
                encoding='utf-8',
                errors='replace'
            )

            buf = []
            last_flush = time.monotonic()
            for line in iter(self.process.stdout.readline, ''):
                if line:
                    buf.append(line)
                now = time.monotonic()
                if len(buf) >= self._FLUSH_LINES or (now - last_flush) >= self._FLUSH_INTERVAL:
                    if buf:
                        self.output_signal.emit(''.join(buf))
                        buf.clear()
                    last_flush = now
            if buf:
                self.output_signal.emit(''.join(buf))

            self.process.wait()
            exit_code = self.process.returncode
            self.output_signal.emit(f"\n[{datetime.now().strftime('%H:%M:%S')}] 命令执行完成, 退出码: {exit_code}\n")
            self.finished_signal.emit(exit_code)

        except Exception as e:
            self.output_signal.emit(f"[错误] {str(e)}\n")
            self.finished_signal.emit(-1)

    def stop(self):
        """停止命令"""
        if self.process:
            self.process.terminate()


class NodeStatusCheckWorker(QThread):
    """节点状态检查线程，避免检查期间阻塞主界面。"""
    finished_signal = pyqtSignal(dict)

    def __init__(self, server_names, cookie, target_ip=None, erl_path=None, parent=None):
        super().__init__(parent)
        self.server_names = list(server_names or [])
        self.cookie = cookie
        self.target_ip = target_ip
        self.erl_path = erl_path

    def run(self):
        results = check_nodes_status(
            self.server_names,
            self.cookie,
            self.target_ip,
            erl_path=self.erl_path,
        )
        self.finished_signal.emit(results)


class GroupedNodeStatusCheckWorker(QThread):
    """按 cookie 分组的节点状态检查线程。"""
    finished_signal = pyqtSignal(dict)

    def __init__(self, cookie_groups, erl_path=None, parent=None):
        super().__init__(parent)
        self.cookie_groups = {
            cookie: list(server_names or [])
            for cookie, server_names in (cookie_groups or {}).items()
        }
        self.erl_path = erl_path

    def run(self):
        merged_results = {}
        for cookie, server_names in self.cookie_groups.items():
            merged_results.update(
                check_nodes_status(server_names, cookie, erl_path=self.erl_path)
            )
        self.finished_signal.emit(merged_results)


class StopServerWorker(QThread):
    """停止服务器线程：
    1) 发送 stop 命令
    2) 轮询节点状态直到离线（或超时）
    3) 关闭此前由本工具启动的相关窗口（服务进程窗口 + remsh 连接窗口）
    """
    progress_signal = pyqtSignal(str)  # 进度消息（带 \n）
    finished_signal = pyqtSignal(bool, str)  # (是否成功停止, 结果描述)
    
    def __init__(self, server_name, cookie, erl_path=None,
                 poll_timeout_sec: int = 30, poll_interval_sec: float = 1.0,
                 parent=None):
        super().__init__(parent)
        self.server_name = server_name
        self.cookie = cookie
        self.erl_path = erl_path
        self.poll_timeout_sec = poll_timeout_sec
        self.poll_interval_sec = poll_interval_sec
    
    def _emit(self, msg: str) -> None:
        if not msg.endswith('\n'):
            msg = msg + '\n'
        self.progress_signal.emit(msg)
    
    def run(self):
        import time
        server = self.server_name
        
        args = build_stop_command(server, self.cookie, erl_path=self.erl_path)
        display_cmd = subprocess_args_to_display(args)
        self._emit(f"[停止] 正在停止 {server}...")
        self._emit(f"[命令] {display_cmd}")
        
        try:
            result = subprocess.run(
                args, capture_output=True, text=True, timeout=15
            )
            if result.returncode != 0:
                self._emit(f"[停止] 命令返回码: {result.returncode}（继续等待节点离线）")
                if result.stderr:
                    self._emit(f"[错误] {result.stderr.strip()}")
        except subprocess.TimeoutExpired:
            self._emit(f"[停止] stop 命令超时，继续等待节点离线")
        except Exception as e:
            self._emit(f"[停止] stop 命令异常: {e}（继续等待节点离线）")
        
        self._emit(f"[停止] 等待 {server} 完全退出（最多 {self.poll_timeout_sec}s）...")
        deadline = time.monotonic() + self.poll_timeout_sec
        offline = False
        while time.monotonic() < deadline:
            try:
                is_online = check_node_status(server, self.cookie, erl_path=self.erl_path)
            except Exception as e:
                logger.debug(f"轮询节点状态异常: {e}")
                is_online = True
            if not is_online:
                offline = True
                break
            time.sleep(self.poll_interval_sec)
        
        if not offline:
            self.finished_signal.emit(False, f"节点 {server} 在 {self.poll_timeout_sec}s 内仍未离线，请手动检查")
            return
        
        self._emit(f"[停止] 节点 {server} 已完全离线")
        
        entries = get_launched_windows(server)
        if not entries:
            self._emit(f"[窗口] 未记录 {server} 的启动窗口（非本工具启动，无需关闭）")
        else:
            self._emit(f"[窗口] 关闭由本工具启动的 {len(entries)} 个关联窗口...")
            results = close_launched_windows(server)
            for entry, ok, msg in results:
                kind_label = '服务进程' if entry.get('kind') == 'server' else '远程连接'
                flag = '✓' if ok else '✗'
                self._emit(f"  {flag} [{kind_label}] {msg}")
        
        self.finished_signal.emit(True, f"服务器 {server} 已停止")


class ClearDatabaseThread(QThread):
    """清档线程 - 避免阻塞主进程"""
    finished_signal = pyqtSignal(list)  # 删除的数据库列表
    error_signal = pyqtSignal(str)  # 错误信息
    status_signal = pyqtSignal(str)  # 状态更新
    
    def __init__(
        self,
        server_name,
        stop_cmd,
        db_host,
        db_port,
        db_user,
        db_pass,
        db_game,
        db_log,
        server_id=None,
        center_node='',
        login_node='',
        cookie='ddxq2-node',
        erl_path=None,
    ):
        super().__init__()
        self.server_name = server_name
        self.stop_cmd = stop_cmd
        self.db_host = db_host
        self.db_port = db_port
        self.db_user = db_user
        self.db_pass = db_pass
        self.db_game = db_game
        self.db_log = db_log
        self.server_id = server_id
        self.center_node = center_node
        self.login_node = login_node
        self.cookie = cookie
        self.erl_path = erl_path
    
    def run(self):
        import time
        import subprocess
        
        try:
            import pymysql
        except ImportError:
            import mysql.connector as pymysql
        
        try:
            logger.info(f"开始清档服务器: {self.server_name}")
            logger.debug(f"数据库信息: host={self.db_host}, port={self.db_port}, game={self.db_game}, log={self.db_log}")
            if self.server_id is None or str(self.server_id).strip() == '':
                raise ValueError("无法获取服务器ID，已取消清档")
            if not str(self.center_node or '').strip():
                raise ValueError("无法获取中心服节点，已取消清档")
            if not str(self.login_node or '').strip():
                raise ValueError("无法获取登录服节点，已取消清档")
            
            # 1. 停止服务器
            self.status_signal.emit("正在停止服务器...")
            logger.info(f"尝试停止服务器: {subprocess_args_to_display(self.stop_cmd)}")
            
            try:
                subprocess.run(self.stop_cmd, timeout=10,
                              stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                logger.info("停止命令已执行，等待服务器关闭...")
                time.sleep(3)
            except subprocess.TimeoutExpired:
                logger.warning("停止命令超时，继续清档")
            except Exception as e:
                logger.warning(f"停止命令执行失败: {e}，继续清档")
            
            # 2. 通知中心服和登录服清档
            self.status_signal.emit("正在通知中心服和登录服清档...")
            logger.info(
                "通知中心服/登录服清档: server_id=%s, center_node=%s, login_node=%s",
                self.server_id,
                self.center_node,
                self.login_node,
            )
            code, stdout, stderr, cmd_line = rpc_clear_server_on_center_and_login(
                self.center_node,
                self.login_node,
                self.server_id,
                self.cookie,
                erl_path=self.erl_path,
            )
            logger.debug(f"清档RPC命令: {cmd_line}")
            if stdout.strip():
                logger.info(f"清档RPC输出: {stdout.strip()}")
            if stderr.strip():
                logger.warning(f"清档RPC错误输出: {stderr.strip()}")
            if code != 0:
                detail = (stderr or stdout or f"exit code {code}").strip()
                raise RuntimeError(f"通知中心服/登录服清档失败: {detail[:1000]}")
            
            # 3. 删除数据库
            self.status_signal.emit("正在删除数据库...")
            logger.info(f"连接数据库: {self.db_host}:{self.db_port}")
            
            conn = pymysql.connect(
                host=self.db_host, port=self.db_port,
                user=self.db_user, password=self.db_pass,
                charset='utf8mb4'
            )
            cursor = conn.cursor()
            
            logger.info(f"删除游戏数据库: {self.db_game}")
            cursor.execute(f"DROP DATABASE IF EXISTS `{self.db_game}`")
            deleted = [self.db_game]
            
            if self.db_log:
                logger.info(f"删除日志数据库: {self.db_log}")
                cursor.execute(f"DROP DATABASE IF EXISTS `{self.db_log}`")
                deleted.append(self.db_log)
            
            conn.commit()
            cursor.close()
            conn.close()
            
            logger.info(f"清档完成: {', '.join(deleted)}")
            self.finished_signal.emit(deleted)
            
        except Exception as e:
            logger.error(f"清档失败: {self.server_name}, 错误: {e}", exc_info=True)
            self.error_signal.emit(str(e))


class UpdateCheckWorker(QThread):
    """后台检查远端是否有新版本，通过信号返回 manifest 或 None"""
    finished_signal = pyqtSignal(object)  # dict manifest or None

    def __init__(self, update_url: str, parent=None):
        super().__init__(parent)
        self.update_url = update_url

    def run(self):
        manifest = fetch_update_manifest(self.update_url)
        self.finished_signal.emit(manifest)


class UpdateDownloadWorker(QThread):
    """后台下载更新包（全量或增量 patch），通过信号报告进度与结果"""
    progress_signal = pyqtSignal(int)  # 0..100
    finished_signal = pyqtSignal(object, bool, object)  # (dest_path or None, is_delta, expected_sha256 or None)

    def __init__(
        self,
        download_url: str,
        dest_path: Path,
        is_delta: bool = False,
        expected_sha256: Optional[str] = None,
        parent=None,
    ):
        super().__init__(parent)
        self.download_url = download_url
        self.dest_path = Path(dest_path)
        self.is_delta = is_delta
        self.expected_sha256 = expected_sha256

    def run(self):
        def on_progress(pct: int):
            self.progress_signal.emit(pct)
        ok = updater_download_file(self.download_url, self.dest_path, progress_callback=on_progress)
        self.finished_signal.emit(
            self.dest_path if ok else None,
            self.is_delta,
            self.expected_sha256,
        )
