use crate::db;
use crate::models::{DirEntry, SourceCategory, SourceProject, SourceVersion};
use std::fs;
use std::path::Path;
use uuid::Uuid;

// ── 分类 CRUD ────────────────────────────────────────────────────────────────

#[tauri::command]
pub fn get_source_categories() -> Result<Vec<SourceCategory>, String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare("SELECT id, name, icon, color, sort_order FROM source_categories ORDER BY sort_order, name")
        .map_err(|e| e.to_string())?;
    let rows = stmt
        .query_map([], SourceCategory::from_row)
        .map_err(|e| e.to_string())?;
    rows.map(|r| r.map_err(|e| e.to_string())).collect()
}

#[tauri::command]
pub fn create_source_category(name: String, icon: Option<String>, color: Option<String>) -> Result<SourceCategory, String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    let id = Uuid::new_v4().to_string();
    let icon = icon.unwrap_or_else(|| "📁".into());
    let color = color.unwrap_or_else(|| "#6B7280".into());
    conn.execute(
        "INSERT INTO source_categories (id, name, icon, color) VALUES (?1, ?2, ?3, ?4)",
        rusqlite::params![id, name, icon, color],
    ).map_err(|e| e.to_string())?;
    Ok(SourceCategory { id, name, icon, color, sort_order: 0 })
}

#[tauri::command]
pub fn update_source_category(id: String, name: String, icon: String, color: String) -> Result<(), String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    conn.execute(
        "UPDATE source_categories SET name=?2, icon=?3, color=?4 WHERE id=?1",
        rusqlite::params![id, name, icon, color],
    ).map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
pub fn delete_source_category(id: String) -> Result<(), String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    conn.execute("UPDATE source_projects SET category_id=NULL WHERE category_id=?1", rusqlite::params![id])
        .map_err(|e| e.to_string())?;
    conn.execute("DELETE FROM source_categories WHERE id=?1", rusqlite::params![id])
        .map_err(|e| e.to_string())?;
    Ok(())
}

// ── 项目 CRUD ────────────────────────────────────────────────────────────────

#[tauri::command]
pub fn get_source_projects(category_id: Option<String>, search: Option<String>) -> Result<Vec<SourceProject>, String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;

    let mut sql = String::from(
        "SELECT p.*, c.name AS category_name,
                (SELECT COUNT(*) FROM source_versions v WHERE v.project_id = p.id) AS version_count
         FROM source_projects p
         LEFT JOIN source_categories c ON c.id = p.category_id
         WHERE 1=1"
    );
    let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = vec![];

    if let Some(ref cid) = category_id {
        sql.push_str(" AND p.category_id = ?");
        params.push(Box::new(cid.clone()));
    }
    if let Some(ref q) = search {
        sql.push_str(" AND (p.name LIKE ? OR p.description LIKE ? OR p.language LIKE ?)");
        let like = format!("%{}%", q);
        params.push(Box::new(like.clone()));
        params.push(Box::new(like.clone()));
        params.push(Box::new(like));
    }
    sql.push_str(" ORDER BY p.updated_at DESC");

    let mut stmt = conn.prepare(&sql).map_err(|e| e.to_string())?;
    let param_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
    let rows = stmt
        .query_map(param_refs.as_slice(), SourceProject::from_row)
        .map_err(|e| e.to_string())?;
    rows.map(|r| r.map_err(|e| e.to_string())).collect()
}

#[tauri::command]
pub fn create_source_project(
    name: String,
    description: Option<String>,
    category_id: Option<String>,
    language: Option<String>,
    repo_url: Option<String>,
    introduction: Option<String>,
) -> Result<SourceProject, String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    let id = Uuid::new_v4().to_string();
    conn.execute(
        "INSERT INTO source_projects (id, name, description, category_id, language, repo_url, introduction)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
        rusqlite::params![id, name, description, category_id, language, repo_url, introduction],
    ).map_err(|e| e.to_string())?;

    let mut stmt = conn.prepare(
        "SELECT p.*, c.name AS category_name, 0 AS version_count
         FROM source_projects p LEFT JOIN source_categories c ON c.id = p.category_id
         WHERE p.id = ?1"
    ).map_err(|e| e.to_string())?;
    stmt.query_row(rusqlite::params![id], SourceProject::from_row)
        .map_err(|e| e.to_string())
}

#[tauri::command]
pub fn update_source_project(
    id: String,
    name: String,
    description: Option<String>,
    category_id: Option<String>,
    language: Option<String>,
    repo_url: Option<String>,
    introduction: Option<String>,
    notes: Option<String>,
) -> Result<(), String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    conn.execute(
        "UPDATE source_projects SET name=?2, description=?3, category_id=?4, language=?5,
         repo_url=?6, introduction=?7, notes=?8, updated_at=datetime('now') WHERE id=?1",
        rusqlite::params![id, name, description, category_id, language, repo_url, introduction, notes],
    ).map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
pub fn delete_source_project(id: String) -> Result<(), String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    // 先获取所有版本的文件路径，清理托管目录中的文件
    let mut stmt = conn.prepare("SELECT file_path FROM source_versions WHERE project_id=?1")
        .map_err(|e| e.to_string())?;
    let paths: Vec<String> = stmt.query_map(rusqlite::params![id], |r| r.get(0))
        .map_err(|e| e.to_string())?
        .filter_map(|r| r.ok())
        .collect();
    for p in &paths {
        let path = Path::new(p);
        if path.is_dir() {
            let _ = fs::remove_dir_all(path);
        } else if path.is_file() {
            let _ = fs::remove_file(path);
        }
    }
    // CASCADE 会删除 source_versions
    conn.execute("DELETE FROM source_projects WHERE id=?1", rusqlite::params![id])
        .map_err(|e| e.to_string())?;
    Ok(())
}

// ── 版本管理 ─────────────────────────────────────────────────────────────────

#[tauri::command]
pub fn get_source_versions(project_id: String) -> Result<Vec<SourceVersion>, String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare("SELECT * FROM source_versions WHERE project_id=?1 ORDER BY is_primary DESC, imported_at DESC")
        .map_err(|e| e.to_string())?;
    let rows = stmt
        .query_map(rusqlite::params![project_id], SourceVersion::from_row)
        .map_err(|e| e.to_string())?;
    rows.map(|r| r.map_err(|e| e.to_string())).collect()
}

/// 导入一个版本——支持两种模式：
/// 1. 目录模式：file_path 指向已有源码目录，原地引用
/// 2. 文件模式：file_path 指向 .zip/.tar.gz 等压缩包，复制到托管目录保管
#[tauri::command]
pub fn add_source_version(
    app_handle: tauri::AppHandle,
    project_id: String,
    project_name: String,
    version: String,
    file_path: String,
) -> Result<SourceVersion, String> {
    use tauri::Manager;
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    let id = Uuid::new_v4().to_string();

    let src = Path::new(&file_path);
    if !src.exists() {
        return Err(format!("路径不存在: {}", file_path));
    }

    let is_dir = src.is_dir();
    let (stored_path, file_size, dir_tree_json, archive_path);

    if is_dir {
        // ── 目录模式：原地引用，扫描目录树 ──
        stored_path = file_path.clone();
        file_size = calc_dir_size(&file_path);
        let dir_tree = scan_dir_tree_internal(&file_path);
        dir_tree_json = serde_json::to_string(&dir_tree).unwrap_or_else(|_| "[]".into());
        archive_path = None::<String>;

        // 自动提取 README
        if let Some(ref readme_content) = extract_readme_internal(&file_path) {
            let _ = conn.execute(
                "UPDATE source_projects SET readme=?2, updated_at=datetime('now') WHERE id=?1 AND (readme IS NULL OR readme='')",
                rusqlite::params![project_id, readme_content],
            );
        }
        // 自动检测语言
        if let Some(ref lang) = detect_language_internal(&file_path) {
            let _ = conn.execute(
                "UPDATE source_projects SET language=?2 WHERE id=?1 AND (language IS NULL OR language='')",
                rusqlite::params![project_id, lang],
            );
        }
    } else {
        // ── 文件模式：复制压缩包到托管目录 ──
        let data_dir = app_handle.path().app_data_dir().map_err(|e| e.to_string())?;
        let lib_root = data_dir.join("source-library");

        let safe_name: String = project_name.chars()
            .map(|c| if c.is_alphanumeric() || c == '-' || c == '_' || c == '.' { c } else { '_' })
            .collect();
        let dest_dir = lib_root.join(&safe_name);
        fs::create_dir_all(&dest_dir).map_err(|e| format!("创建托管目录失败: {}", e))?;

        let file_name = src.file_name()
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_else(|| format!("{}.archive", version));

        let dest_file = dest_dir.join(&file_name);

        // 复制文件到托管目录
        fs::copy(src, &dest_file).map_err(|e| format!("复制文件失败: {}", e))?;

        file_size = dest_file.metadata().map(|m| m.len() as i64).unwrap_or(0);
        stored_path = dest_file.to_string_lossy().to_string();
        archive_path = Some(file_path.clone()); // 记录原始路径

        // 文件模式的目录树：显示压缩包文件名本身
        let entry = DirEntry {
            name: file_name,
            type_: "file".into(),
            children_count: None,
        };
        dir_tree_json = serde_json::to_string(&vec![entry]).unwrap_or_else(|_| "[]".into());
    }

    // 检查是否为首个版本
    let count: i64 = conn
        .query_row("SELECT COUNT(*) FROM source_versions WHERE project_id=?1", rusqlite::params![project_id], |r| r.get(0))
        .unwrap_or(0);
    let is_primary = if count == 0 { 1 } else { 0 };

    conn.execute(
        "INSERT INTO source_versions (id, project_id, version, file_path, archive_path, file_size, is_primary, dir_tree)
         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
        rusqlite::params![id, project_id, version, stored_path, archive_path, file_size, is_primary, dir_tree_json],
    ).map_err(|e| e.to_string())?;

    // 更新项目 updated_at
    let _ = conn.execute("UPDATE source_projects SET updated_at=datetime('now') WHERE id=?1", rusqlite::params![project_id]);

    Ok(SourceVersion {
        id,
        project_id,
        version,
        file_path: stored_path,
        archive_path,
        file_size,
        is_primary: is_primary != 0,
        dir_tree: Some(dir_tree_json),
        notes: None,
        imported_at: None,
    })
}

#[tauri::command]
pub fn set_primary_version(version_id: String, project_id: String) -> Result<(), String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    conn.execute("UPDATE source_versions SET is_primary=0 WHERE project_id=?1", rusqlite::params![project_id])
        .map_err(|e| e.to_string())?;
    conn.execute("UPDATE source_versions SET is_primary=1 WHERE id=?1", rusqlite::params![version_id])
        .map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
pub fn delete_source_version(version_id: String) -> Result<(), String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    let path: String = conn
        .query_row("SELECT file_path FROM source_versions WHERE id=?1", rusqlite::params![version_id], |r| r.get(0))
        .map_err(|e| e.to_string())?;
    let p = Path::new(&path);
    if p.is_dir() {
        let _ = fs::remove_dir_all(p);
    } else if p.is_file() {
        let _ = fs::remove_file(p);
    }
    conn.execute("DELETE FROM source_versions WHERE id=?1", rusqlite::params![version_id])
        .map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
pub fn update_version_notes(version_id: String, notes: String) -> Result<(), String> {
    let conn = db::pool().get().map_err(|e| e.to_string())?;
    conn.execute("UPDATE source_versions SET notes=?2 WHERE id=?1", rusqlite::params![version_id, notes])
        .map_err(|e| e.to_string())?;
    Ok(())
}

/// 在文件管理器中打开文件/目录所在位置
#[tauri::command]
pub fn reveal_source_in_explorer(path: String) -> Result<(), String> {
    let p = Path::new(&path);
    if !p.exists() {
        return Err(format!("路径不存在: {}", path));
    }
    // 如果是文件，打开其父目录并选中；如果是目录，直接打开
    #[cfg(target_os = "windows")]
    {
        if p.is_file() {
            // explorer /select,"path" 会打开并选中文件
            let _ = std::process::Command::new("explorer")
                .arg("/select,")
                .arg(&path)
                .spawn();
        } else {
            let _ = std::process::Command::new("explorer")
                .arg(&path)
                .spawn();
        }
    }
    #[cfg(target_os = "macos")]
    {
        if p.is_file() {
            let _ = std::process::Command::new("open").arg("-R").arg(&path).spawn();
        } else {
            let _ = std::process::Command::new("open").arg(&path).spawn();
        }
    }
    #[cfg(target_os = "linux")]
    {
        let dir = if p.is_file() { p.parent().unwrap_or(p) } else { p };
        let _ = std::process::Command::new("xdg-open").arg(dir).spawn();
    }
    Ok(())
}

/// 复制文件/目录到指定目标路径
#[tauri::command]
pub fn copy_source_to(source: String, dest_dir: String) -> Result<String, String> {
    let src = Path::new(&source);
    if !src.exists() {
        return Err(format!("源路径不存在: {}", source));
    }
    let dest = Path::new(&dest_dir);
    fs::create_dir_all(dest).map_err(|e| format!("创建目标目录失败: {}", e))?;

    if src.is_file() {
        let file_name = src.file_name().ok_or("无法获取文件名")?;
        let dest_file = dest.join(file_name);
        fs::copy(src, &dest_file).map_err(|e| format!("复制文件失败: {}", e))?;
        Ok(dest_file.to_string_lossy().to_string())
    } else {
        // 复制整个目录
        let dir_name = src.file_name().ok_or("无法获取目录名")?;
        let dest_sub = dest.join(dir_name);
        copy_dir_recursive(src, &dest_sub)?;
        Ok(dest_sub.to_string_lossy().to_string())
    }
}

fn copy_dir_recursive(src: &Path, dest: &Path) -> Result<(), String> {
    fs::create_dir_all(dest).map_err(|e| format!("创建目录失败: {}", e))?;
    for entry in fs::read_dir(src).map_err(|e| format!("读取目录失败: {}", e))? {
        let entry = entry.map_err(|e| e.to_string())?;
        let src_path = entry.path();
        let dest_path = dest.join(entry.file_name());
        if src_path.is_dir() {
            copy_dir_recursive(&src_path, &dest_path)?;
        } else {
            fs::copy(&src_path, &dest_path).map_err(|e| format!("复制失败: {}", e))?;
        }
    }
    Ok(())
}

// ── 目录树扫描 ───────────────────────────────────────────────────────────────

#[tauri::command]
pub fn scan_source_dir_tree(path: String) -> Result<Vec<DirEntry>, String> {
    Ok(scan_dir_tree_internal(&path))
}

fn scan_dir_tree_internal(path: &str) -> Vec<DirEntry> {
    let root = Path::new(path);
    if !root.is_dir() {
        return vec![];
    }

    let ignore_dirs = [
        "node_modules", ".git", "target", "vendor", "__pycache__",
        ".next", ".nuxt", "dist", "build", ".idea", ".vscode",
        "venv", ".venv", "env",
    ];

    let mut entries: Vec<DirEntry> = Vec::new();

    if let Ok(read_dir) = fs::read_dir(root) {
        let mut items: Vec<_> = read_dir.filter_map(|e| e.ok()).collect();
        items.sort_by(|a, b| {
            let a_dir = a.path().is_dir();
            let b_dir = b.path().is_dir();
            b_dir.cmp(&a_dir).then_with(|| a.file_name().cmp(&b.file_name()))
        });

        for entry in items {
            let name = entry.file_name().to_string_lossy().to_string();
            let path = entry.path();

            if path.is_dir() {
                if ignore_dirs.contains(&name.as_str()) {
                    continue;
                }
                let children_count = fs::read_dir(&path)
                    .map(|rd| rd.count() as i32)
                    .unwrap_or(0);
                entries.push(DirEntry {
                    name: format!("{}/", name),
                    type_: "dir".into(),
                    children_count: Some(children_count),
                });
            } else {
                entries.push(DirEntry {
                    name,
                    type_: "file".into(),
                    children_count: None,
                });
            }
        }
    }

    entries
}

// ── README 提取 ──────────────────────────────────────────────────────────────

fn extract_readme_internal(path: &str) -> Option<String> {
    let root = Path::new(path);
    let candidates = ["README.md", "readme.md", "README.MD", "Readme.md", "README", "readme"];
    for name in &candidates {
        let file = root.join(name);
        if file.is_file() {
            if let Ok(content) = fs::read_to_string(&file) {
                if content.len() > 50 * 1024 {
                    return Some(content[..50 * 1024].to_string());
                }
                return Some(content);
            }
        }
    }
    None
}

// ── 语言检测 ─────────────────────────────────────────────────────────────────

fn detect_language_internal(path: &str) -> Option<String> {
    let root = Path::new(path);
    let checks: &[(&str, &str)] = &[
        ("Cargo.toml", "Rust"),
        ("go.mod", "Go"),
        ("package.json", "TypeScript"),
        ("pom.xml", "Java"),
        ("build.gradle", "Java"),
        ("build.gradle.kts", "Kotlin"),
        ("requirements.txt", "Python"),
        ("setup.py", "Python"),
        ("pyproject.toml", "Python"),
        ("Gemfile", "Ruby"),
        ("composer.json", "PHP"),
        ("Package.swift", "Swift"),
        ("CMakeLists.txt", "C++"),
        ("Makefile", "C"),
    ];

    for (file, lang) in checks {
        if root.join(file).exists() {
            if *file == "package.json" {
                if root.join("tsconfig.json").exists() {
                    return Some("TypeScript".into());
                }
                return Some("JavaScript".into());
            }
            return Some(lang.to_string());
        }
    }
    None
}

// ── 工具函数 ─────────────────────────────────────────────────────────────────

fn calc_dir_size(path: &str) -> i64 {
    let mut total: u64 = 0;
    if let Ok(entries) = walkdir(Path::new(path)) {
        total = entries;
    }
    total as i64
}

fn walkdir(path: &Path) -> Result<u64, ()> {
    let mut size = 0u64;
    if path.is_dir() {
        if let Ok(rd) = fs::read_dir(path) {
            for entry in rd.flatten() {
                let p = entry.path();
                if p.is_dir() {
                    size += walkdir(&p).unwrap_or(0);
                } else if let Ok(meta) = p.metadata() {
                    size += meta.len();
                }
            }
        }
    }
    Ok(size)
}
