#![allow(dead_code)]

use crate::db::pool;
use rusqlite::params;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

// ── 数据结构 ──────────────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MonitoredTool {
    pub id: String,
    pub name: String,
    pub exe_path: String,
    pub enabled: bool,
}

#[derive(Debug, Serialize, Clone)]
pub struct ConnectionInfo {
    pub process_name: String,
    pub process_path: String,
    pub remote_address: String,
    pub remote_port: u16,
}

#[derive(Debug, Serialize, Clone)]
pub struct ModeLogEntry {
    pub id: i64,
    pub mode: String,
    pub created_at: Option<String>,
}

// ── 防火墙规则管理 ────────────────────────────────────────────────────────────

fn rule_display_name(tool_name: &str) -> String {
    format!("DevMgr_Block_{}", tool_name.replace(' ', "_"))
}

fn run_ps(script: &str) -> Result<String, String> {
    let output = crate::silent_cmd("powershell")
        .args(["-NonInteractive", "-NoProfile", "-Command", script])
        .output()
        .map_err(|e| format!("无法启动 PowerShell: {e}"))?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    } else {
        let err = String::from_utf8_lossy(&output.stderr).trim().to_string();
        Err(err)
    }
}

/// 添加出站封锁规则
fn firewall_block(tool_name: &str, exe_path: &str) -> Result<(), String> {
    let name = rule_display_name(tool_name);
    // 先尝试删除旧规则（幂等）
    let _ = run_ps(&format!(
        "Remove-NetFirewallRule -DisplayName '{name}' -ErrorAction SilentlyContinue"
    ));
    run_ps(&format!(
        "New-NetFirewallRule -DisplayName '{name}' -Direction Outbound \
         -Program '{exe_path}' -Action Block -Profile Any -ErrorAction Stop"
    ))
    .map(|_| ())
    .map_err(|e| {
        if e.contains("access") || e.contains("denied") || e.contains("privilege") || e.contains("Privilege") {
            "需要管理员权限才能修改防火墙规则，请以管理员身份运行本程序".to_string()
        } else {
            e
        }
    })
}

/// 删除出站封锁规则（恢复放行）
fn firewall_unblock(tool_name: &str) -> Result<(), String> {
    let name = rule_display_name(tool_name);
    run_ps(&format!(
        "Remove-NetFirewallRule -DisplayName '{name}' -ErrorAction SilentlyContinue"
    ))
    .map(|_| ())
    .map_err(|e| {
        if e.contains("access") || e.contains("denied") || e.contains("privilege") || e.contains("Privilege") {
            "需要管理员权限才能修改防火墙规则，请以管理员身份运行本程序".to_string()
        } else {
            e
        }
    })
}

// ── Tauri 命令 ────────────────────────────────────────────────────────────────

/// 获取当前开发模式状态
#[tauri::command]
pub fn get_dev_mode() -> Result<bool, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;
    let val: Option<String> = conn
        .query_row(
            "SELECT value FROM settings WHERE key = 'dev_mode'",
            [],
            |row| row.get(0),
        )
        .ok();
    // 默认为 false（非开发模式）
    Ok(val.as_deref() == Some("1"))
}

/// 切换开发模式（同时应用/撤销防火墙规则）
#[tauri::command]
pub fn set_dev_mode(enabled: bool) -> Result<(), String> {
    let conn = pool().get().map_err(|e| e.to_string())?;

    // 获取所有已启用的受监控工具
    let mut stmt = conn
        .prepare("SELECT name, exe_path FROM network_monitored_tools WHERE enabled = 1")
        .map_err(|e| e.to_string())?;
    let tools: Vec<(String, String)> = stmt
        .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
        .map_err(|e| e.to_string())?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.to_string())?;

    // 应用防火墙规则
    let mut errors: Vec<String> = Vec::new();
    for (name, path) in &tools {
        let result = if enabled {
            firewall_unblock(name)
        } else {
            firewall_block(name, path)
        };
        if let Err(e) = result {
            errors.push(format!("{name}: {e}"));
            break; // 权限错误无需继续尝试
        }
    }

    if !errors.is_empty() {
        return Err(errors.join("\n"));
    }

    // 保存模式到设置
    conn.execute(
        "INSERT INTO settings (key, value) VALUES ('dev_mode', ?1)
         ON CONFLICT(key) DO UPDATE SET value = excluded.value",
        params![if enabled { "1" } else { "0" }],
    )
    .map_err(|e| e.to_string())?;

    // 记录切换历史
    conn.execute(
        "INSERT INTO network_mode_log (mode, created_at) VALUES (?1, datetime('now'))",
        params![if enabled { "dev" } else { "non_dev" }],
    )
    .map_err(|e| e.to_string())?;

    Ok(())
}

/// 获取受监控工具列表
#[tauri::command]
pub fn get_monitored_tools() -> Result<Vec<MonitoredTool>, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare(
            "SELECT id, name, exe_path, enabled FROM network_monitored_tools ORDER BY name",
        )
        .map_err(|e| e.to_string())?;
    let tools = stmt
        .query_map([], |row| {
            Ok(MonitoredTool {
                id:       row.get(0)?,
                name:     row.get(1)?,
                exe_path: row.get(2)?,
                enabled:  row.get::<_, i64>(3)? == 1,
            })
        })
        .map_err(|e| e.to_string())?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.to_string())?;
    Ok(tools)
}

/// 添加受监控工具
#[tauri::command]
pub fn add_monitored_tool(name: String, exe_path: String) -> Result<String, String> {
    let id = Uuid::new_v4().to_string();
    let conn = pool().get().map_err(|e| e.to_string())?;
    conn.execute(
        "INSERT INTO network_monitored_tools (id, name, exe_path, enabled) VALUES (?1, ?2, ?3, 1)",
        params![id, name, exe_path],
    )
    .map_err(|e| e.to_string())?;

    // 如果当前是非开发模式，立即对新工具应用封锁规则
    let dev_mode: bool = conn
        .query_row("SELECT value FROM settings WHERE key = 'dev_mode'", [], |row| row.get::<_, String>(0))
        .ok()
        .as_deref()
        == Some("1");

    if !dev_mode {
        let _ = firewall_block(&name, &exe_path); // 非关键失败，忽略
    }

    Ok(id)
}

/// 移除受监控工具（同时清除其防火墙规则）
#[tauri::command]
pub fn remove_monitored_tool(id: String) -> Result<(), String> {
    let conn = pool().get().map_err(|e| e.to_string())?;

    // 先查出名称，用于删除防火墙规则
    let name: Option<String> = conn
        .query_row(
            "SELECT name FROM network_monitored_tools WHERE id = ?1",
            params![id],
            |row| row.get(0),
        )
        .ok();

    conn.execute(
        "DELETE FROM network_monitored_tools WHERE id = ?1",
        params![id],
    )
    .map_err(|e| e.to_string())?;

    // 清除对应防火墙规则（不管当前是什么模式都清理干净）
    if let Some(n) = name {
        let _ = firewall_unblock(&n);
    }

    Ok(())
}

/// 获取当前活跃的出站网络连接（只返回受监控工具相关的进程）
#[tauri::command]
pub fn get_active_connections() -> Result<Vec<ConnectionInfo>, String> {
    // 获取受监控工具的进程名（exe 文件名，不含路径）
    let conn = pool().get().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare("SELECT exe_path FROM network_monitored_tools WHERE enabled = 1")
        .map_err(|e| e.to_string())?;
    let paths: Vec<String> = stmt
        .query_map([], |row| row.get(0))
        .map_err(|e| e.to_string())?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.to_string())?;

    if paths.is_empty() {
        return Ok(vec![]);
    }

    // 提取文件名（不含后缀）用于匹配进程名
    let process_names: Vec<String> = paths
        .iter()
        .filter_map(|p| {
            std::path::Path::new(p)
                .file_stem()
                .map(|s| s.to_string_lossy().to_lowercase())
        })
        .collect();

    // PowerShell 脚本：获取已建立的出站连接，每行 "进程名||远端IP|端口"
    // 不读取 MainModule.FileName（极慢，需枚举每个进程的模块列表，且对系统进程易超时）
    let ps_script = r#"
$results = @()
try {
    Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue |
    Where-Object { $_.RemoteAddress -notmatch '^(0\.0\.0\.0|127\.|::1$|::$)' } |
    ForEach-Object {
        $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue
        if ($proc) {
            $results += "$($proc.ProcessName)||$($_.RemoteAddress)|$($_.RemotePort)"
        }
    }
} catch {}
$results -join "`n"
"#;

    let output = run_ps(ps_script).unwrap_or_default();
    let mut connections: Vec<ConnectionInfo> = Vec::new();

    for line in output.lines() {
        let parts: Vec<&str> = line.splitn(4, '|').collect();
        if parts.len() < 4 { continue; }

        let proc_name = parts[0].to_lowercase();
        // 只返回受监控工具的连接
        if !process_names.iter().any(|n| proc_name.contains(n.as_str())) {
            continue;
        }

        let port: u16 = parts[3].parse().unwrap_or(0);
        connections.push(ConnectionInfo {
            process_name:   parts[0].to_string(),
            process_path:   parts[1].to_string(),
            remote_address: parts[2].to_string(),
            remote_port:    port,
        });
    }

    Ok(connections)
}

// ── WSL v2rayA 代理控制 ────────────────────────────────────────────────────────

fn get_wsl_distro() -> String {
    use crate::db::pool;
    use rusqlite::params;
    pool().get().ok()
        .and_then(|conn| {
            conn.query_row(
                "SELECT value FROM settings WHERE key = 'wsl_distro'",
                params![],
                |row| row.get::<_, String>(0),
            ).ok()
        })
        .filter(|s| !s.is_empty())
        .unwrap_or_else(|| "Ubuntu".into())
}

fn run_wsl_root(cmd: &str) -> Result<std::process::Output, String> {
    let distro = get_wsl_distro();
    crate::silent_cmd("wsl")
        .args(["-d", &distro, "-u", "root", "-e", "bash", "-c", cmd])
        .output()
        .map_err(|e| format!("WSL 不可用: {e}"))
}

/// run_wsl_root + 检查 exit code，用于关键写操作
fn run_wsl_root_ok(cmd: &str) -> Result<String, String> {
    let output = run_wsl_root(cmd)?;
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        return Err(if stderr.is_empty() {
            format!("WSL 命令失败 (exit {})", output.status.code().unwrap_or(-1))
        } else {
            let first_line = stderr.lines().next().unwrap_or(&stderr);
            format!("WSL 命令失败: {}", &first_line[..first_line.len().min(120)])
        });
    }
    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}

/// 验证字符串是合法 IPv4/IPv6 地址
fn is_valid_ip(s: &str) -> bool {
    s.parse::<std::net::IpAddr>().is_ok()
}

/// 获取 WSL 对外 IP（用于从 Windows 侧访问 WSL 服务）
/// WSL2 NAT 模式下 localhost 不通，需用 WSL 实际 IP
#[tauri::command]
pub fn get_wsl_ip() -> Result<String, String> {
    let output = run_wsl_root(
        "hostname -I | awk '{print $1}'"
    ).map_err(|_| "WSL 不可用".to_string())?;
    let ip = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if ip.is_empty() {
        Err("无法获取 WSL IP".to_string())
    } else {
        Ok(ip)
    }
}

/// 将 v2rayN 代理端口同步写入 WSL /etc/profile.d/v2rayn-proxy.sh
#[tauri::command]
pub fn sync_wsl_proxy(db_path: Option<String>) -> Result<String, String> {
    // 1. 定位 guiNConfig.json
    let db_file = match db_path.filter(|s| !s.is_empty()) {
        Some(p) => std::path::PathBuf::from(p),
        None => {
            let saved = crate::db::pool().get().ok()
                .and_then(|conn| conn.query_row(
                    "SELECT value FROM settings WHERE key = 'v2rayn_db_path'",
                    rusqlite::params![],
                    |row| row.get::<_, String>(0),
                ).ok())
                .filter(|s| !s.is_empty());
            match saved {
                Some(p) => std::path::PathBuf::from(p),
                None => return Err("未配置 v2rayN 数据库路径，请先在「Win 代理配置」中设置".into()),
            }
        }
    };

    let config_file = db_file.parent()
        .ok_or("无法获取 v2rayN 配置目录")?
        .join("guiNConfig.json");

    // 2. 读取代理端口（Inbound[0].LocalPort，默认 10808）
    let config_str = std::fs::read_to_string(&config_file)
        .map_err(|e| format!("读取 guiNConfig.json 失败: {e}"))?;

    #[derive(serde::Deserialize)]
    struct Inbound { #[serde(rename = "LocalPort")] local_port: u16 }
    #[derive(serde::Deserialize)]
    struct GuiConfig { #[serde(rename = "Inbound")] inbound: Option<Vec<Inbound>> }

    let cfg: GuiConfig = serde_json::from_str(&config_str)
        .map_err(|e| format!("解析 guiNConfig.json 失败: {e}"))?;
    let port = cfg.inbound.as_ref()
        .and_then(|v| v.first())
        .map(|i| i.local_port)
        .unwrap_or(10808);

    // 3. 生成脚本内容并用 base64 写入 WSL，避免 shell 引号转义问题
    let script = format!(
        "# v2rayN proxy - managed by dev-manager (port {port})\n\
         _WIN_HOST=$(ip route 2>/dev/null | grep default | awk '{{print $3}}' | head -1)\n\
         if [ -n \"$_WIN_HOST\" ]; then\n\
           export http_proxy=\"http://$_WIN_HOST:{port}\"\n\
           export https_proxy=\"http://$_WIN_HOST:{port}\"\n\
           export HTTP_PROXY=\"http://$_WIN_HOST:{port}\"\n\
           export HTTPS_PROXY=\"http://$_WIN_HOST:{port}\"\n\
           export ALL_PROXY=\"socks5://$_WIN_HOST:{port}\"\n\
           export no_proxy=\"localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16\"\n\
           export NO_PROXY=\"localhost,127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16\"\n\
         fi\n"
    );
    let encoded = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &script);
    // rm -f 先删除旧文件（包括符号链接本身，不跟随），再创建新普通文件，防止链接劫持
    let cmd = format!(
        "rm -f /etc/profile.d/v2rayn-proxy.sh && echo '{encoded}' | base64 -d > /etc/profile.d/v2rayn-proxy.sh && chmod 644 /etc/profile.d/v2rayn-proxy.sh"
    );
    run_wsl_root_ok(&cmd)?;

    // 让非登录 shell（如 VSCode 终端）也能加载代理变量
    run_wsl_root_ok(
        r#"grep -qF 'v2rayn-proxy.sh' /etc/bash.bashrc 2>/dev/null || \
           echo '[ -f /etc/profile.d/v2rayn-proxy.sh ] && source /etc/profile.d/v2rayn-proxy.sh' >> /etc/bash.bashrc"#
    )?;

    // 为 WSL 默认登录用户写入 ~/.bashrc（root 的 ~ 是 /root，不是用户目录）
    // 优先读 /etc/wsl.conf [user] default=xxx，否则 fallback 到 UID=1000
    run_wsl_root_ok(
        r#"_DEFAULT_USER=$(grep -oP '(?<=default=)\S+' /etc/wsl.conf 2>/dev/null | head -1); \
           [ -z "$_DEFAULT_USER" ] && _DEFAULT_USER=$(getent passwd 1000 | cut -d: -f1); \
           _USER_HOME=$(getent passwd "$_DEFAULT_USER" 2>/dev/null | cut -d: -f6); \
           [ -n "$_USER_HOME" ] && { \
             grep -qF 'v2rayn-proxy.sh' "$_USER_HOME/.bashrc" 2>/dev/null || \
             echo '[ -f /etc/profile.d/v2rayn-proxy.sh ] && source /etc/profile.d/v2rayn-proxy.sh' >> "$_USER_HOME/.bashrc"; \
           }"#
    )?;

    // 将当前 Windows host IP 写入 /etc/environment（PAM 全局，覆盖所有 shell 类型）
    // /etc/environment 不支持 shell 展开，必须用当前实际 IP
    let win_host = {
        let out = run_wsl_root("ip route 2>/dev/null | grep default | awk '{print $3}' | head -1")?;
        String::from_utf8_lossy(&out.stdout).trim().to_string()
    };
    if win_host.is_empty() {
        // WSL 网络异常时 L4（/etc/environment）无法写入，仅 L1 动态脚本生效
        // PAM 类会话（GUI 程序）代理将不可用，给用户明确提示
        return Ok(format!(
            "WSL 代理脚本已写入（端口 {port}），但无法获取 Windows 网关 IP，\
             /etc/environment 未更新（重启 WSL 后可重新同步）"
        ));
    }

    if !win_host.is_empty() {
        let no_proxy_val = "localhost,127.0.0.1,::1";
        let env_block = format!(
            "http_proxy=http://{win_host}:{port}\n\
             https_proxy=http://{win_host}:{port}\n\
             HTTP_PROXY=http://{win_host}:{port}\n\
             HTTPS_PROXY=http://{win_host}:{port}\n\
             ALL_PROXY=socks5://{win_host}:{port}\n\
             no_proxy={no_proxy_val}\n\
             NO_PROXY={no_proxy_val}"
        );
        let env_b64 = base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &env_block);
        // 先删除旧的 proxy 条目，再追加新的
        let update_env_cmd = format!(
            r#"sed -i -E '/^https?_proxy|^HTTP_PROXY|^HTTPS_PROXY|^ALL_PROXY|^no_proxy|^NO_PROXY/Id' /etc/environment 2>/dev/null; \
               echo '{env_b64}' | base64 -d >> /etc/environment"#
        );
        run_wsl_root_ok(&update_env_cmd)?;
    }

    Ok(format!("WSL 代理已配置（端口 {port} → {win_host}），新开终端自动生效"))
}

/// 读取 WSL 当前代理配置状态，并检测 /etc/environment 中的 IP 是否已过时
/// 4 项检测合并为 1 次 WSL 调用，降低延迟约 75%
#[tauri::command]
pub fn get_wsl_proxy_status() -> Result<serde_json::Value, String> {
    // 单次调用输出 4 段，以 __SEP1__ ~ __SEP3__ 分隔：
    //   段0: profile.d 脚本内容（缺失则输出 __MISSING__）
    //   段1: /etc/environment 中的 http_proxy 主机 IP
    //   段2: 当前网关 IP
    //   段3: bash.bashrc 是否包含 source 行（yes/no）
    let output = run_wsl_root(
        r#"if [ -f /etc/profile.d/v2rayn-proxy.sh ]; then cat /etc/profile.d/v2rayn-proxy.sh; else echo __MISSING__; fi; \
           echo __SEP1__; \
           grep -i '^http_proxy=' /etc/environment 2>/dev/null | head -1 | sed 's|.*://||;s|:.*||'; \
           echo __SEP2__; \
           ip route 2>/dev/null | grep default | awk '{print $3}' | head -1; \
           echo __SEP3__; \
           grep -qF 'v2rayn-proxy.sh' /etc/bash.bashrc 2>/dev/null && echo yes || echo no"#
    ).map_err(|_| "WSL 不可用".to_string())?;

    let raw = String::from_utf8_lossy(&output.stdout).to_string();
    // 手动按分隔符切割
    let seg: Vec<&str> = {
        let mut v = Vec::new();
        let mut rest = raw.as_str();
        // 兼容 Windows 管道可能插入的 \r\n
        for sep in &["__SEP1__\r\n", "__SEP1__\n", "__SEP2__\r\n", "__SEP2__\n", "__SEP3__\r\n", "__SEP3__\n"]
            .chunks(2)
            .map(|pair| if raw.contains(pair[0]) { pair[0] } else { pair[1] })
            .collect::<Vec<_>>()
        {
            if let Some(pos) = rest.find(sep) {
                v.push(rest[..pos].trim());
                rest = &rest[pos + sep.len()..];
            } else {
                v.push(rest.trim());
                rest = "";
            }
        }
        v.push(rest.trim());
        v
    };

    let script_content = seg.first().copied().unwrap_or("");
    if script_content.is_empty() || script_content == "__MISSING__" {
        return Ok(serde_json::json!({ "configured": false }));
    }

    // 从注释行提取端口 "# ... (port NNNN)"
    let port: Option<u16> = script_content.lines()
        .find(|l| l.contains("(port "))
        .and_then(|l| l.split("(port ").nth(1))
        .and_then(|s| s.trim_end_matches(')').trim().parse().ok());

    let configured_ip = seg.get(1).copied().unwrap_or("").to_string();
    let configured_ip = if is_valid_ip(&configured_ip) { Some(configured_ip) } else { None };

    let current_ip = seg.get(2).copied().unwrap_or("").to_string();
    let current_ip = if is_valid_ip(&current_ip) { Some(current_ip) } else { None };

    // 若无法提取 configured_ip（如 /etc/environment 被手动修改），保守地标为 stale
    let stale = match (&configured_ip, &current_ip) {
        (Some(a), Some(b)) => a != b,
        (None, Some(_)) => true,  // 无法确认，提示重新同步
        _ => false,
    };

    let bashrc_ok = seg.get(3).copied().unwrap_or("no") == "yes";

    Ok(serde_json::json!({
        "configured": true,
        "port": port,
        "stale": stale,
        "configured_ip": configured_ip,
        "current_ip": current_ip,
        "bashrc_ok": bashrc_ok,
    }))
}

/// 清除 WSL 代理配置
#[tauri::command]
pub fn clear_wsl_proxy() -> Result<(), String> {
    run_wsl_root_ok("rm -f /etc/profile.d/v2rayn-proxy.sh")?;
    run_wsl_root_ok(r#"sed -i '/v2rayn-proxy\.sh/d' /etc/bash.bashrc 2>/dev/null; true"#)?;
    // 同时清理 WSL 默认登录用户的 ~/.bashrc
    run_wsl_root_ok(
        r#"_DEFAULT_USER=$(grep -oP '(?<=default=)\S+' /etc/wsl.conf 2>/dev/null | head -1); \
           [ -z "$_DEFAULT_USER" ] && _DEFAULT_USER=$(getent passwd 1000 | cut -d: -f1); \
           _USER_HOME=$(getent passwd "$_DEFAULT_USER" 2>/dev/null | cut -d: -f6); \
           [ -n "$_USER_HOME" ] && sed -i '/v2rayn-proxy\.sh/d' "$_USER_HOME/.bashrc" 2>/dev/null; true"#
    )?;
    run_wsl_root_ok(r#"sed -i -E '/^https?_proxy|^HTTP_PROXY|^HTTPS_PROXY|^ALL_PROXY|^no_proxy|^NO_PROXY/Id' /etc/environment 2>/dev/null; true"#)?;
    Ok(())
}

/// 测试 WSL 侧出口 IP（通过 v2rayn-proxy.sh 代理）
#[tauri::command]
pub fn test_wsl_proxy_ip() -> Result<String, String> {
    let output = run_wsl_root(
        "source /etc/profile.d/v2rayn-proxy.sh 2>/dev/null && \
         curl -s --max-time 10 --proxy \"$http_proxy\" https://api.ipify.org 2>&1"
    ).map_err(|_| "WSL 不可用".to_string())?;
    let ip = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if !is_valid_ip(&ip) {
        let hint = if ip.is_empty() { "无输出".to_string() }
                   else { ip.lines().next().unwrap_or(&ip).chars().take(80).collect() };
        Err(format!("获取失败（{hint}），请确认代理已配置且节点可用"))
    } else {
        Ok(ip)
    }
}

/// 测试 Windows 侧出口 IP（通过系统代理）
#[tauri::command]
pub fn test_win_proxy_ip() -> Result<String, String> {
    // 1. 读取系统代理地址
    let reg_out = crate::silent_cmd("powershell")
        .args(["-NonInteractive", "-NoProfile", "-Command",
               r#"(Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -ErrorAction SilentlyContinue).ProxyServer"#])
        .output()
        .map_err(|e| format!("读取系统代理失败: {e}"))?;
    let proxy_server = String::from_utf8_lossy(&reg_out.stdout).trim().to_string();
    if proxy_server.is_empty() {
        return Err("Windows 未配置系统代理（v2rayN 可能未启动）".to_string());
    }

    // 2. 用 curl.exe 通过代理获取 IP
    let output = crate::silent_cmd("curl.exe")
        .args([
            "--proxy", &format!("http://{}", proxy_server),
            "-s", "--max-time", "10",
            "https://api.ipify.org",
        ])
        .output()
        .map_err(|e| format!("curl.exe 执行失败: {e}"))?;
    let ip = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if !is_valid_ip(&ip) {
        let hint = if ip.is_empty() { "无输出".to_string() }
                   else { ip.lines().next().unwrap_or(&ip).chars().take(80).collect() };
        Err(format!("获取失败（代理 {proxy_server}，{hint}）"))
    } else {
        Ok(ip)
    }
}

/// 获取模式切换历史
#[tauri::command]
pub fn get_mode_log(limit: Option<i64>) -> Result<Vec<ModeLogEntry>, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;
    let n = limit.unwrap_or(20);
    let mut stmt = conn
        .prepare(
            "SELECT id, mode, created_at FROM network_mode_log ORDER BY created_at DESC LIMIT ?1",
        )
        .map_err(|e| e.to_string())?;
    let rows = stmt
        .query_map(params![n], |row| {
            Ok(ModeLogEntry {
                id:         row.get(0)?,
                mode:       row.get(1)?,
                created_at: row.get(2)?,
            })
        })
        .map_err(|e| e.to_string())?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.to_string())?;
    Ok(rows)
}
