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

// ── 返回结构 ──────────────────────────────────────────────────────────────────

#[derive(Debug, Serialize)]
pub struct ProjectHealth {
    pub path_valid: bool,
    pub has_git: bool,
    /// git log --oneline -1 输出
    pub last_commit: Option<String>,
    /// 最近 10 条提交（hash + 日期 + message）
    pub recent_commits: Vec<String>,
    /// 项目记录的技术栈
    pub tech_stack: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct BlueprintSummary {
    pub module_count: u32,
    pub done_count: u32,
    pub in_progress: u32,
    pub planned: u32,
    pub total_tasks: u32,
    pub done_tasks: u32,
    /// 可派发任务数
    pub dispatchable: u32,
}

#[derive(Debug, Serialize)]
pub struct RuntimeLogEntry {
    pub ts: String,
    pub category: String,
    pub level: String,
    pub message: String,
}

#[derive(Debug, Serialize)]
pub struct ActiveTaskCard {
    pub module_name: String,
    pub title: String,
    pub status: String,
    pub files: Option<String>,
    pub acceptance: Option<String>,
    pub blocked_reason: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct ReviewCoverage {
    /// 已完成的 M/L 复杂度任务数
    pub completed_ml_tasks: u32,
    /// 该项目已有的复盘笔记数
    pub note_count: u32,
}

#[derive(Debug, Serialize)]
pub struct MentorContext {
    pub project_id: String,
    pub project_name: String,
    pub health: ProjectHealth,
    pub blueprint_summary: Option<BlueprintSummary>,
    pub active_tasks: Vec<ActiveTaskCard>,
    pub recent_errors: Vec<RuntimeLogEntry>,
    /// 复盘笔记覆盖情况
    pub review_coverage: Option<ReviewCoverage>,
    /// 格式化好的 Markdown，可直接复制给 Claude
    pub mentor_markdown: String,
}

// ── Tauri command ─────────────────────────────────────────────────────────────

#[tauri::command]
pub fn get_mentor_context(project_id: String) -> Result<MentorContext, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;

    // 1. 从 DB 读取项目基本信息
    let row: rusqlite::Result<(String, Option<String>, Option<String>, Option<String>, Option<String>)> = conn
        .query_row(
            "SELECT pp.name, pw.win_path, pw.wsl_path, pw.platform, pp.tech_stack
             FROM project_workspaces pw
             JOIN project_profiles pp ON pp.id = pw.profile_id
             WHERE pw.id = ?1",
            params![&project_id],
            |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?, r.get(3)?, r.get(4)?)),
        );

    let (project_name, win_path, wsl_path, platform, tech_stack) =
        row.map_err(|_| format!("项目 {} 不存在", project_id))?;


    // 2. 判断是否为 WSL 项目，并提前解析 distro（只解析一次，健康检查和蓝图共用）
    //    - platform == "wsl"：明确标记
    //    - 旧数据兼容：wsl_path 有值且 win_path 为空（迁移时 COALESCE 可能错填 'windows'）
    let is_wsl = platform.as_deref() == Some("wsl")
        || (wsl_path.is_some()
            && (win_path.is_none() || win_path.as_deref() == Some("")));

    // 2b. 若 wsl_path 存的是 UNC 路径（\\wsl.localhost\Distro\...），
    //     自动提取 distro 和对应的 Linux 路径；否则视为原始 Linux 路径
    let (wsl_distro, wsl_linux_path): (Option<String>, Option<String>) = if is_wsl {
        match wsl_path.as_deref() {
            Some(p) if p.starts_with("\\\\wsl.localhost\\") || p.starts_with("\\\\wsl$\\") => {
                // UNC 格式：\\wsl.localhost\Ubuntu-22.04\home\user\project
                let (distro, linux) = unc_to_distro_and_linux(p);
                (Some(distro), Some(linux))
            }
            Some(p) if !p.is_empty() => {
                // Linux 格式：/home/user/project
                let configured = read_wsl_distro(&conn);
                let distro = resolve_wsl_distro(&configured, p);
                (Some(distro), Some(p.to_string()))
            }
            _ => (None, None),
        }
    } else {
        (None, None)
    };

    // 3. 本地健康检查
    let (path_valid, has_git, last_commit, recent_commits) = match (is_wsl, &wsl_linux_path, &wsl_distro) {
        (true, Some(linux_path), Some(distro)) => {
            let pv = wsl_test_dir(distro, linux_path);
            let hg = pv && wsl_test_dir(distro, &format!("{}/.git", linux_path));
            let lc = if hg { wsl_git_log(distro, linux_path) } else { None };
            let rc = if hg { wsl_git_log_recent(distro, linux_path) } else { vec![] };
            (pv, hg, lc, rc)
        }
        (true, _, _) => (false, false, None, vec![]),
        (false, _, _) => match win_path.as_deref().filter(|p| !p.is_empty()).map(Path::new) {
            Some(root) => {
                let pv = root.exists();
                let hg = root.join(".git").exists();
                let lc = if hg { win_git_log(root) } else { None };
                let rc = if hg { win_git_log_recent(root) } else { vec![] };
                (pv, hg, lc, rc)
            }
            None => (false, false, None, vec![]),
        },
    };

    let health = ProjectHealth {
        path_valid,
        has_git,
        last_commit,
        recent_commits,
        tech_stack,
    };

    // 4. 蓝图摘要（复用已解析的 wsl_distro / wsl_linux_path）
    let blueprint_summary = match (is_wsl, &wsl_linux_path, &wsl_distro) {
        (true, Some(linux_path), Some(distro)) => {
            read_blueprint_summary_wsl(linux_path, distro)
        }
        (true, _, _) => None,
        (false, _, _) => {
            win_path.as_deref().filter(|p| !p.is_empty()).map(Path::new).and_then(read_blueprint_summary_win)
        }
    };

    // 4b. 进行中 / 阻塞任务卡详情
    let active_tasks = match (is_wsl, &wsl_linux_path, &wsl_distro) {
        (true, Some(linux_path), Some(distro)) => {
            let unc_root = linux_to_unc(linux_path, distro);
            read_active_task_cards(&unc_root)
        }
        (true, _, _) => vec![],
        (false, _, _) => {
            win_path.as_deref().filter(|p| !p.is_empty()).map(Path::new)
                .map(read_active_task_cards)
                .unwrap_or_default()
        }
    };

    // 5. 近期错误日志
    let recent_errors = read_recent_errors(&conn, &project_id);

    // 5b. AI 笔记（最近 5 条，作为跨会话记忆）
    let ai_notes = read_ai_notes(&conn, &project_id);

    // 5c. 复盘笔记覆盖率
    let review_coverage = {
        let completed_ml_tasks = match (is_wsl, &wsl_linux_path, &wsl_distro) {
            (true, Some(linux_path), Some(distro)) => {
                let unc_root = linux_to_unc(linux_path, distro);
                count_done_ml_tasks(&unc_root)
            }
            (true, _, _) => 0,
            (false, _, _) => {
                win_path.as_deref().filter(|p| !p.is_empty()).map(Path::new)
                    .map(count_done_ml_tasks)
                    .unwrap_or(0)
            }
        };
        let note_count = conn
            .query_row(
                "SELECT COUNT(*) FROM project_notes WHERE project_id = ?1",
                params![&project_id],
                |r| r.get::<_, u32>(0),
            )
            .unwrap_or(0);
        Some(ReviewCoverage { completed_ml_tasks, note_count })
    };

    // 6. 生成 mentor_markdown
    let mentor_markdown = build_mentor_markdown(
        &project_id,
        &project_name,
        &health,
        &blueprint_summary,
        &active_tasks,
        &recent_errors,
        &ai_notes,
        review_coverage.as_ref(),
    );

    Ok(MentorContext {
        project_id,
        project_name,
        health,
        blueprint_summary,
        active_tasks,
        recent_errors,
        review_coverage,
        mentor_markdown,
    })
}

// ── WSL 辅助 ──────────────────────────────────────────────────────────────────

/// 从 settings 表读取 wsl_distro，默认 "Ubuntu"
fn read_wsl_distro(conn: &rusqlite::Connection) -> String {
    conn.query_row(
        "SELECT value FROM settings WHERE key = 'wsl_distro'",
        [],
        |r| r.get(0),
    )
    .unwrap_or_else(|_| "Ubuntu".to_string())
}

/// 用 `wsl -d <distro> -- test -d <path>` 检查 Linux 路径是否存在（目录）
/// 比 Path::exists() 对 \\wsl.localhost\... UNC 路径更可靠
/// stderr 重定向到 null，避免 WSL 网络警告打印到控制台
fn wsl_test_dir(distro: &str, linux_path: &str) -> bool {
    crate::silent_cmd("wsl")
        .args(["-d", distro, "--", "test", "-d", linux_path])
        .stderr(std::process::Stdio::null())
        .status()
        .map(|s| s.success())
        .unwrap_or(false)
}

/// 解析 WSL 项目实际使用的发行版名称：
/// 1. 先试配置的 distro
/// 2. 失败则枚举 `wsl -l -q` 找第一个能访问该 linux_path 的 distro
fn resolve_wsl_distro(configured: &str, linux_path: &str) -> String {
    if wsl_test_dir(configured, linux_path) {
        return configured.to_string();
    }
    // 枚举所有已安装 distro
    let distros = list_wsl_distros();
    distros.into_iter()
        .find(|d| wsl_test_dir(d, linux_path))
        .unwrap_or_else(|| configured.to_string())
}

/// 列出系统所有已安装 WSL 发行版（解析 UTF-16 LE 输出）
fn list_wsl_distros() -> Vec<String> {
    let output = match crate::silent_cmd("wsl")
        .args(["-l", "-q"])
        .stderr(std::process::Stdio::null())
        .output()
    {
        Ok(o) => o,
        Err(_) => return vec![],
    };
    let bytes = &output.stdout;
    let text = if bytes.len() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE {
        let words: Vec<u16> = bytes[2..]
            .chunks(2)
            .filter(|c| c.len() == 2)
            .map(|c| u16::from_le_bytes([c[0], c[1]]))
            .collect();
        String::from_utf16_lossy(&words).to_string()
    } else if bytes.windows(2).any(|w| w[1] == 0) {
        let words: Vec<u16> = bytes
            .chunks(2)
            .filter(|c| c.len() == 2)
            .map(|c| u16::from_le_bytes([c[0], c[1]]))
            .collect();
        String::from_utf16_lossy(&words).to_string()
    } else {
        String::from_utf8_lossy(bytes).to_string()
    };
    text.lines()
        .map(|l| l.trim_matches(|c: char| c.is_whitespace() || c == '\0').to_string())
        .filter(|l| !l.is_empty())
        .collect()
}

/// 在 WSL 内运行 git log，返回最近一条提交的单行摘要
fn wsl_git_log(distro: &str, linux_path: &str) -> Option<String> {
    let output = crate::silent_cmd("wsl")
        .args(["-d", distro, "--", "git", "-C", linux_path, "log", "--oneline", "-1"])
        .stderr(std::process::Stdio::null())
        .output()
        .ok()?;
    if output.status.success() {
        let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
        if s.is_empty() { None } else { Some(s) }
    } else {
        None
    }
}

/// 将 Linux 路径转为 \\wsl.localhost\<distro>\... UNC 路径
fn linux_to_unc(linux_path: &str, distro: &str) -> PathBuf {
    let inner = linux_path.trim_start_matches('/').replace('/', "\\");
    PathBuf::from(format!("\\\\wsl.localhost\\{}\\{}", distro, inner))
}

/// UNC 路径 → (distro, linux_path)
/// \\wsl.localhost\Ubuntu-22.04\home\user\proj → ("Ubuntu-22.04", "/home/user/proj")
fn unc_to_distro_and_linux(unc: &str) -> (String, String) {
    let prefix = if unc.starts_with("\\\\wsl.localhost\\") {
        "\\\\wsl.localhost\\"
    } else {
        "\\\\wsl$\\"
    };
    let rest = &unc[prefix.len()..];
    let mut parts = rest.splitn(2, '\\');
    let distro = parts.next().unwrap_or("Ubuntu").to_string();
    let inner = parts.next().unwrap_or("");
    let linux = format!("/{}", inner.replace('\\', "/"));
    (distro, linux)
}

// ── Windows 辅助 ──────────────────────────────────────────────────────────────

fn win_git_log(root: &Path) -> Option<String> {
    crate::silent_cmd("git")
        .args(["log", "--oneline", "-1"])
        .current_dir(root)
        .output()
        .ok()
        .filter(|o| o.status.success())
        .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
        .filter(|s| !s.is_empty())
}

/// 最近 10 条提交，格式：`abc1234 2026-04-08 fix: something`
fn win_git_log_recent(root: &Path) -> Vec<String> {
    let output = crate::silent_cmd("git")
        .args(["log", "--pretty=format:%h %ad %s", "--date=short", "-10"])
        .current_dir(root)
        .output();
    match output {
        Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout)
            .lines()
            .filter(|l| !l.is_empty())
            .map(|l| l.to_string())
            .collect(),
        _ => vec![],
    }
}

/// WSL 版最近 10 条提交
fn wsl_git_log_recent(distro: &str, linux_path: &str) -> Vec<String> {
    let output = crate::silent_cmd("wsl")
        .args([
            "-d", distro, "--", "git", "-C", linux_path,
            "log", "--pretty=format:%h %ad %s", "--date=short", "-10",
        ])
        .stderr(std::process::Stdio::null())
        .output();
    match output {
        Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout)
            .lines()
            .filter(|l| !l.is_empty())
            .map(|l| l.to_string())
            .collect(),
        _ => vec![],
    }
}

// ── 蓝图摘要 ──────────────────────────────────────────────────────────────────

/// WSL 项目：用 wsl 命令判断 manifest 存在性，再通过 UNC 路径读取内容
fn read_blueprint_summary_wsl(linux_path: &str, distro: &str) -> Option<BlueprintSummary> {
    let manifest_linux = format!("{}/.blueprint/manifest.yaml", linux_path);
    // 用 wsl test -f 判断文件存在（不依赖 Path::exists()）
    let exists = crate::silent_cmd("wsl")
        .args(["-d", distro, "--", "test", "-f", &manifest_linux])
        .stderr(std::process::Stdio::null())
        .status()
        .map(|s| s.success())
        .unwrap_or(false);
    if !exists {
        return None;
    }
    // 文件存在，通过 UNC 路径读取（CreateFileW 可正常访问 \\wsl.localhost\...）
    let unc_root = linux_to_unc(linux_path, distro);
    parse_blueprint_summary(&unc_root)
}

/// Windows 项目：走常规 Path::exists()
fn read_blueprint_summary_win(root: &Path) -> Option<BlueprintSummary> {
    if !root.join(".blueprint").join("manifest.yaml").exists() {
        return None;
    }
    parse_blueprint_summary(root)
}

fn parse_blueprint_summary(root: &Path) -> Option<BlueprintSummary> {
    let result = crate::commands::blueprint::get_blueprint(root.to_string_lossy().to_string());
    match result {
        Ok(Some(data)) => {
            let s = &data.stats;
            Some(BlueprintSummary {
                module_count: s.total_modules,
                done_count: s.done,
                in_progress: s.in_progress,
                planned: s.planned,
                total_tasks: s.total_tasks,
                done_tasks: s.tasks_done,
                dispatchable: s.dispatchable,
            })
        }
        _ => {
            // manifest.yaml 存在但解析失败时，返回有蓝图但无法读取的状态
            Some(BlueprintSummary {
                module_count: 0,
                done_count: 0,
                in_progress: 0,
                planned: 0,
                total_tasks: 0,
                done_tasks: 0,
                dispatchable: 0,
            })
        }
    }
}

/// 统计蓝图中已完成的 M/L 复杂度任务数
fn count_done_ml_tasks(root: &Path) -> u32 {
    match crate::commands::blueprint::get_blueprint(root.to_string_lossy().to_string()) {
        Ok(Some(data)) => data
            .manifest
            .modules
            .iter()
            .flat_map(|m| m.tasks.iter())
            .filter(|t| {
                t.status == "done"
                    && matches!(t.complexity.as_deref(), Some("M") | Some("L"))
            })
            .count() as u32,
        _ => 0,
    }
}

/// 从项目根路径读取蓝图，返回所有进行中/阻塞的任务卡
fn read_active_task_cards(root: &Path) -> Vec<ActiveTaskCard> {
    match crate::commands::blueprint::get_blueprint(root.to_string_lossy().to_string()) {
        Ok(Some(data)) => data
            .manifest
            .modules
            .into_iter()
            .flat_map(|m| {
                let module_name = m.name.clone();
                m.tasks.into_iter()
                    .filter(|t| t.status == "in_progress" || t.status == "blocked")
                    .map(move |t| ActiveTaskCard {
                        module_name: module_name.clone(),
                        title: t.title,
                        status: t.status,
                        files: t.files,
                        acceptance: t.acceptance,
                        blocked_reason: t.blocked_reason,
                    })
            })
            .collect(),
        _ => vec![],
    }
}

// ── 错误日志 ──────────────────────────────────────────────────────────────────

fn read_recent_errors(
    conn: &rusqlite::Connection,
    project_id: &str,
) -> Vec<RuntimeLogEntry> {
    let sql = "SELECT ts, category, level, message FROM runtime_logs
               WHERE level IN ('error', 'warn')
                 AND (json_extract(context, '$.project_id') = ?1
                      OR context IS NULL)
               ORDER BY id DESC LIMIT 20";

    let result = conn.prepare(sql).and_then(|mut stmt| {
        stmt.query_map(params![project_id], |row| {
            Ok(RuntimeLogEntry {
                ts: row.get(0)?,
                category: row.get(1)?,
                level: row.get(2)?,
                message: row.get(3)?,
            })
        })
        .map(|iter| iter.filter_map(|r| r.ok()).collect::<Vec<_>>())
    });

    result.unwrap_or_default()
}

fn read_ai_notes(conn: &rusqlite::Connection, project_id: &str) -> Vec<ProjectNote> {
    let mut stmt = match conn.prepare(
        "SELECT id, project_id, source, content, created_at
         FROM project_notes
         WHERE project_id = ?1
         ORDER BY id DESC LIMIT 5",
    ) {
        Ok(s) => s,
        Err(_) => return vec![],
    };
    stmt.query_map(params![project_id], |row| {
        Ok(ProjectNote {
            id: row.get(0)?,
            project_id: row.get(1)?,
            source: row.get(2)?,
            content: row.get(3)?,
            created_at: row.get(4)?,
        })
    })
    .map(|iter| iter.filter_map(|r| r.ok()).collect())
    .unwrap_or_default()
}

// ── Markdown 生成 ─────────────────────────────────────────────────────────────

fn build_mentor_markdown(
    project_id: &str,
    project_name: &str,
    health: &ProjectHealth,
    blueprint: &Option<BlueprintSummary>,
    active_tasks: &[ActiveTaskCard],
    errors: &[RuntimeLogEntry],
    ai_notes: &[ProjectNote],
    review_coverage: Option<&ReviewCoverage>,
) -> String {
    let mut md = String::new();

    md.push_str(&format!("# 项目导师报告：{}\n\n", project_name));
    md.push_str(&format!("项目 ID：`{}`\n\n", project_id));

    // ── 本地健康 ──────────────────────────────────────────────────────────────
    md.push_str("## 本地健康\n\n");
    let path_icon = if health.path_valid { "✅" } else { "❌" };
    let git_icon  = if health.has_git   { "✅" } else { "⚠️" };
    md.push_str(&format!("- 本地路径：{} {}\n", path_icon, if health.path_valid { "有效" } else { "路径不存在" }));
    md.push_str(&format!("- Git 仓库：{} {}\n", git_icon, if health.has_git { "已初始化" } else { "未找到 .git 目录" }));
    if let Some(ref stack) = health.tech_stack {
        md.push_str(&format!("- 技术栈：{}\n", stack));
    }
    if let Some(ref commit) = health.last_commit {
        md.push_str(&format!("- 最近提交：`{}`\n", commit));
    }

    // ── 近期提交历史 ──────────────────────────────────────────────────────────
    md.push_str("\n## 近期提交（最近 10 条）\n\n");
    if health.recent_commits.is_empty() {
        md.push_str("暂无提交记录。\n");
    } else {
        for c in &health.recent_commits {
            md.push_str(&format!("- `{}`\n", c));
        }
    }

    // ── 蓝图状态 ──────────────────────────────────────────────────────────────
    md.push_str("\n## 蓝图状态\n\n");
    if let Some(ref bp) = blueprint {
        if bp.module_count == 0 {
            md.push_str("蓝图存在，但暂无模块数据。\n");
        } else {
            let progress = (bp.done_count as f64 / bp.module_count as f64 * 100.0).round() as u32;
            md.push_str("| 维度 | 数值 |\n|------|------|\n");
            md.push_str(&format!("| 模块总数 | {} |\n", bp.module_count));
            md.push_str(&format!("| 已完成模块 | {} ({progress}%) |\n", bp.done_count));
            md.push_str(&format!("| 进行中 | {} |\n", bp.in_progress));
            md.push_str(&format!("| 已规划 | {} |\n", bp.planned));
            md.push_str(&format!("| 任务总数 | {} |\n", bp.total_tasks));
            md.push_str(&format!("| 已完成任务 | {} |\n", bp.done_tasks));
            md.push_str(&format!("| 可派发任务 | {} |\n", bp.dispatchable));
        }
    } else {
        md.push_str("该项目尚未接入蓝图（无 .blueprint/manifest.yaml）。\n");
    }

    // ── 进行中 / 阻塞任务卡 ───────────────────────────────────────────────────
    if !active_tasks.is_empty() {
        let in_progress: Vec<_> = active_tasks.iter().filter(|t| t.status == "in_progress").collect();
        let blocked: Vec<_> = active_tasks.iter().filter(|t| t.status == "blocked").collect();

        if !in_progress.is_empty() {
            md.push_str("\n## 进行中任务\n\n");
            for t in &in_progress {
                md.push_str(&format!("### [{}] {}\n", t.module_name, t.title));
                if let Some(ref f) = t.files { md.push_str(&format!("- files: `{}`\n", f)); }
                if let Some(ref a) = t.acceptance { md.push_str(&format!("- acceptance: {}\n", a)); }
            }
        }

        if !blocked.is_empty() {
            md.push_str("\n## 阻塞任务\n\n");
            for t in &blocked {
                md.push_str(&format!("### [{}] {}\n", t.module_name, t.title));
                if let Some(ref r) = t.blocked_reason { md.push_str(&format!("- blocked_reason: {}\n", r)); }
                if let Some(ref f) = t.files { md.push_str(&format!("- files: `{}`\n", f)); }
            }
        }
    }

    // ── 近期错误日志 ──────────────────────────────────────────────────────────
    md.push_str("\n## 近期错误日志（最多 20 条）\n\n");
    if errors.is_empty() {
        md.push_str("无错误记录 ✅\n");
    } else {
        for e in errors {
            md.push_str(&format!("- `[{}]` **{}** `{}` — {}\n", e.ts, e.level, e.category, e.message));
        }
    }

    // ── 复盘笔记覆盖率 ────────────────────────────────────────────────────────
    if let Some(cov) = review_coverage {
        md.push_str("\n## 复盘笔记覆盖率\n\n");
        let gap = cov.completed_ml_tasks.saturating_sub(cov.note_count);
        md.push_str(&format!(
            "| 已完成 M/L 任务 | 复盘笔记数 | 缺口 |\n|------|------|------|\n| {} | {} | {} |\n",
            cov.completed_ml_tasks, cov.note_count, gap
        ));
        if gap > 0 {
            md.push_str(&format!(
                "\n> ⚠️ 有 {} 个 M/L 任务尚无复盘笔记。完成本次任务后请通过 `append_project_note` 补写，格式见 CONVENTIONS.md §交互质量复盘笔记。\n",
                gap
            ));
        } else if cov.completed_ml_tasks > 0 {
            md.push_str("\n> ✅ 所有已完成 M/L 任务均有复盘笔记。\n");
        }
    }

    // ── AI 笔记（跨会话记忆）────────────────────────────────────────────────────
    if !ai_notes.is_empty() {
        md.push_str("\n## AI 笔记（历史观察）\n\n");
        for note in ai_notes {
            md.push_str(&format!("- `{}` {}\n", note.created_at, note.content));
        }
    }

    md.push_str("\n---\n\n请根据以上信息，对该项目的当前状态、质量和下一步方向给出你的判断与建议。如需历史趋势数据，可调用 `get_project_snapshots` 工具。\n");

    md
}

// ── 项目笔记 ──────────────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize)]
pub struct ProjectNote {
    pub id: i64,
    pub project_id: String,
    pub source: String,
    pub content: String,
    pub created_at: String,
}

#[tauri::command]
pub fn get_project_notes(project_id: String) -> Result<Vec<ProjectNote>, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare(
            "SELECT id, project_id, source, content, created_at
             FROM project_notes
             WHERE project_id = ?1
             ORDER BY id DESC LIMIT 10",
        )
        .map_err(|e| e.to_string())?;
    let notes = stmt
        .query_map(params![project_id], |row| {
            Ok(ProjectNote {
                id: row.get(0)?,
                project_id: row.get(1)?,
                source: row.get(2)?,
                content: row.get(3)?,
                created_at: row.get(4)?,
            })
        })
        .map_err(|e| e.to_string())?
        .filter_map(|r| r.ok())
        .collect();
    Ok(notes)
}

#[tauri::command]
pub fn add_project_note(
    project_id: String,
    content: String,
    source: String,
) -> Result<(), String> {
    if content.trim().is_empty() {
        return Err("笔记内容不能为空".to_string());
    }
    let conn = pool().get().map_err(|e| e.to_string())?;
    conn.execute(
        "INSERT INTO project_notes (project_id, source, content) VALUES (?1, ?2, ?3)",
        params![project_id, source, content],
    )
    .map_err(|e| e.to_string())?;
    Ok(())
}

// ── 启动健康扫描 ──────────────────────────────────────────────────────────────

#[derive(Debug, Serialize)]
pub struct StartupAlert {
    pub project_name: String,
    pub alert_type: String,
    pub detail: String,
}

/// 应用启动时调用，扫描所有项目：
/// - stale_git：超过 14 天无 git 提交
/// - blocked_tasks：蓝图中有 🔴 blocked 任务
/// 结果写入 runtime_logs（category="startup_alert"），同时返回告警列表供前端展示
pub fn run_startup_health_scan() {
    let conn = match pool().get() {
        Ok(c) => c,
        Err(_) => return,
    };

    // 清除上次启动的 startup_alert 记录，避免堆积
    let _ = conn.execute(
        "DELETE FROM runtime_logs WHERE category = 'startup_alert'",
        [],
    );

    // 查询所有 workspace 的基本信息
    let rows: Vec<(String, String, Option<String>, Option<String>, Option<String>)> = {
        let mut stmt = match conn.prepare(
            "SELECT pw.id, pp.name, pw.win_path, pw.wsl_path, pw.platform
             FROM project_workspaces pw
             JOIN project_profiles pp ON pp.id = pw.profile_id",
        ) {
            Ok(s) => s,
            Err(_) => return,
        };
        stmt.query_map([], |row| {
            Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?))
        })
        .map(|iter| iter.filter_map(|r| r.ok()).collect())
        .unwrap_or_default()
    };

    for (project_id, project_name, win_path, wsl_path, platform) in rows {
        // 1. 检查 git 活跃度（超 14 天无提交）
        // 直接获取距今天数，None 表示路径不存在或无 git（跳过，不是"停滞"）
        let days_opt = get_days_since_last_commit(platform.as_deref(), win_path.as_deref(), wsl_path.as_deref(), &conn);
        if let Some(days) = days_opt {
            if days > 14 {
                // \x1f (ASCII Unit Separator) 作分隔符，避免项目名含 | 导致解析错位
                crate::db::log_event(
                    "startup_alert",
                    "warn",
                    &format!("{}\x1fstale_git\x1f{} 天未提交", project_name, days),
                    Some(&format!(r#"{{"project_id":"{}"}}"#, project_id)),
                );
            }
        }

        // 2. 检查蓝图 blocked 任务
        let blueprint_path_opt = if platform.as_deref() == Some("wsl") {
            wsl_path.as_deref().and_then(|lp| {
                let distro = conn
                    .query_row("SELECT value FROM settings WHERE key='wsl_distro'", [], |r| r.get::<_, String>(0))
                    .unwrap_or_else(|_| "Ubuntu".to_string());
                let manifest_linux = format!("{}/.blueprint/manifest.yaml", lp);
                let exists = crate::silent_cmd("wsl")
                    .args(["-d", &distro, "--", "test", "-f", &manifest_linux])
                    .stderr(std::process::Stdio::null())
                    .status()
                    .map(|s| s.success())
                    .unwrap_or(false);
                if exists {
                    let inner = lp.trim_start_matches('/').replace('/', "\\");
                    Some(std::path::PathBuf::from(format!("\\\\wsl.localhost\\{}\\{}\\.blueprint", distro, inner)))
                } else {
                    None
                }
            })
        } else {
            win_path.as_deref().map(|p| std::path::Path::new(p).join(".blueprint"))
        };

        let blocked_count = blueprint_path_opt
            .as_ref()
            .map(|bp_dir| count_blocked_tasks(bp_dir))
            .unwrap_or(0);

        let (bp_done_rate, bp_in_progress) = blueprint_path_opt
            .as_ref()
            .and_then(|bp_dir| bp_dir.parent())
            .and_then(parse_blueprint_summary)
            .map(|s| {
                let rate = if s.module_count > 0 {
                    Some(s.done_count as f64 / s.module_count as f64)
                } else {
                    None
                };
                (rate, s.in_progress)
            })
            .unwrap_or((None, 0));

        if blocked_count > 0 {
            crate::db::log_event(
                "startup_alert",
                "warn",
                &format!("{}\x1fblocked_tasks\x1f{} 个任务阻塞", project_name, blocked_count),
                Some(&format!(r#"{{"project_id":"{}"}}"#, project_id)),
            );
        }

        // 写入每日快照（INSERT OR IGNORE 保证每天只存一条）
        let has_errors = has_recent_errors(&conn, &project_id);
        save_project_snapshot(
            &conn,
            &project_id,
            days_opt,
            bp_done_rate,
            bp_in_progress,
            blocked_count,
            has_errors,
        );
    }
}

/// 获取项目最后一次 git 提交距今天数
fn get_days_since_last_commit(
    platform: Option<&str>,
    win_path: Option<&str>,
    wsl_path: Option<&str>,
    conn: &rusqlite::Connection,
) -> Option<u64> {
    let output = if platform == Some("wsl") {
        let lp = wsl_path?;
        let distro = conn
            .query_row("SELECT value FROM settings WHERE key='wsl_distro'", [], |r| r.get::<_, String>(0))
            .unwrap_or_else(|_| "Ubuntu".to_string());
        crate::silent_cmd("wsl")
            .args(["-d", &distro, "--", "git", "-C", lp, "log", "-1", "--format=%ct"])
            .stderr(std::process::Stdio::null())
            .output()
            .ok()?
    } else {
        let root = win_path?;
        crate::silent_cmd("git")
            .args(["log", "-1", "--format=%ct"])
            .current_dir(root)
            .output()
            .ok()?
    };

    if !output.status.success() {
        return None;
    }
    let ts_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
    let commit_ts: u64 = ts_str.parse().ok()?;
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .ok()?
        .as_secs();
    Some((now.saturating_sub(commit_ts)) / 86400)
}

/// 统计 .blueprint/modules/ 下所有 .md 文件中 🔴 前缀任务卡的数量
fn count_blocked_tasks(blueprint_dir: &std::path::Path) -> usize {
    let modules_dir = blueprint_dir.join("modules");
    let entries = match std::fs::read_dir(&modules_dir) {
        Ok(e) => e,
        Err(_) => return 0,
    };
    let mut count = 0;
    for entry in entries.filter_map(|e| e.ok()) {
        if entry.path().extension().and_then(|e| e.to_str()) != Some("md") {
            continue;
        }
        if let Ok(content) = std::fs::read_to_string(entry.path()) {
            count += content.lines().filter(|l| l.starts_with("### 🔴")).count();
        }
    }
    count
}

/// 检查该项目在最近 7 天内是否有 error 级别的日志
fn has_recent_errors(conn: &rusqlite::Connection, project_id: &str) -> bool {
    conn.query_row(
        "SELECT COUNT(*) FROM runtime_logs
         WHERE level = 'error'
           AND (json_extract(context, '$.project_id') = ?1 OR context IS NULL)
           AND ts >= datetime('now', '-7 days')",
        params![project_id],
        |r| r.get::<_, i64>(0),
    )
    .unwrap_or(0) > 0
}

/// 写入项目每日快照，INSERT OR IGNORE 保证每天只存一条
fn save_project_snapshot(
    conn: &rusqlite::Connection,
    project_id: &str,
    days_since_commit: Option<u64>,
    blueprint_done_rate: Option<f64>,
    blueprint_in_progress: u32,
    blueprint_blocked: usize,
    has_errors: bool,
) {
    let _ = conn.execute(
        "INSERT OR IGNORE INTO project_snapshots
         (project_id, snapshot_date, days_since_commit, blueprint_done_rate,
          blueprint_in_progress, blueprint_blocked, has_errors)
         VALUES (?1, date('now', 'localtime'), ?2, ?3, ?4, ?5, ?6)",
        params![
            project_id,
            days_since_commit.map(|d| d as i64),
            blueprint_done_rate,
            blueprint_in_progress,
            blueprint_blocked as i64,
            if has_errors { 1i64 } else { 0i64 },
        ],
    );
}

#[tauri::command]
pub fn get_startup_alerts() -> Result<Vec<StartupAlert>, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare(
            "SELECT message FROM runtime_logs
             WHERE category = 'startup_alert' AND level = 'warn'
             ORDER BY id DESC LIMIT 50",
        )
        .map_err(|e| e.to_string())?;

    let alerts = stmt
        .query_map([], |row| row.get::<_, String>(0))
        .map_err(|e| e.to_string())?
        .filter_map(|r| r.ok())
        .filter_map(|msg| {
            // 消息格式："{project_name}\x1f{alert_type}\x1f{detail}"
            // 使用 ASCII Unit Separator 避免项目名含 | 导致解析错位
            let parts: Vec<&str> = msg.splitn(3, '\x1f').collect();
            if parts.len() == 3 {
                Some(StartupAlert {
                    project_name: parts[0].to_string(),
                    alert_type: parts[1].to_string(),
                    detail: parts[2].to_string(),
                })
            } else {
                None
            }
        })
        .collect();

    Ok(alerts)
}

// ── 全组巡检 ──────────────────────────────────────────────────────────────────

#[tauri::command]
pub fn get_group_mentor_report(group_id: String) -> Result<String, String> {
    let conn = pool().get().map_err(|e| e.to_string())?;

    // 查询该产品组所有成员项目
    let mut stmt = conn
        .prepare(
            "SELECT pw.id, pp.name
             FROM project_group_map pgm
             JOIN project_workspaces pw ON pw.id = pgm.project_id
             JOIN project_profiles   pp ON pp.id = pw.profile_id
             WHERE pgm.group_id = ?1
             ORDER BY pp.name",
        )
        .map_err(|e| e.to_string())?;

    let members: Vec<(String, String)> = stmt
        .query_map(params![group_id], |row| Ok((row.get(0)?, row.get(1)?)))
        .map_err(|e| e.to_string())?
        .filter_map(|r| r.ok())
        .collect();

    if members.is_empty() {
        return Err("该产品组暂无成员项目".to_string());
    }

    let mut report = format!(
        "# 全组巡检报告\n\n产品组 ID：`{}`\n共 {} 个项目\n\n---\n\n",
        group_id,
        members.len()
    );

    for (project_id, project_name) in &members {
        report.push_str(&format!("## {}\n\n", project_name));
        match get_mentor_context(project_id.clone()) {
            Ok(ctx) => {
                // 去掉一级标题，同时把 ## 章节降级为 ###，避免与项目名 ## 同级混淆
                let body = ctx
                    .mentor_markdown
                    .lines()
                    .skip_while(|l| l.starts_with("# "))
                    .map(|l| {
                        if l.starts_with("## ") {
                            format!("#{}", l)
                        } else {
                            l.to_string()
                        }
                    })
                    .collect::<Vec<_>>()
                    .join("\n");
                report.push_str(body.trim_start());
            }
            Err(e) => {
                report.push_str(&format!("_获取导师上下文失败：{}_\n", e));
            }
        }
        report.push_str("\n\n---\n\n");
    }

    report.push_str("请根据以上所有项目的信息，对这个产品组的整体状态给出综合分析和下一步建议。\n");

    Ok(report)
}
