use crate::db::pool;
use rusqlite::params;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

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

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct EnvToolItem {
    pub id: String,
    pub project_id: String,
    pub layer: u8,
    pub name: String,
    pub source: Option<String>,
    pub version: Option<String>,
    pub note: Option<String>,
}

#[derive(Debug, Deserialize)]
pub struct SaveEnvToolInput {
    pub id: String,
    pub layer: u8,
    pub name: String,
    pub source: Option<String>,
    pub version: Option<String>,
    pub note: Option<String>,
}

// ── 内部扫描结构 ──────────────────────────────────────────────────────────────

struct RawItem {
    source: Option<String>,
    version: Option<String>,
    note: Option<String>,
}

// Key = (layer, name)，用于去重
type DetectedMap = BTreeMap<(u8, String), RawItem>;

/// 插入（已存在则不覆盖）
fn insert(
    map: &mut DetectedMap,
    layer: u8,
    name: &str,
    source: Option<&str>,
    version: Option<String>,
    note: Option<&str>,
) {
    map.entry((layer, name.to_string())).or_insert(RawItem {
        source: source.map(|s| s.to_string()),
        version,
        note: note.map(|s| s.to_string()),
    });
}

/// 插入或补充（已存在时填充空字段）
fn upsert(
    map: &mut DetectedMap,
    layer: u8,
    name: &str,
    source: Option<&str>,
    version: Option<String>,
    note: Option<&str>,
) {
    let entry = map
        .entry((layer, name.to_string()))
        .or_insert(RawItem { source: None, version: None, note: None });
    if source.is_some() && entry.source.is_none() {
        entry.source = source.map(|s| s.to_string());
    }
    if version.is_some() && entry.version.is_none() {
        entry.version = version;
    }
    if note.is_some() && entry.note.is_none() {
        entry.note = note.map(|s| s.to_string());
    }
}

// ── 文件读取工具 ──────────────────────────────────────────────────────────────

fn read_first_line(path: &Path) -> Option<String> {
    std::fs::read_to_string(path)
        .ok()
        .and_then(|s| s.lines().next().map(|l| l.trim().to_string()))
        .filter(|s| !s.is_empty())
}

fn read_all(path: &Path) -> Option<String> {
    std::fs::read_to_string(path).ok()
}

// ── 目录扫描核心 ──────────────────────────────────────────────────────────────

fn scan_dir_files(dir: &Path) -> DetectedMap {
    let mut map: DetectedMap = BTreeMap::new();

    // ── Node.js 生态 ──────────────────────────────────────────────────────────

    // .nvmrc → nvm(layer0) + Node.js(layer1, source=nvm)
    let nvmrc = dir.join(".nvmrc");
    if nvmrc.exists() {
        let ver = read_first_line(&nvmrc);
        insert(&mut map, 0, "nvm", None, None, Some(".nvmrc"));
        upsert(&mut map, 1, "Node.js", Some("nvm"), ver, None);
    }

    // .node-version → fnm(layer0) + Node.js(layer1, source=fnm)
    let node_version_file = dir.join(".node-version");
    if node_version_file.exists() {
        let ver = read_first_line(&node_version_file);
        insert(&mut map, 0, "fnm", None, None, Some(".node-version"));
        upsert(&mut map, 1, "Node.js", Some("fnm"), ver, None);
    }

    // package.json → Node.js(layer1)，检查 volta 字段
    let pkg_json = dir.join("package.json");
    if pkg_json.exists() {
        upsert(&mut map, 1, "Node.js", None, None, None);
        if let Some(content) = read_all(&pkg_json) {
            if content.contains("\"volta\"") {
                insert(&mut map, 0, "Volta", None, None, Some("package.json#volta"));
                let key = (1u8, "Node.js".to_string());
                if let Some(item) = map.get_mut(&key) {
                    if item.source.is_none() {
                        item.source = Some("volta".to_string());
                    }
                }
            }
        }
    }

    // 包管理器 lock 文件 → layer2
    if dir.join("pnpm-lock.yaml").exists() {
        insert(&mut map, 2, "pnpm", Some("direct"), None, None);
    }
    if dir.join("package-lock.json").exists() {
        insert(&mut map, 2, "npm", Some("direct"), None, None);
    }
    if dir.join("yarn.lock").exists() {
        insert(&mut map, 2, "Yarn", Some("direct"), None, None);
    }
    if dir.join("bun.lockb").exists() || dir.join("bun.lock").exists() {
        insert(&mut map, 2, "Bun", Some("direct"), None, None);
    }

    // tsconfig.json → TypeScript(layer3, local)
    if dir.join("tsconfig.json").exists() {
        insert(&mut map, 3, "TypeScript", Some("local"), None, Some("tsconfig.json"));
    }

    // ── Rust 生态 ─────────────────────────────────────────────────────────────

    let toolchain_toml = dir.join("rust-toolchain.toml");
    let toolchain_plain = dir.join("rust-toolchain");
    if toolchain_toml.exists() || toolchain_plain.exists() {
        let file = if toolchain_toml.exists() { toolchain_toml } else { toolchain_plain };
        let channel = read_all(&file).and_then(|content| {
            content
                .lines()
                .find(|l| l.trim_start().starts_with("channel"))
                .and_then(|l| l.splitn(2, '=').nth(1).map(|v| v.trim().trim_matches('"').to_string()))
        });
        insert(&mut map, 0, "rustup", None, channel.clone(), Some("rust-toolchain.toml"));
        upsert(&mut map, 1, "Rust", Some("rustup"), channel, None);
        upsert(&mut map, 2, "Cargo", Some("direct"), None, None);
    } else if dir.join("Cargo.toml").exists() {
        upsert(&mut map, 1, "Rust", Some("rustup"), None, None);
        insert(&mut map, 2, "Cargo", Some("direct"), None, None);
    }

    // ── Go 生态 ───────────────────────────────────────────────────────────────

    let go_mod = dir.join("go.mod");
    if go_mod.exists() {
        let ver = read_all(&go_mod).and_then(|content| {
            content
                .lines()
                .find(|l| l.trim_start().starts_with("go "))
                .map(|l| l.trim().to_string())
        });
        insert(&mut map, 1, "Go", Some("direct"), ver, None);
    }

    // ── Python 生态 ───────────────────────────────────────────────────────────

    let python_version_file = dir.join(".python-version");
    if python_version_file.exists() {
        let ver = read_first_line(&python_version_file);
        insert(&mut map, 0, "pyenv", None, None, Some(".python-version"));
        upsert(&mut map, 1, "Python", Some("pyenv"), ver, None);
    }

    if dir.join("poetry.lock").exists() {
        upsert(&mut map, 1, "Python", None, None, None);
        insert(&mut map, 2, "poetry", Some("direct"), None, None);
    }

    if dir.join("Pipfile").exists() {
        upsert(&mut map, 1, "Python", None, None, None);
        insert(&mut map, 2, "pipenv", Some("direct"), None, None);
    }

    if dir.join("requirements.txt").exists() || dir.join("setup.py").exists() {
        upsert(&mut map, 1, "Python", Some("direct"), None, None);
    }

    if dir.join("pyproject.toml").exists() {
        upsert(&mut map, 1, "Python", None, None, None);
        if let Some(content) = read_all(&dir.join("pyproject.toml")) {
            if content.contains("[tool.poetry]") {
                upsert(&mut map, 2, "poetry", Some("direct"), None, None);
            }
        }
    }

    if dir.join(".venv").exists() || dir.join("venv").exists() {
        upsert(&mut map, 1, "Python", None, None, Some("venv"));
    }

    // ── Java 生态 ─────────────────────────────────────────────────────────────

    if dir.join("pom.xml").exists() {
        insert(&mut map, 1, "Java", None, None, Some("pom.xml"));
        insert(&mut map, 2, "Maven", Some("direct"), None, None);
    }
    if dir.join("build.gradle").exists() || dir.join("build.gradle.kts").exists() {
        upsert(&mut map, 1, "Java", None, None, Some("build.gradle"));
        insert(&mut map, 2, "Gradle", Some("direct"), None, None);
    }

    // ── Ruby 生态 ─────────────────────────────────────────────────────────────

    let ruby_version_file = dir.join(".ruby-version");
    if ruby_version_file.exists() {
        let ver = read_first_line(&ruby_version_file);
        insert(&mut map, 0, "rbenv", None, ver.clone(), Some(".ruby-version"));
        upsert(&mut map, 1, "Ruby", Some("rbenv"), ver, None);
    } else if dir.join("Gemfile").exists() {
        insert(&mut map, 1, "Ruby", Some("direct"), None, None);
    }

    if dir.join("Gemfile").exists() {
        insert(&mut map, 2, "Bundler", Some("direct"), None, None);
    }

    // ── 基础设施工具 ──────────────────────────────────────────────────────────

    if dir.join(".gitignore").exists() || dir.join(".git").exists() {
        insert(&mut map, 2, "Git", Some("direct"), None, None);
    }

    if dir.join("Dockerfile").exists()
        || dir.join("docker-compose.yml").exists()
        || dir.join("docker-compose.yaml").exists()
        || dir.join("compose.yml").exists()
    {
        insert(&mut map, 2, "Docker", Some("direct"), None, None);
    }

    // Claude CLI
    if dir.join(".claude").exists() {
        insert(&mut map, 2, "Claude CLI", Some("direct"), None, Some(".claude/"));
    }

    map
}

// ── WSL 路径转换 ──────────────────────────────────────────────────────────────

fn get_wsl_distro() -> String {
    pool()
        .get()
        .ok()
        .and_then(|c| {
            c.query_row(
                "SELECT value FROM settings WHERE key = 'wsl_distro'",
                [],
                |row| row.get::<_, String>(0),
            )
            .ok()
        })
        .unwrap_or_else(|| "Ubuntu".to_string())
}

fn wsl_to_unc(wsl_path: &str, distro: &str) -> PathBuf {
    // /home/user/project → \\wsl$\Ubuntu\home\user\project
    let stripped = wsl_path.trim_start_matches('/');
    PathBuf::from(format!(
        "\\\\wsl$\\{}\\{}",
        distro,
        stripped.replace('/', "\\")
    ))
}

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

/// 扫描项目目录的环境工具（基于配置文件指纹）
#[tauri::command]
pub fn scan_project_env(project_id: String, env: String) -> Result<Vec<EnvToolItem>, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;
    let (wsl_path, win_path): (Option<String>, Option<String>) = conn
        .query_row(
            "SELECT wsl_path, win_path FROM project_workspaces WHERE id = ?1",
            params![project_id],
            |row| Ok((row.get(0)?, row.get(1)?)),
        )
        .map_err(|_| format!("Project not found: {project_id}"))?;

    let dir: PathBuf = match env.as_str() {
        "wsl" => {
            let path = wsl_path.ok_or("该项目没有配置 WSL 路径")?;
            let distro = get_wsl_distro();
            wsl_to_unc(&path, &distro)
        }
        _ => PathBuf::from(win_path.ok_or("该项目没有配置 Windows 路径")?),
    };

    if !dir.exists() {
        return Err(format!("目录不存在: {}", dir.display()));
    }

    let detected = scan_dir_files(&dir);
    let items: Vec<EnvToolItem> = detected
        .into_iter()
        .enumerate()
        .map(|(i, ((layer, name), raw))| EnvToolItem {
            id: format!("{}-{}-{}-{}", project_id, env, layer, i),
            project_id: project_id.clone(),
            layer,
            name,
            source: raw.source,
            version: raw.version,
            note: raw.note,
        })
        .collect();

    Ok(items)
}

/// 获取已保存的项目环境工具
#[tauri::command]
pub fn get_project_env_tools(project_id: String) -> Result<Vec<EnvToolItem>, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare(
            "SELECT id, project_id, layer, name, source, version, note
             FROM project_env_tools
             WHERE project_id = ?1
             ORDER BY layer, name",
        )
        .map_err(|e| e.to_string())?;
    let rows = stmt
        .query_map(params![project_id], |row| {
            Ok(EnvToolItem {
                id:         row.get(0)?,
                project_id: row.get(1)?,
                layer:      row.get(2)?,
                name:       row.get(3)?,
                source:     row.get(4)?,
                version:    row.get(5)?,
                note:       row.get(6)?,
            })
        })
        .map_err(|e| e.to_string())?
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| e.to_string())?;
    Ok(rows)
}

/// 保存项目环境工具（先清空再写入）
#[tauri::command]
pub fn save_project_env_tools(
    project_id: String,
    items: Vec<SaveEnvToolInput>,
) -> Result<(), String> {
    let conn = pool().get().map_err(|e| e.to_string())?;
    conn.execute(
        "DELETE FROM project_env_tools WHERE project_id = ?1",
        params![project_id],
    )
    .map_err(|e| e.to_string())?;
    for item in items {
        conn.execute(
            "INSERT INTO project_env_tools (id, project_id, layer, name, source, version, note, updated_at)
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, datetime('now'))",
            params![
                item.id, project_id, item.layer, item.name,
                item.source, item.version, item.note,
            ],
        )
        .map_err(|e| e.to_string())?;
    }
    Ok(())
}
