use serde::{Deserialize, Serialize};
use std::path::Path;
use tauri::AppHandle;

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

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BuffStatus {
    pub project_path: String,
    pub project_id: String,
    pub applied_at: String,
    pub last_sync_at: Option<String>,
    pub last_commit: Option<String>,
    pub status: String,
}

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

/// 向项目施加蓝图 Buff：注入 CONVENTIONS + CLAUDE.md，写入 DB
#[tauri::command]
pub fn apply_blueprint_buff(
    project_path: String,
    project_id: String,
) -> Result<BuffStatus, String> {
    let path = project_path.trim_end_matches(['/', '\\']).to_string();

    // 注入蓝图规则（CONVENTIONS.md + CLAUDE.md）
    crate::commands::blueprint::sync_blueprint_rules(path.clone())
        .map_err(|e| format!("注入蓝图规则失败: {e}"))?;

    // 独立项目自动创建产品组（含 MCP 注入）
    if let Err(e) = crate::commands::groups::ensure_standalone_group(&project_id) {
        // 非致命，不阻断 Buff 应用
        crate::db::log_event("buff", "warn", &format!("独立产品组创建失败: {e}"), None);
    }

    // 写入 DB
    let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
    conn.execute(
        "INSERT OR REPLACE INTO blueprint_buffs
             (project_path, project_id, applied_at, status)
         VALUES (?1, ?2, datetime('now'), 'active')",
        rusqlite::params![path, project_id],
    )
    .map_err(|e| e.to_string())?;

    query_buff(&conn, &path)
}

/// 撤销蓝图 Buff
#[tauri::command]
pub fn remove_blueprint_buff(project_path: String) -> Result<(), String> {
    let path = project_path.trim_end_matches(['/', '\\']).to_string();
    let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
    conn.execute(
        "DELETE FROM blueprint_buffs WHERE project_path = ?1",
        rusqlite::params![path],
    )
    .map_err(|e| e.to_string())?;
    Ok(())
}

/// 查询单个项目的 Buff 状态
#[tauri::command]
pub fn get_buff_status(project_path: String) -> Result<Option<BuffStatus>, String> {
    let path = project_path.trim_end_matches(['/', '\\']).to_string();
    let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
    match query_buff(&conn, &path) {
        Ok(s) => Ok(Some(s)),
        Err(_) => Ok(None),
    }
}

/// 列出所有已施加 Buff 的项目
#[tauri::command]
pub fn list_buffed_projects() -> Result<Vec<BuffStatus>, String> {
    let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare(
            "SELECT project_path, project_id, applied_at, last_sync_at, last_commit, status
             FROM blueprint_buffs WHERE status = 'active'",
        )
        .map_err(|e| e.to_string())?;

    let items = stmt
        .query_map([], |row| {
            Ok(BuffStatus {
                project_path: row.get(0)?,
                project_id: row.get(1)?,
                applied_at: row.get(2)?,
                last_sync_at: row.get(3)?,
                last_commit: row.get(4)?,
                status: row.get(5)?,
            })
        })
        .map_err(|e| e.to_string())?
        .filter_map(|r| r.ok())
        .collect();

    Ok(items)
}

// ── 后台 git 轮询（T3）──────────────────────────────────────────────────────

/// App 启动时调用，启动后台轮询任务（每 5 秒检查所有 buffed 项目的 git HEAD）
pub fn start_blueprint_watchers(app: AppHandle) {
    tauri::async_runtime::spawn(async move {
        loop {
            tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
            poll_all_buffs(&app);
        }
    });
}

fn poll_all_buffs(app: &AppHandle) {
    let paths = match list_active_paths() {
        Ok(p) => p,
        Err(_) => return,
    };
    for path in paths {
        let _ = check_project(app, &path);
    }
}

fn check_project(app: &AppHandle, project_path: &str) -> Result<(), String> {
    let root = Path::new(project_path);

    // 获取当前 HEAD commit
    let current_commit = match get_head_commit(root) {
        Ok(c) => c,
        Err(_) => return Ok(()), // 不是 git 仓库或无提交，跳过
    };

    // 查询 project_id 与上次已处理的 commit
    let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
    let (project_id, last_commit): (String, Option<String>) = conn
        .query_row(
            "SELECT project_id, last_commit FROM blueprint_buffs WHERE project_path = ?1",
            rusqlite::params![project_path],
            |row| Ok((row.get(0)?, row.get(1)?)),
        )
        .unwrap_or_else(|_| (String::new(), None));

    // 没有变化，跳过
    if last_commit.as_deref() == Some(current_commit.as_str()) {
        return Ok(());
    }

    // 飞轮 L0：解析新增 commit 的 conventional 指标写 commit_metrics（与下方文件匹配互不干扰）
    if !project_id.is_empty() {
        let first_time = last_commit.is_none();
        let commits = get_new_commits(root, last_commit.as_deref());
        if first_time && commits.len() >= FIRST_TIME_LIMIT {
            crate::db::log_event(
                "buff",
                "info",
                &format!("首次接入仅解析最近 {FIRST_TIME_LIMIT} 条 commit，更早历史跳过"),
                Some(project_path),
            );
        }
        // 批量写入用事务包裹（首次可达数十条，避免逐条 fsync）
        let tx = conn.unchecked_transaction();
        for (sha, subject, date) in &commits {
            let _ = crate::commands::commit_metrics::record_commit(
                &conn,
                &project_id,
                sha,
                subject,
                Some(date.as_str()),
            );
        }
        if let Ok(tx) = tx {
            let _ = tx.commit();
        }
    }

    // 文件路径匹配 → 标记命中的任务卡 🔵
    let changed = get_changed_files(root, last_commit.as_deref())?;
    let mut tasks_updated = false;
    if !changed.is_empty() && update_task_statuses(root, &changed)? > 0 {
        tasks_updated = true;
    }

    // 有新 commit 即刷新飞轮快照（rework_count 可能已变）；任务卡有变动则通知前端重绘
    let _ = crate::commands::blueprint::get_blueprint(project_path.to_string());
    if tasks_updated {
        use tauri::Emitter;
        let _ = app.emit(
            "blueprint://changed",
            serde_json::json!({ "project_path": project_path }),
        );
    }

    // 更新 DB：记录已处理的 commit
    let _ = conn.execute(
        "UPDATE blueprint_buffs
         SET last_commit = ?1, last_sync_at = datetime('now')
         WHERE project_path = ?2",
        rusqlite::params![current_commit, project_path],
    );

    Ok(())
}

// ── 辅助：WSL 路径检测与转换 ─────────────────────────────────────────────────

/// 判断路径是否为 WSL UNC 路径（\\wsl.localhost\... 或 \\wsl$\...）
fn is_wsl_path(path: &str) -> bool {
    path.starts_with("\\\\wsl.localhost\\") || path.starts_with("\\\\wsl$\\")
}

/// 将 WSL UNC 路径转为 Linux 绝对路径（去掉 \\wsl.localhost\Distro 前缀）
fn unc_to_linux(path: &str) -> Option<String> {
    let prefix = if path.starts_with("\\\\wsl.localhost\\") {
        "\\\\wsl.localhost\\"
    } else if path.starts_with("\\\\wsl$\\") {
        "\\\\wsl$\\"
    } else {
        return None;
    };
    let rest = &path[prefix.len()..];
    let slash_idx = rest.find('\\')?;
    Some("/".to_string() + &rest[slash_idx + 1..].replace('\\', "/"))
}

// ── 辅助：git 操作 ────────────────────────────────────────────────────────────

fn get_head_commit(root: &Path) -> Result<String, String> {
    let path_str = root.to_string_lossy();
    let out = if is_wsl_path(&path_str) {
        let linux = unc_to_linux(&path_str)
            .ok_or_else(|| "无法转换 WSL 路径".to_string())?;
        crate::silent_cmd("wsl")
            .args(["git", "-C", &linux, "rev-parse", "HEAD"])
            .output()
            .map_err(|e| e.to_string())?
    } else {
        crate::silent_cmd("git")
            .args(["rev-parse", "HEAD"])
            .current_dir(root)
            .output()
            .map_err(|e| e.to_string())?
    };

    if out.status.success() {
        Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
    } else {
        Err("无法获取 HEAD".to_string())
    }
}

fn get_changed_files(root: &Path, last_commit: Option<&str>) -> Result<Vec<String>, String> {
    let path_str = root.to_string_lossy();
    let out = if is_wsl_path(&path_str) {
        let linux = unc_to_linux(&path_str)
            .ok_or_else(|| "无法转换 WSL 路径".to_string())?;
        let cmd = if let Some(prev) = last_commit {
            format!("git -C {linux} diff --name-only {prev} HEAD")
        } else {
            format!("git -C {linux} diff-tree --no-commit-id -r --name-only HEAD")
        };
        crate::silent_cmd("wsl")
            .args(["bash", "-c", &cmd])
            .output()
            .map_err(|e| e.to_string())?
    } else {
        let args: Vec<&str> = if let Some(prev) = last_commit {
            vec!["diff", "--name-only", prev, "HEAD"]
        } else {
            vec!["diff-tree", "--no-commit-id", "-r", "--name-only", "HEAD"]
        };
        crate::silent_cmd("git")
            .args(&args)
            .current_dir(root)
            .output()
            .map_err(|e| e.to_string())?
    };

    if out.status.success() {
        Ok(String::from_utf8_lossy(&out.stdout)
            .lines()
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .collect())
    } else {
        Ok(vec![])
    }
}

// ── 辅助：新增 commit 解析（飞轮 L0）─────────────────────────────────────────

/// 首次接入时只解析最近这么多条 commit，避免一次性解析整个历史。
const FIRST_TIME_LIMIT: usize = 50;

/// 获取 last_commit..HEAD 之间的新 commit（sha, subject, committed_at ISO）。
/// 首次接入（last_commit 为 None）只取最近 FIRST_TIME_LIMIT 条。
fn get_new_commits(root: &Path, last_commit: Option<&str>) -> Vec<(String, String, String)> {
    // sha \x1f subject \x1f committer-date(ISO)，每条 commit 一行
    const FMT: &str = "--format=%H%x1f%s%x1f%cI";
    let path_str = root.to_string_lossy();

    let out = if is_wsl_path(&path_str) {
        let linux = match unc_to_linux(&path_str) {
            Some(l) => l,
            None => return vec![],
        };
        let range = match last_commit {
            Some(prev) => format!("{prev}..HEAD"),
            None => format!("-n {FIRST_TIME_LIMIT}"),
        };
        let cmd = format!("git -C {linux} log {range} {FMT}");
        crate::silent_cmd("wsl").args(["bash", "-c", &cmd]).output()
    } else {
        let mut cmd = crate::silent_cmd("git");
        cmd.arg("log");
        match last_commit {
            Some(prev) => {
                cmd.arg(format!("{prev}..HEAD"));
            }
            None => {
                cmd.arg("-n").arg(FIRST_TIME_LIMIT.to_string());
            }
        }
        cmd.arg(FMT);
        cmd.current_dir(root).output()
    };

    let out = match out {
        Ok(o) if o.status.success() => o,
        _ => return vec![],
    };

    String::from_utf8_lossy(&out.stdout)
        .lines()
        .filter_map(|line| {
            let mut parts = line.split('\u{1f}');
            let sha = parts.next()?.trim().to_string();
            if sha.is_empty() {
                return None;
            }
            let subject = parts.next().unwrap_or("").to_string();
            let date = parts.next().unwrap_or("").to_string();
            Some((sha, subject, date))
        })
        .collect()
}

// ── 辅助：任务状态更新 ────────────────────────────────────────────────────────

fn update_task_statuses(root: &Path, changed_files: &[String]) -> Result<usize, String> {
    let modules_dir = root.join(".blueprint").join("modules");
    if !modules_dir.exists() {
        return Ok(0);
    }

    let mut total = 0;
    for entry in std::fs::read_dir(&modules_dir)
        .map_err(|e| e.to_string())?
        .flatten()
    {
        let path = entry.path();
        if path.extension().and_then(|e| e.to_str()) != Some("md") {
            continue;
        }
        let content = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
        let (new_content, count) = mark_tasks_in_progress(&content, changed_files);
        if count > 0 {
            std::fs::write(&path, new_content).map_err(|e| e.to_string())?;
            total += count;
        }
    }
    Ok(total)
}

/// 扫描 markdown 内容，将 files 字段命中 changed_files 的 📋 todo 任务标记为 🔵 in_progress
fn mark_tasks_in_progress(content: &str, changed_files: &[String]) -> (String, usize) {
    let mut lines: Vec<String> = content.lines().map(|l| l.to_string()).collect();
    let mut updated = 0;
    let mut i = 0;

    while i < lines.len() {
        if !lines[i].starts_with("### 📋 ") {
            i += 1;
            continue;
        }

        // 扫描该任务卡的属性行（到下一个 ## 或 ### 为止，最多 20 行）
        let task_head = i;
        let mut files_value: Option<String> = None;
        let mut status_idx: Option<usize> = None;

        let end = (i + 1 + 20).min(lines.len());
        for j in (i + 1)..end {
            if j > i + 1
                && (lines[j].starts_with("### ") || lines[j].starts_with("## "))
            {
                break;
            }
            let t = lines[j].trim().trim_start_matches("- ");
            if let Some(v) = t.strip_prefix("files: ") {
                files_value = Some(v.to_string());
            }
            if t.starts_with("status: ") {
                status_idx = Some(j);
            }
        }

        if let Some(fv) = files_value {
            let task_files: Vec<String> = fv
                .split(',')
                .map(|s| normalize_path(s.trim()))
                .collect();

            let hit = changed_files.iter().any(|cf| {
                let ncf = normalize_path(cf);
                task_files
                    .iter()
                    .any(|tf| tf == &ncf || ncf.ends_with(tf.as_str()) || tf.ends_with(ncf.as_str()))
            });

            if hit {
                // 替换标题前缀
                lines[task_head] = lines[task_head].replacen("### 📋 ", "### 🔵 ", 1);
                // 替换 status 行
                if let Some(si) = status_idx {
                    lines[si] = lines[si].replace("status: todo", "status: in_progress");
                }
                updated += 1;
            }
        }

        i += 1;
    }

    let mut result = lines.join("\n");
    if content.ends_with('\n') {
        result.push('\n');
    }
    (result, updated)
}

fn normalize_path(p: &str) -> String {
    p.trim_start_matches("./").replace('\\', "/").to_string()
}

// ── 内部辅助 ──────────────────────────────────────────────────────────────────

fn list_active_paths() -> Result<Vec<String>, String> {
    let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
    let mut stmt = conn
        .prepare("SELECT project_path FROM blueprint_buffs WHERE status = 'active'")
        .map_err(|e| e.to_string())?;

    let paths = stmt
        .query_map([], |row| row.get(0))
        .map_err(|e| e.to_string())?
        .filter_map(|r| r.ok())
        .collect();
    Ok(paths)
}

fn query_buff(
    conn: &rusqlite::Connection,
    path: &str,
) -> Result<BuffStatus, String> {
    conn.query_row(
        "SELECT project_path, project_id, applied_at, last_sync_at, last_commit, status
         FROM blueprint_buffs WHERE project_path = ?1",
        rusqlite::params![path],
        |row| {
            Ok(BuffStatus {
                project_path: row.get(0)?,
                project_id: row.get(1)?,
                applied_at: row.get(2)?,
                last_sync_at: row.get(3)?,
                last_commit: row.get(4)?,
                status: row.get(5)?,
            })
        },
    )
    .map_err(|e| e.to_string())
}
