# -*- coding: utf-8 -*-
"""Remote server database/RPC queries and role RPC helpers."""

from .shared import *
from .erlang import _ensure_epmd_daemon, get_erl_cmd, get_local_ip

def query_remote_servers(db_host: str, db_port: int, db_user: str, db_pass: str, 
                         db_name: str) -> List[Dict[str, str]]:
    """从登录服数据库查询远程服务器列表
    
    Args:
        db_host: 数据库地址
        db_port: 数据库端口
        db_user: 数据库用户名
        db_pass: 数据库密码
        db_name: 数据库名（登录服数据库，如 ai002_login_s900）
    
    Returns:
        服务器列表，每项包含 server_id, server_node, center_node 等信息
    """
    try:
        import pymysql
    except ImportError:
        # 尝试使用 mysql-connector
        try:
            import mysql.connector as pymysql
        except ImportError:
            raise ImportError("需要安装 pymysql 或 mysql-connector-python: pip install pymysql")
    
    servers = []
    
    try:
        # 连接数据库
        conn = pymysql.connect(
            host=db_host,
            port=db_port,
            user=db_user,
            password=db_pass,
            database=db_name,
            charset='utf8mb4'
        )
        
        cursor = conn.cursor()
        
        # 查询 server_info 表
        # 注意: running 字段是运行时字段(sync=false)，不存储在数据库中
        sql = """
            SELECT server_id, server_node, center_node, game_db 
            FROM server_info 
            WHERE server_node IS NOT NULL AND server_node != ''
            ORDER BY server_id
        """
        cursor.execute(sql)
        
        for row in cursor.fetchall():
            server_id, server_node, center_node, game_db = row
            servers.append({
                'server_id': str(server_id),
                'server_node': str(server_node) if server_node else '',
                'center_node': str(center_node) if center_node else '',
                'game_db': str(game_db) if game_db else '',
                'running': False  # 运行状态需要实时检测，这里默认为 False
            })
        
        cursor.close()
        conn.close()
        
    except Exception as e:
        raise Exception(f"数据库查询失败: {str(e)}")
    
    return servers


def _erl_quoted_atom(node: str) -> str:
    """将节点名转为 Erlang 源码中的单引号原子字面量。"""
    n = (node or "").strip()
    if not n:
        raise ValueError("登录服节点为空")
    return "'" + n.replace("\\", "\\\\").replace("'", "\\'") + "'"


# 由 file:script/1 加载。输出 SM_COUNT 行 + 每行「服务器ID\t名称base64\t节点明文」。
# 注意：file:script 生成的匿名 fun 属于 erl_eval，若本地与远程 OTP 版本不一致会 badfun。
# 需确保本地 erl 路径所指版本与登录服 OTP 版本相同。
_FETCH_REMOTE_RPC_SCRIPT = """begin
    LN = __LOGIN_ATOM__,
    R = rpc:call(LN, erlang, apply, [
        fun() ->
            ms_cache:tab2list_foldl(server_temp_info,
                fun(OneServer, ResultAcc) ->
                    ServerId = element(2, server_temp_info_c:get_server_id(OneServer)),
                    ServerName = unicode:characters_to_binary([
                        element(2, server_temp_info_c:get_server_name(OneServer))
                    ]),
                    case server_info_lib:get_server_node(ServerId) of
                        {_, Node} ->
                            [{ServerId, ServerName, Node} | ResultAcc];
                        _ ->
                            ResultAcc
                    end
                end, [])
        end, []]),
    case R of
        {badrpc, Err} ->
            io:format(standard_io, "RPC_ERROR: ~p~n", [Err]),
            erlang:halt(2, [{flush, true}]);
        _ when is_list(R) ->
            RowLine = fun({Id, NameBin, Node}) ->
                Nb = case NameBin of B when is_binary(B) -> B; _ -> <<>> end,
                NameB64 = binary_to_list(base64:encode(Nb)),
                NodeStr = case Node of
                    N when is_atom(N) -> unicode:characters_to_list(atom_to_binary(N, utf8));
                    N when is_list(N) -> N;
                    N when is_binary(N) -> unicode:characters_to_list(N);
                    _ -> lists:flatten(io_lib:format("~p", [Node]))
                end,
                Sid = lists:flatten(io_lib:format("~w", [Id])),
                lists:flatten([Sid, $\\t, NameB64, $\\t, NodeStr])
            end,
            io:format(standard_io, "SM_COUNT\\t~w~n", [length(R)]),
            lists:foreach(
                fun(Row) ->
                    io:format(standard_io, "~s~n", [RowLine(Row)])
                end, R),
            erlang:halt(0, [{flush, true}]);
        Other ->
            io:format(standard_io, "RPC_ERROR: ~p~n", [Other]),
            erlang:halt(3, [{flush, true}])
    end
end.
"""


def query_remote_servers_from_login_rpc(
    login_node: str,
    cookie: str,
    erl_path: Optional[str] = None,
    timeout: int = 120,
) -> List[Dict[str, Any]]:
    """通过向登录服节点 ``rpc:call`` 获取游戏服列表。

    使用 ``erl`` + ``file:script/1`` 执行临时脚本，避免 Windows 超长 ``-eval``。
    注意：脚本中的匿名 fun 属于 erl_eval，需要本地 erl 与登录服 OTP 版本一致，
    否则远程执行会 badfun。

    返回每项含 server_id、server_name（UTF-8 文本）、server_node、running（默认 False）。

    Raises:
        ValueError: 参数无效
        Exception: erl 执行失败、RPC 错误或输出无法解析
    """
    ln = _erl_quoted_atom(login_node)
    ping_node = f"sm_ls_{int(time.time() * 1000) % 100000}"
    cookie_arg = (cookie or "").strip() or "ddxq2-node"
    if any(c in cookie_arg for c in " \t\r\n'\""):
        raise ValueError("Cookie 不能包含空格或引号（请使用项目设置中的纯文本 cookie）")

    erl = get_erl_cmd(erl_path)
    _ensure_epmd_daemon(erl_path)

    # 与 check_nodes_status 一致：本机节点优先用 get_local_ip()，再试 127.0.0.1
    host_parts: List[str] = []
    lip = get_local_ip()
    if lip:
        host_parts.append(lip)
    if "127.0.0.1" not in host_parts:
        host_parts.append("127.0.0.1")
    if not host_parts:
        host_parts = ["127.0.0.1"]

    def _looks_like_vm_nodistribution(stderr: str, stdout: str) -> bool:
        c = (stderr or "") + (stdout or "")
        return any(
            x in c
            for x in (
                "nodistribution",
                "application_start_failure",
                "failed_to_start_child,net_kernel",
                "Kernel pid terminated",
            )
        )

    script_body = _FETCH_REMOTE_RPC_SCRIPT.replace("__LOGIN_ATOM__", ln)

    result: Optional[Any] = None
    for idx, host_part in enumerate(host_parts):
        tmp_path: Optional[str] = None
        try:
            with tempfile.NamedTemporaryFile(
                mode="w",
                suffix=".erl",
                delete=False,
                encoding="utf-8",
                newline="\n",
            ) as tf:
                tf.write(script_body)
                tmp_path = tf.name

            path_for_erl = str(Path(tmp_path).resolve()).replace("\\", "/")
            if '"' in path_for_erl:
                raise ValueError("临时脚本路径含引号，无法传给 Erlang")
            # 脚本内已 halt；若脚本本身无法解析，file:script 返回 {error,_}
            eval_launch = (
                f'case file:script("{path_for_erl}") of '
                "{{error, E}} -> io:format(\"SCRIPT_ERROR: ~p~n\", [E]), halt(1); "
                "_ -> halt(0) end."
            )

            result = subprocess.run(
                [
                    erl,
                    "-noshell",
                    "-name", f"{ping_node}@{host_part}",
                    "-setcookie", cookie_arg,
                    "-eval", eval_launch,
                ],
                capture_output=True,
                text=True,
                encoding="utf-8",
                errors="replace",
                timeout=timeout,
                creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0) if IS_WINDOWS else 0,
            )
        except subprocess.TimeoutExpired as e:
            raise Exception(f"从登录服加载超时（{timeout}s）: {e}") from e
        except FileNotFoundError:
            raise Exception(
                f"找不到 erl 可执行文件: {erl}。"
                "请确认已安装 Erlang/OTP，或在项目设置中填写正确的 Erlang 安装路径。"
            )
        except Exception as e:
            raise Exception(f"执行 erl 失败: {e}") from e
        finally:
            if tmp_path:
                try:
                    os.unlink(tmp_path)
                except OSError:
                    pass

        if result is None:
            raise Exception("内部错误：未获得 erl 执行结果")

        if (
            result.returncode != 0
            and _looks_like_vm_nodistribution(result.stderr, result.stdout)
            and idx < len(host_parts) - 1
        ):
            continue
        break

    if result is None:
        raise Exception("内部错误：未获得 erl 执行结果")

    out = (result.stdout or "").strip()
    err = (result.stderr or "").strip()
    # Windows 下 -noshell 时 io:format 有时落在 stderr；与 stdout 合并后再解析
    combined_text = ((result.stdout or "") + "\n" + (result.stderr or "")).strip()

    rpc_err_lines = [line for line in combined_text.splitlines() if "RPC_ERROR:" in line]
    if rpc_err_lines:
        raise Exception(f"登录服 RPC 失败: {rpc_err_lines[0]}")

    if result.returncode != 0:
        raise Exception(f"erl 退出码 {result.returncode}: {(err or out)[:2000]}")

    servers: List[Dict[str, Any]] = []
    for line in combined_text.splitlines():
        line = line.strip()
        if not line:
            continue
        if line.startswith("RPC_ERROR") or line.startswith("SCRIPT_ERROR"):
            continue
        if line.startswith("SM_COUNT\t"):
            continue
        # 跳过 Eshell/版本等无关行
        if "Eshell" in line or "Erlang/OTP" in line:
            continue
        parts = line.split("\t", 2)
        if len(parts) < 3:
            continue
        sid_s, name_b64, node_s = parts[0], parts[1], parts[2]
        try:
            raw = base64.b64decode(name_b64.encode("ascii"))
            name_dec = raw.decode("utf-8")
        except Exception:
            try:
                name_dec = base64.b64decode(name_b64.encode("ascii")).decode("utf-8", errors="replace")
            except Exception:
                name_dec = ""
        servers.append(
            {
                "server_id": str(sid_s).strip(),
                "server_name": name_dec,
                "server_node": str(node_s).strip(),
                "running": False,
            }
        )

    sm_count: Optional[int] = None
    for line in combined_text.splitlines():
        if line.strip().startswith("SM_COUNT\t"):
            try:
                sm_count = int(line.strip().split("\t", 1)[1])
            except (ValueError, IndexError):
                pass
            break
    if sm_count is not None and sm_count > 0 and len(servers) == 0:
        raise Exception(
            f"登录服报告 SM_COUNT={sm_count} 条，但未解析出数据行（可能编码或输出分流异常）。"
            f" 原始 stdout 前 500 字: {(result.stdout or '')[:500]!r}"
        )

    return servers


def get_login_db_name(prefix: str, login_server_id: int) -> str:
    """生成登录服数据库名
    
    Args:
        prefix: 项目前缀
        login_server_id: 登录服ID
    
    Returns:
        数据库名，如 ai002_login_s900
    """
    return f"{prefix}_login_s{login_server_id}"


def _format_target_node(server_name: str, target_ip: Optional[str]) -> str:
    if '@' in server_name:
        return server_name
    ip = target_ip if target_ip else get_local_ip()
    return f"{server_name}@{ip}"


def _argv_to_shell_line(args: List[str]) -> str:
    """将实际传给 subprocess 的参数列表格式化为可复制的命令行字符串。"""
    if IS_WINDOWS:
        return subprocess.list2cmdline(args)
    return shlex.join(args)


def rpc_clear_server_on_center_and_login(
    center_node: str,
    login_node: str,
    server_id: Union[int, str],
    cookie: str,
    erl_path: Optional[str] = None,
    timeout: int = 45,
) -> Tuple[int, str, str, str]:
    """Notify center/login nodes to clear a game server.

    Returns:
        (returncode, stdout, stderr, full_command_line)
    """
    clean_center_node = str(center_node or "").strip().strip("'\"")
    clean_login_node = str(login_node or "").strip().strip("'\"")
    if not clean_center_node:
        raise ValueError("center_node is empty")
    if not clean_login_node:
        raise ValueError("login_node is empty")

    try:
        sid = int(str(server_id).strip())
    except (TypeError, ValueError) as exc:
        raise ValueError(f"invalid server_id: {server_id!r}") from exc

    cookie_arg = (cookie or "").strip() or "ddxq2-node"
    if any(c in cookie_arg for c in " \t\r\n'\""):
        raise ValueError("cookie cannot contain whitespace or quotes")

    erl = get_erl_cmd(erl_path)
    local_ip = get_local_ip()
    ping_node = f"clear_{os.getpid()}_{time.time_ns() % 100000000}"
    center_atom = _erl_quoted_atom(clean_center_node)
    login_atom = _erl_quoted_atom(clean_login_node)
    eval_code = (
        f"CenterNode = {center_atom}, "
        f"LoginNode = {login_atom}, "
        f"ServerId = {sid}, "
        "CenterResult = rpc:call(CenterNode, center_node_gs_lib, clear_server, [ServerId]), "
        "LoginResult = rpc:call(LoginNode, login_server_app, clear_server, [ServerId]), "
        "io:format(\"CENTER: ~p~nLOGIN: ~p~n\", [CenterResult, LoginResult]), "
        "case {CenterResult, LoginResult} of "
        "{{badrpc, _}, _} -> erlang:halt(2, [{flush, true}]); "
        "{_, {badrpc, _}} -> erlang:halt(3, [{flush, true}]); "
        "_ -> erlang:halt(0, [{flush, true}]) "
        "end."
    )
    args = [
        erl,
        "-noshell",
        "-name", f"{ping_node}@{local_ip}",
        "-setcookie", cookie_arg,
        "-eval", eval_code,
    ]
    cmd_line = _argv_to_shell_line(args)

    try:
        _ensure_epmd_daemon(erl_path)
        result = subprocess.run(
            args,
            capture_output=True,
            text=True,
            encoding="utf-8",
            errors="replace",
            timeout=timeout,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0) if IS_WINDOWS else 0,
        )
        return result.returncode, (result.stdout or ""), (result.stderr or ""), cmd_line
    except subprocess.TimeoutExpired:
        return -1, "", f"RPC timeout after {timeout}s", cmd_line
    except Exception as e:
        return -1, "", str(e), cmd_line


def rpc_role_gs_trace_network(
    server_name: str,
    cookie: str,
    role_id: int,
    enable: bool,
    target_ip: Optional[str] = None,
    erl_path: Optional[str] = None,
) -> Tuple[int, str, str, str]:
    """远程调用 ``role_gs:trace_network/1`` 或 ``trace_network_close/1``。

    Returns:
        (returncode, stdout, stderr, full_command_line)
    """
    erl = get_erl_cmd(erl_path)
    ip = get_local_ip()
    ping_node = f"sm_rpc_{int(time.time() * 1000) % 100000}"
    target_node = _format_target_node(server_name, target_ip)
    func = "trace_network" if enable else "trace_network_close"
    eval_code = (
        f"R = rpc:call('{target_node}', role_gs, {func}, [{int(role_id)}]), "
        f"io:format(\"~p~n\", [R]), halt(0)."
    )
    args = [
        erl,
        "-noshell",
        "-name", f"{ping_node}@{ip}",
        "-setcookie", cookie,
        "-eval", eval_code,
    ]
    cmd_line = _argv_to_shell_line(args)
    try:
        r = subprocess.run(
            args,
            capture_output=True,
            text=True,
            timeout=45,
            creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0) if IS_WINDOWS else 0,
        )
        return r.returncode, (r.stdout or ""), (r.stderr or ""), cmd_line
    except subprocess.TimeoutExpired:
        return -1, "", "RPC 超时", cmd_line
    except Exception as e:
        return -1, "", str(e), cmd_line
