use crate::db::{log_event, pool};
use rusqlite::params;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::HashMap;

/// 全局 key（不按空间隔离，属于整个应用/机器级别配置）
const GLOBAL_KEYS: &[&str] = &[
    "wsl_distro",       // 机器级别，与空间无关
    "wsl_open_mode",    // WSL 编辑器启动方式
    "editors", "active_editors", "active_editor",
    "v2rayn_db_path",   // v2rayN 数据库路径
    "v2raya_username",  // v2rayA 登录用户名
    "v2raya_password",  // v2rayA 登录密码
    "mcp_port",         // MCP Server 监听端口（全局，与空间无关）
];

/// 获取 settings：
/// - 有 space_id：返回该空间的 space_settings + 全局 key（GLOBAL_KEYS）
/// - 无 space_id：返回全局 settings 表
#[tauri::command]
pub fn get_settings(space_id: Option<String>) -> Result<HashMap<String, String>, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;

    if let Some(ref sid) = space_id {
        // 1. 只读当前空间的 space_settings
        let mut map: HashMap<String, String> = {
            let mut stmt = conn
                .prepare("SELECT key, value FROM space_settings WHERE space_id = ?1")
                .map_err(|e| e.to_string())?;
            let x = stmt
                .query_map(params![sid], |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)))
                .map_err(|e| e.to_string())?
                .collect::<Result<HashMap<_, _>, _>>()
                .map_err(|e| e.to_string())?; x
        };
        // 2. 补充全局 key（应用级，非空间相关）
        for key in GLOBAL_KEYS {
            if let Ok(val) = conn.query_row(
                "SELECT value FROM settings WHERE key = ?1",
                params![key],
                |row| row.get::<_, String>(0),
            ) {
                map.insert(key.to_string(), val);
            }
        }
        Ok(map)
    } else {
        // 无空间上下文：返回全局表（应用设置弹窗使用）
        let mut stmt = conn.prepare("SELECT key, value FROM settings").map_err(|e| e.to_string())?;
        let map = stmt
            .query_map([], |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)))
            .map_err(|e| e.to_string())?
            .collect::<Result<HashMap<_, _>, _>>()
            .map_err(|e| e.to_string())?;
        Ok(map)
    }
}

/// 更新 settings：全局 key 写入 settings 表；其余写入 space_settings 表
#[tauri::command]
pub fn update_settings(settings: HashMap<String, String>, space_id: Option<String>) -> Result<(), String> {
    let conn = pool().get().map_err(|e| e.to_string())?;
    let mut mcp_port_changed = false;
    for (k, v) in &settings {
        if k == "mcp_port" {
            mcp_port_changed = true;
        }
        if GLOBAL_KEYS.contains(&k.as_str()) || space_id.is_none() {
            conn.execute(
                "INSERT OR REPLACE INTO settings (key, value) VALUES (?1, ?2)",
                params![k, v],
            )
            .map_err(|e| e.to_string())?;
        } else {
            conn.execute(
                "INSERT OR REPLACE INTO space_settings (space_id, key, value) VALUES (?1, ?2, ?3)",
                params![space_id, k, v],
            )
            .map_err(|e| e.to_string())?;
        }
    }
    // 端口变更后重新注入所有产品组成员的 MCP 配置
    if mcp_port_changed {
        let errors = crate::mcp_inject::reinject_all_mcp();
        for e in &errors {
            log_event("mcp_inject", "error", &format!("端口变更重注入失败: {e}"), None);
        }
    }
    Ok(())
}

/// 检测系统中已安装的常见编辑器，返回 [{name, cmd, path, found}]
#[tauri::command]
pub fn detect_editors() -> Result<Vec<Value>, String> {
    // (显示名, 命令名, 常见安装路径列表)
    let candidates: &[(&str, &str, &[&str])] = &[
        ("Cursor", "cursor", &[
            r"%LOCALAPPDATA%\Programs\cursor\Cursor.exe",
            r"%USERPROFILE%\AppData\Local\Programs\cursor\Cursor.exe",
        ]),
        ("VS Code", "code", &[
            r"%LOCALAPPDATA%\Programs\Microsoft VS Code\Code.exe",
            r"C:\Program Files\Microsoft VS Code\Code.exe",
        ]),
        ("VS Code Insiders", "code-insiders", &[
            r"%LOCALAPPDATA%\Programs\Microsoft VS Code Insiders\Code - Insiders.exe",
        ]),
        ("Windsurf", "windsurf", &[
            r"%LOCALAPPDATA%\Programs\windsurf\Windsurf.exe",
        ]),
    ];

    let mut results = Vec::new();

    for (name, cmd, fallback_paths) in candidates {
        // 先用 where 命令查 PATH
        let where_out = crate::silent_cmd("cmd")
            .args(["/c", &format!("where {cmd} 2>nul")])
            .output()
            .ok();

        let path_from_where = where_out
            .as_ref()
            .filter(|o| o.status.success())
            .and_then(|o| String::from_utf8(o.stdout.clone()).ok())
            .and_then(|s| s.lines().next().map(|l| l.trim().to_string()))
            .filter(|s| !s.is_empty());

        // 如果 where 找不到，尝试常见安装路径（展开 %LOCALAPPDATA% 等）
        let resolved_path = path_from_where.or_else(|| {
            for &p in *fallback_paths {
                let expanded = expand_env(p);
                if std::path::Path::new(&expanded).exists() {
                    return Some(expanded);
                }
            }
            None
        });

        let found = resolved_path.is_some();
        let mut obj = Map::new();
        obj.insert("name".into(), Value::String(name.to_string()));
        obj.insert("cmd".into(), Value::String(cmd.to_string()));
        obj.insert("path".into(), match &resolved_path {
            Some(p) => Value::String(p.clone()),
            None => Value::Null,
        });
        obj.insert("found".into(), Value::Bool(found));
        results.push(Value::Object(obj));
    }

    Ok(results)
}

/// 搜索指定命令名对应的可执行路径
#[tauri::command]
pub fn search_editor_path(cmd: String) -> Result<Option<String>, String> {
    // 先 where
    let where_out = crate::silent_cmd("cmd")
        .args(["/c", &format!("where {cmd} 2>nul")])
        .output()
        .map_err(|e| e.to_string())?;

    if where_out.status.success() {
        if let Ok(s) = String::from_utf8(where_out.stdout) {
            if let Some(line) = s.lines().next() {
                let p = line.trim().to_string();
                if !p.is_empty() {
                    return Ok(Some(p));
                }
            }
        }
    }

    // 再搜 Program Files / LocalAppData
    let search_dirs = [
        std::env::var("LOCALAPPDATA").unwrap_or_default(),
        std::env::var("PROGRAMFILES").unwrap_or_default(),
        std::env::var("PROGRAMFILES(X86)").unwrap_or_default(),
    ];

    let extensions = ["exe", "cmd"];
    for dir in &search_dirs {
        if dir.is_empty() { continue; }
        for ext in &extensions {
            let candidate = format!("{dir}\\{cmd}.{ext}");
            if std::path::Path::new(&candidate).exists() {
                return Ok(Some(candidate));
            }
            // 一层子目录
            if let Ok(entries) = std::fs::read_dir(dir) {
                for entry in entries.flatten() {
                    let sub = entry.path().join(format!("{cmd}.{ext}"));
                    if sub.exists() {
                        return Ok(Some(sub.to_string_lossy().into_owned()));
                    }
                }
            }
        }
    }

    Ok(None)
}

/// 检测 MCP Server 是否真正在监听（实际 HTTP ping）
#[tauri::command]
pub fn check_mcp_health() -> Result<serde_json::Value, String> {
    let port = crate::mcp_server::read_port();
    let url = format!("http://localhost:{}/health", port);
    let start = std::time::Instant::now();
    match ureq::builder()
        .timeout(std::time::Duration::from_secs(2))
        .build()
        .get(&url)
        .call()
    {
        Ok(_) => {
            let latency_ms = start.elapsed().as_millis() as u64;
            Ok(serde_json::json!({ "ok": true, "latency_ms": latency_ms, "port": port }))
        }
        Err(e) => Ok(serde_json::json!({ "ok": false, "error": e.to_string(), "port": port })),
    }
}

// ── 健康检查 ──────────────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct HealthItem {
    pub id: String,
    pub name: String,
    /// "ok" | "warn" | "error" | "skip"
    pub status: String,
    pub detail: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct HealthReport {
    pub checked_at: String,
    pub items: Vec<HealthItem>,
    pub quality: QualityMetrics,
}

fn err_item(id: &str, name: &str, detail: &str) -> HealthItem {
    HealthItem { id: id.into(), name: name.into(), status: "error".into(), detail: Some(detail.into()) }
}

fn skip_item(id: &str, name: &str, detail: &str) -> HealthItem {
    HealthItem { id: id.into(), name: name.into(), status: "skip".into(), detail: Some(detail.into()) }
}

/// 检测 MCP Server 是否响应
fn check_mcp() -> HealthItem {
    let port = crate::mcp_server::read_port();
    let url = format!("http://localhost:{}/health", port);
    let start = std::time::Instant::now();
    match ureq::builder().timeout(std::time::Duration::from_secs(5)).build().get(&url).call() {
        Ok(_) => HealthItem {
            id: "mcp".into(), name: "MCP Server".into(), status: "ok".into(),
            detail: Some(format!("端口 {} 响应正常，延迟 {}ms", port, start.elapsed().as_millis())),
        },
        Err(e) => HealthItem {
            id: "mcp".into(), name: "MCP Server".into(), status: "error".into(),
            detail: Some(format!("端口 {} 无响应: {}", port, e)),
        },
    }
}

/// 检测已注册仓库的本地 .git 目录是否存在（仅检测关联了 git_repos 的 workspace）
fn check_git_paths() -> HealthItem {
    let result: Result<HealthItem, String> = (|| {
        let conn = pool().get().map_err(|e| e.to_string())?;
        let mut stmt = conn.prepare(
            "SELECT win_path FROM project_workspaces WHERE win_path IS NOT NULL AND win_path != '' AND repo_id IS NOT NULL"
        ).map_err(|e| e.to_string())?;
        let paths: Vec<String> = stmt
            .query_map([], |row| row.get::<_, String>(0))
            .map_err(|e| e.to_string())?
            .flatten().collect();

        if paths.is_empty() {
            return Ok(skip_item("git_paths", "Git 仓库路径", "暂无本地路径"));
        }
        let total = paths.len();
        let invalid: Vec<&str> = paths.iter()
            .filter(|p| !std::path::Path::new(p).join(".git").exists())
            .map(|p| p.as_str()).collect();
        if invalid.is_empty() {
            Ok(HealthItem { id: "git_paths".into(), name: "Git 仓库路径".into(), status: "ok".into(),
                detail: Some(format!("全部 {} 个仓库路径有效", total)) })
        } else {
            let status = if invalid.len() == total { "error" } else { "warn" };
            Ok(HealthItem { id: "git_paths".into(), name: "Git 仓库路径".into(), status: status.into(),
                detail: Some(format!("{}/{} 个 .git 目录不存在", invalid.len(), total)) })
        }
    })();
    result.unwrap_or_else(|e| err_item("git_paths", "Git 仓库路径", &e))
}

/// 检测项目本地路径（win_path）是否存在于文件系统
fn check_project_paths() -> HealthItem {
    let result: Result<HealthItem, String> = (|| {
        let conn = pool().get().map_err(|e| e.to_string())?;
        let mut stmt = conn.prepare(
            "SELECT win_path FROM project_workspaces WHERE win_path IS NOT NULL AND win_path != ''"
        ).map_err(|e| e.to_string())?;
        let paths: Vec<String> = stmt
            .query_map([], |row| row.get::<_, String>(0))
            .map_err(|e| e.to_string())?
            .flatten().collect();

        if paths.is_empty() {
            return Ok(skip_item("project_paths", "项目本地路径", "暂无配置路径的项目"));
        }
        let total = paths.len();
        let missing = paths.iter().filter(|p| !std::path::Path::new(p).exists()).count();
        if missing == 0 {
            Ok(HealthItem { id: "project_paths".into(), name: "项目本地路径".into(), status: "ok".into(),
                detail: Some(format!("全部 {} 个路径存在", total)) })
        } else {
            Ok(HealthItem { id: "project_paths".into(), name: "项目本地路径".into(), status: "warn".into(),
                detail: Some(format!("{}/{} 个路径不存在（可能已移动或删除）", missing, total)) })
        }
    })();
    result.unwrap_or_else(|e| err_item("project_paths", "项目本地路径", &e))
}

/// 检测活跃编辑器路径是否可用
/// 存储结构：editors = [{id,name,path}]，active_editors = ["id1","id2"]
fn check_editor() -> HealthItem {
    let result: Result<HealthItem, String> = (|| {
        let conn = pool().get().map_err(|e| e.to_string())?;

        let get_val = |key: &str| -> Option<String> {
            conn.query_row(
                "SELECT value FROM settings WHERE key = ?1",
                rusqlite::params![key], |row| row.get(0),
            ).ok()
        };

        // 读取编辑器列表
        let editors_raw = match get_val("editors") {
            Some(s) if !s.trim().is_empty() => s,
            _ => return Ok(skip_item("editor", "编辑器路径", "未配置任何编辑器")),
        };
        let editors: Vec<Value> = serde_json::from_str(&editors_raw)
            .unwrap_or_default();
        if editors.is_empty() {
            return Ok(skip_item("editor", "编辑器路径", "未配置任何编辑器"));
        }

        // 读取活跃编辑器 ID 列表（兼容旧单值字段）
        let active_ids: Vec<String> = if let Some(raw) = get_val("active_editors") {
            serde_json::from_str(&raw).unwrap_or_default()
        } else if let Some(old_id) = get_val("active_editor") {
            if old_id.is_empty() { vec![] } else { vec![old_id] }
        } else {
            vec![]
        };

        if active_ids.is_empty() {
            return Ok(HealthItem { id: "editor".into(), name: "编辑器路径".into(), status: "warn".into(),
                detail: Some("已添加编辑器但未勾选活跃编辑器，请前往设置页勾选至少一个".into()) });
        }

        // 取第一个活跃编辑器检测路径
        let first_id = &active_ids[0];
        let entry = editors.iter().find(|e| e["id"].as_str() == Some(first_id));
        let (name, path) = match entry {
            Some(e) => (
                e["name"].as_str().unwrap_or("编辑器").to_string(),
                e["path"].as_str().unwrap_or("").to_string(),
            ),
            None => return Ok(HealthItem { id: "editor".into(), name: "编辑器路径".into(), status: "warn".into(),
                detail: Some(format!("活跃编辑器 ID `{}` 在编辑器列表中未找到，请重新配置", first_id)) }),
        };

        if path.is_empty() {
            return Ok(HealthItem { id: "editor".into(), name: "编辑器路径".into(), status: "warn".into(),
                detail: Some(format!("{} 路径为空，请在设置页编辑该编辑器条目补全路径", name)) });
        }
        if std::path::Path::new(&path).exists() {
            let extra = if active_ids.len() > 1 {
                format!("（共 {} 个活跃编辑器）", active_ids.len())
            } else {
                String::new()
            };
            Ok(HealthItem { id: "editor".into(), name: "编辑器路径".into(), status: "ok".into(),
                detail: Some(format!("{} 路径有效{}", name, extra)) })
        } else {
            Ok(HealthItem { id: "editor".into(), name: "编辑器路径".into(), status: "error".into(),
                detail: Some(format!("{} 可执行文件不存在: {}，请在设置页更新路径", name, path)) })
        }
    })();
    result.unwrap_or_else(|e| err_item("editor", "编辑器路径", &e))
}

/// 检测 SQLite 读写延迟
fn check_db_latency() -> HealthItem {
    let result: Result<HealthItem, String> = (|| {
        let conn = pool().get().map_err(|e| e.to_string())?;
        let start = std::time::Instant::now();
        conn.execute_batch("
            BEGIN;
            INSERT OR REPLACE INTO settings (key, value) VALUES ('__health_probe__', '1');
            DELETE FROM settings WHERE key = '__health_probe__';
            COMMIT;
        ").map_err(|e| e.to_string())?;
        let ms = start.elapsed().as_millis();
        let status = if ms < 100 { "ok" } else { "warn" };
        Ok(HealthItem { id: "db_latency".into(), name: "数据库延迟".into(), status: status.into(),
            detail: Some(format!("读写延迟 {}ms{}", ms, if ms >= 100 { "（偏高）" } else { "" })) })
    })();
    result.unwrap_or_else(|e| err_item("db_latency", "数据库延迟", &e))
}

// ── 质量指标 ──────────────────────────────────────────────────────────────────

#[derive(Debug, Serialize)]
pub struct DormantProject {
    pub id: String,
    pub name: String,
    pub days_since_push: i64,
}

#[derive(Debug, Serialize)]
pub struct OrphanedProject {
    pub id: String,
    pub name: String,
}

#[derive(Debug, Serialize)]
pub struct QualityMetrics {
    /// last_push_at 超过 30 天且有 repo_url 的项目
    pub dormant_projects: Vec<DormantProject>,
    /// 未加入任何产品组且无 repo_url 的项目
    pub orphaned_projects: Vec<OrphanedProject>,
    /// runtime_logs 近 7 天内有 error/warn 的产品组 id 列表
    pub groups_with_inject_warn: Vec<String>,
    /// win_path 和 wsl_path 均为空的项目数
    pub projects_without_path: i64,
}

impl Default for QualityMetrics {
    fn default() -> Self {
        Self {
            dormant_projects: vec![],
            orphaned_projects: vec![],
            groups_with_inject_warn: vec![],
            projects_without_path: 0,
        }
    }
}

fn collect_quality_metrics() -> QualityMetrics {
    let conn = match pool().get() {
        Ok(c) => c,
        Err(_) => return QualityMetrics::default(),
    };

    // 1. 休眠项目：last_push_at 超 30 天 且 有 repo_url
    let dormant_projects: Vec<DormantProject> = {
        let stmt = conn.prepare(
            "SELECT pw.id, pp.name,
                    CAST(julianday('now') - julianday(pw.last_push_at) AS INTEGER) as days
             FROM project_workspaces pw
             JOIN project_profiles pp ON pp.id = pw.profile_id
             WHERE (pp.repo_url IS NOT NULL AND pp.repo_url != '')
               AND pw.last_push_at IS NOT NULL AND pw.last_push_at != ''
               AND julianday('now') - julianday(pw.last_push_at) > 30
             ORDER BY days DESC"
        );
        match stmt {
            Err(_) => vec![],
            Ok(mut stmt) => stmt
                .query_map([], |row| {
                    Ok(DormantProject {
                        id: row.get(0)?,
                        name: row.get(1)?,
                        days_since_push: row.get(2)?,
                    })
                })
                .map(|rows| rows.flatten().collect())
                .unwrap_or_default(),
        }
    };

    // 2. 孤立项目：无 repo_url 且不在任何产品组
    let orphaned_projects: Vec<OrphanedProject> = {
        let stmt = conn.prepare(
            "SELECT pw.id, pp.name
             FROM project_workspaces pw
             JOIN project_profiles pp ON pp.id = pw.profile_id
             WHERE (pp.repo_url IS NULL OR pp.repo_url = '')
               AND pw.id NOT IN (SELECT project_id FROM project_group_map)"
        );
        match stmt {
            Err(_) => vec![],
            Ok(mut stmt) => stmt
                .query_map([], |row| {
                    Ok(OrphanedProject { id: row.get(0)?, name: row.get(1)? })
                })
                .map(|rows| rows.flatten().collect())
                .unwrap_or_default(),
        }
    };

    // 3. 近 7 天有注入 error/warn 的产品组（解析 context JSON 取 group_id）
    let groups_with_inject_warn: Vec<String> = {
        let stmt = conn.prepare(
            "SELECT DISTINCT context FROM runtime_logs
             WHERE category = 'mcp_inject'
               AND level IN ('error', 'warn')
               AND ts > datetime('now', '-7 days')
               AND context IS NOT NULL"
        );
        let contexts: Vec<String> = match stmt {
            Err(_) => vec![],
            Ok(mut stmt) => stmt
                .query_map([], |row| row.get::<_, String>(0))
                .map(|rows| rows.flatten().collect())
                .unwrap_or_default(),
        };

        let mut group_ids: Vec<String> = contexts.iter()
            .filter_map(|ctx| {
                serde_json::from_str::<Value>(ctx).ok()
                    .and_then(|v| v["group_id"].as_str().map(|s| s.to_string()))
            })
            .collect();
        group_ids.sort();
        group_ids.dedup();
        group_ids
    };

    // 4. 无路径项目数
    let projects_without_path: i64 = conn.query_row(
        "SELECT COUNT(*) FROM project_workspaces
         WHERE (win_path IS NULL OR win_path = '')
           AND (wsl_path IS NULL OR wsl_path = '')",
        [], |row| row.get(0),
    ).unwrap_or(0);

    QualityMetrics { dormant_projects, orphaned_projects, groups_with_inject_warn, projects_without_path }
}

/// 并行执行全部健康检查，返回结构化报告（内部复用入口，mcp_server 也可调用）
pub fn collect_health_report() -> HealthReport {
    use std::thread;
    let checked_at = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S").to_string();

    let h_mcp     = thread::spawn(check_mcp);
    let h_git     = thread::spawn(check_git_paths);
    let h_proj    = thread::spawn(check_project_paths);
    let h_editor  = thread::spawn(check_editor);
    let h_db      = thread::spawn(check_db_latency);
    let h_quality = thread::spawn(collect_quality_metrics);

    let items = vec![
        h_mcp   .join().unwrap_or_else(|_| err_item("mcp",           "MCP Server",    "检测线程异常")),
        h_git   .join().unwrap_or_else(|_| err_item("git_paths",     "Git 仓库路径",   "检测线程异常")),
        h_proj  .join().unwrap_or_else(|_| err_item("project_paths", "项目本地路径",   "检测线程异常")),
        h_editor.join().unwrap_or_else(|_| err_item("editor",        "编辑器路径",    "检测线程异常")),
        h_db    .join().unwrap_or_else(|_| err_item("db_latency",    "数据库延迟",    "检测线程异常")),
    ];
    let quality = h_quality.join().unwrap_or_default();

    HealthReport { checked_at, items, quality }
}

#[tauri::command]
pub fn run_health_check() -> Result<HealthReport, String> {
    Ok(collect_health_report())
}

fn expand_env(s: &str) -> String {
    let mut result = s.to_string();
    for (key, val) in std::env::vars() {
        result = result.replace(&format!("%{key}%"), &val);
    }
    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn global_keys_contains_infra() {
        assert!(GLOBAL_KEYS.contains(&"mcp_port"));
        assert!(GLOBAL_KEYS.contains(&"wsl_distro"));
    }

    #[test]
    fn space_specific_keys_not_in_global() {
        // editor / dev_mode 是空间维度的配置，不应进入全局表
        assert!(!GLOBAL_KEYS.contains(&"editor"));
        assert!(!GLOBAL_KEYS.contains(&"dev_mode"));
    }

    #[test]
    fn settings_routing_via_db() {
        crate::db::conn_with_schema(); // 仅验证 schema 可正常建立，不依赖全局 pool
    }
}
