//! Git worktree 生命周期管理 — 多 Agent 并行执行的文件隔离基础
//!
//! 设计依据:docs/02-架构设计/专项设计/多Agent并行执行与仲裁合并设计-2026-07-01.md §4.2
//!
//! 每个 SubTask 创建独立 worktree(git worktree add + 新分支),
//! 写操作天然隔离。合并时用 git merge-tree 预检 + git merge 实际合并。

use std::path::{Path, PathBuf};
use std::process::Command;

/// worktree 管理错误
#[derive(Debug)]
pub enum WorktreeError {
    /// Git 命令执行失败(stdout/stderr 含错误信息)
    GitFailed(String),
    /// 工程根目录无 .git(非 Git 工程,应降级串行)
    NotGitRepo,
    /// worktree 目录已存在(可能上次未清理)
    AlreadyExists(PathBuf),
}

impl std::fmt::Display for WorktreeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::GitFailed(msg) => write!(f, "Git 命令失败: {}", msg),
            Self::NotGitRepo => write!(f, "非 Git 工程(无 .git 目录)"),
            Self::AlreadyExists(p) => write!(f, "worktree 目录已存在: {}", p.display()),
        }
    }
}

impl std::error::Error for WorktreeError {}

/// worktree 创建结果
#[derive(Debug, Clone)]
pub struct WorktreeHandle {
    /// worktree 物理路径(SubTask 工具操作的 cwd)
    pub path: PathBuf,
    /// worktree 对应的分支名(merge 时用)
    pub branch: String,
    /// 所属 Plan id(清理时按 plan 批量)
    pub plan_id: String,
    /// SubTask id
    pub subtask_id: String,
}

/// 在指定工程根目录下创建 SubTask 专属 worktree。
///
/// - `project_root`: 工程根目录(含 .git)
/// - `plan_id`: 所属 Plan id
/// - `subtask_id`: SubTask id
/// - `base_branch`: fork 基点分支(plan 分支或主分支)
///
/// 返回 WorktreeHandle(path + branch)。
/// worktree 路径:`{project_root}/.devflow/wt/{plan_id}/{subtask_id}`
/// 分支名:`subtask/{plan_id}/{subtask_id}`
pub fn create_worktree(
    project_root: &Path,
    plan_id: &str,
    subtask_id: &str,
    base_branch: &str,
) -> Result<WorktreeHandle, WorktreeError> {
    if !project_root.join(".git").exists() {
        return Err(WorktreeError::NotGitRepo);
    }

    let wt_path = project_root.join(".devflow/wt").join(plan_id).join(subtask_id);
    if wt_path.exists() {
        return Err(WorktreeError::AlreadyExists(wt_path));
    }

    let branch = format!("subtask/{}/{}", plan_id, subtask_id);

    // git worktree add <path> -b <branch> <base_branch>
    let output = Command::new("git")
        .args([
            "worktree",
            "add",
            wt_path.to_str().unwrap_or(""),
            "-b",
            &branch,
            base_branch,
        ])
        .current_dir(project_root)
        .output()
        .map_err(|e| WorktreeError::GitFailed(format!("git worktree add 执行失败: {}", e)))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(WorktreeError::GitFailed(format!(
            "git worktree add 失败: {}",
            stderr.trim()
        )));
    }

    Ok(WorktreeHandle {
        path: wt_path,
        branch,
        plan_id: plan_id.to_string(),
        subtask_id: subtask_id.to_string(),
    })
}

/// 在 worktree 内提交所有改动(git add -A + git commit)。
///
/// - `wt_path`: worktree 物理路径
/// - `message`: commit 消息
pub fn commit_worktree(wt_path: &Path, message: &str) -> Result<(), WorktreeError> {
    // git add -A
    let add = Command::new("git")
        .args(["add", "-A"])
        .current_dir(wt_path)
        .output()
        .map_err(|e| WorktreeError::GitFailed(format!("git add 失败: {}", e)))?;
    if !add.status.success() {
        return Err(WorktreeError::GitFailed(format!(
            "git add 失败: {}",
            String::from_utf8_lossy(&add.stderr).trim()
        )));
    }

    // git commit -m (允许空提交,--allow-empty 防无改动时报错)
    let commit = Command::new("git")
        .args(["commit", "--allow-empty", "-m", message])
        .current_dir(wt_path)
        .output()
        .map_err(|e| WorktreeError::GitFailed(format!("git commit 失败: {}", e)))?;
    if !commit.status.success() {
        // commit 失败可能因无 user.email 配置(测试环境),尝试自动配
        let stderr = String::from_utf8_lossy(&commit.stderr);
        if stderr.contains("user.email") || stderr.contains("user.name") {
            // 降级:设置 worktree 局部配置(--local 不污染全局 git config)后重试
            Command::new("git")
                .args(["config", "--local", "user.email", "devflow@ai.local"])
                .current_dir(wt_path)
                .output()
                .ok();
            Command::new("git")
                .args(["config", "--local", "user.name", "DevFlow Agent"])
                .current_dir(wt_path)
                .output()
                .ok();
            let retry = Command::new("git")
                .args(["commit", "--allow-empty", "-m", message])
                .current_dir(wt_path)
                .output()
                .map_err(|e| WorktreeError::GitFailed(format!("git commit 重试失败: {}", e)))?;
            if !retry.status.success() {
                return Err(WorktreeError::GitFailed(format!(
                    "git commit 重试仍失败: {}",
                    String::from_utf8_lossy(&retry.stderr).trim()
                )));
            }
            return Ok(());
        }
        return Err(WorktreeError::GitFailed(format!("git commit 失败: {}", stderr.trim())));
    }

    Ok(())
}

/// 将 SubTask 分支 merge 到 plan 分支(在 plan worktree 内执行)。
///
/// 返回 Ok(true) 表示有改动被合并,Ok(false) 表示无改动(空分支)。
/// 返回 Err 表示 merge 冲突。
pub fn merge_branch(
    plan_wt_path: &Path,
    subtask_branch: &str,
) -> Result<MergeResult, WorktreeError> {
    // 用旧版兼容的 merge-tree(base branch → 输出冲突树)
    // --write-tree 需 Git 2.38+,改用三参数 merge-tree <base> <branch1> <branch2>
    // 成功(exit 0)= 无冲突可自动合并;非0且有输出 = 冲突
    let base = "HEAD";
    let precheck = Command::new("git")
        .args(["merge-tree", base, subtask_branch, base])
        .current_dir(plan_wt_path)
        .output()
        .map_err(|e| WorktreeError::GitFailed(format!("git merge-tree 失败: {}", e)))?;

    // merge-tree 输出含 conflict marker(<<<<<<<)时表示有冲突
    let precheck_stdout = String::from_utf8_lossy(&precheck.stdout);
    if precheck_stdout.contains("<<<<<<<") || precheck_stdout.contains("=======") {
        return Ok(MergeResult::Conflict {
            branch: subtask_branch.to_string(),
            details: precheck_stdout.to_string(),
        });
    }

    // 无冲突,执行实际 merge
    let merge = Command::new("git")
        .args(["merge", "--no-edit", subtask_branch])
        .current_dir(plan_wt_path)
        .output()
        .map_err(|e| WorktreeError::GitFailed(format!("git merge 失败: {}", e)))?;

    if !merge.status.success() {
        // merge 冲突(git merge 返回非0)
        let stderr = String::from_utf8_lossy(&merge.stderr);
        let stdout = String::from_utf8_lossy(&merge.stdout);
        // abort 部分合并状态(防 worktree 处于 conflicted 状态)
        let _ = Command::new("git")
            .args(["merge", "--abort"])
            .current_dir(plan_wt_path)
            .output();
        return Ok(MergeResult::Conflict {
            branch: subtask_branch.to_string(),
            details: format!("{}\n{}", stdout, stderr),
        });
    }

    Ok(MergeResult::Clean)
}

/// merge 结果
#[derive(Debug, Clone)]
pub enum MergeResult {
    /// 干净合并(无冲突)
    Clean,
    /// 合并冲突(details 含 conflict 信息)
    Conflict { branch: String, details: String },
}

/// 清理单个 SubTask 的 worktree(git worktree remove --force + 删分支)。
pub fn remove_worktree(project_root: &Path, handle: &WorktreeHandle) -> Result<(), WorktreeError> {
    // git worktree remove --force <path>
    let _ = Command::new("git")
        .args([
            "worktree",
            "remove",
            "--force",
            handle.path.to_str().unwrap_or(""),
        ])
        .current_dir(project_root)
        .output();

    // 删除分支(-D 强制,已 merge 的分支正常删,未 merge 的也删)
    let _ = Command::new("git")
        .args(["branch", "-D", &handle.branch])
        .current_dir(project_root)
        .output();

    Ok(())
}

/// 清理某 Plan 的所有 worktree(Plan 完成/失败/取消时批量清理)。
pub fn remove_plan_worktrees(project_root: &Path, plan_id: &str) -> Result<(), WorktreeError> {
    let plan_wt_dir = project_root.join(".devflow/wt").join(plan_id);
    if !plan_wt_dir.exists() {
        return Ok(());
    }

    // git worktree list 获取该 plan 下所有 worktree
    let list = Command::new("git")
        .args(["worktree", "list", "--porcelain"])
        .current_dir(project_root)
        .output()
        .map_err(|e| WorktreeError::GitFailed(format!("git worktree list 失败: {}", e)))?;

    let list_str = String::from_utf8_lossy(&list.stdout);
    for line in list_str.lines() {
        if let Some(path_str) = line.strip_prefix("worktree ") {
            let path = PathBuf::from(path_str);
            // 仅清理属于本 plan 的 worktree
            if path.starts_with(&plan_wt_dir) {
                let _ = Command::new("git")
                    .args(["worktree", "remove", "--force", path_str])
                    .current_dir(project_root)
                    .output();
            }
        }
    }

    // 删除该 plan 的所有子任务分支
    let branches = Command::new("git")
        .args(["branch", "--list", &format!("subtask/{}/*", plan_id)])
        .current_dir(project_root)
        .output()
        .map_err(|e| WorktreeError::GitFailed(format!("git branch --list 失败: {}", e)))?;

    let branches_str = String::from_utf8_lossy(&branches.stdout);
    for line in branches_str.lines() {
        let branch = line.trim();
        if !branch.is_empty() {
            let _ = Command::new("git")
                .args(["branch", "-D", branch])
                .current_dir(project_root)
                .output();
        }
    }

    // 清理空目录
    let _ = std::fs::remove_dir_all(&plan_wt_dir);

    Ok(())
}

/// 检测工程是否为 Git 仓库(有 .git 目录)。
pub fn is_git_repo(project_root: &Path) -> bool {
    project_root.join(".git").exists()
}

/// 获取工程当前分支名(用于作为 plan worktree 的 fork 基点)。
pub fn current_branch(project_root: &Path) -> Result<String, WorktreeError> {
    let output = Command::new("git")
        .args(["rev-parse", "--abbrev-ref", "HEAD"])
        .current_dir(project_root)
        .output()
        .map_err(|e| WorktreeError::GitFailed(format!("git rev-parse 失败: {}", e)))?;

    if !output.status.success() {
        return Err(WorktreeError::GitFailed(
            String::from_utf8_lossy(&output.stderr).trim().to_string(),
        ));
    }

    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}

/// 创建 Plan worktree(Plan 级工作分支,所有 SubTask 分支 merge 到这里)。
///
/// 返回 (plan_worktree_path, plan_branch)。
pub fn create_plan_worktree(
    project_root: &Path,
    plan_id: &str,
) -> Result<(PathBuf, String), WorktreeError> {
    if !is_git_repo(project_root) {
        return Err(WorktreeError::NotGitRepo);
    }

    let base = current_branch(project_root)?;
    let plan_branch = format!("plan/{}", plan_id);
    let plan_wt_path = project_root.join(".devflow/wt").join(plan_id);

    if plan_wt_path.exists() {
        return Err(WorktreeError::AlreadyExists(plan_wt_path));
    }

    let output = Command::new("git")
        .args([
            "worktree",
            "add",
            plan_wt_path.to_str().unwrap_or(""),
            "-b",
            &plan_branch,
            &base,
        ])
        .current_dir(project_root)
        .output()
        .map_err(|e| WorktreeError::GitFailed(format!("git worktree add(plan) 失败: {}", e)))?;

    if !output.status.success() {
        return Err(WorktreeError::GitFailed(format!(
            "git worktree add(plan) 失败: {}",
            String::from_utf8_lossy(&output.stderr).trim()
        )));
    }

    Ok((plan_wt_path, plan_branch))
}

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

    /// 创建临时 Git 仓库 + 初始 commit
    fn setup_repo() -> tempfile::TempDir {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path();

        Command::new("git").args(["init"]).current_dir(path).output().unwrap();
        Command::new("git").args(["config", "user.email", "test@test.com"]).current_dir(path).output().unwrap();
        Command::new("git").args(["config", "user.name", "test"]).current_dir(path).output().unwrap();

        std::fs::write(path.join("main.rs"), "fn main() {}\n").unwrap();
        Command::new("git").args(["add", "."]).current_dir(path).output().unwrap();
        Command::new("git").args(["commit", "-m", "init"]).current_dir(path).output().unwrap();

        dir
    }

    #[test]
    fn wt_01_create_and_isolate() {
        let repo = setup_repo();
        let root = repo.path();
        let base = current_branch(root).unwrap();

        let handle = create_worktree(root, "plan-test", "A", &base).unwrap();
        assert!(handle.path.exists(), "worktree 目录应存在");
        assert_eq!(handle.branch, "subtask/plan-test/A");

        // 在 worktree 内写文件
        std::fs::write(handle.path.join("new_a.rs"), "// A 的改动").unwrap();
        // 主目录不应有此文件
        assert!(!root.join("new_a.rs").exists(), "主目录不应有 A 的改动");

        // 清理
        remove_worktree(root, &handle).unwrap();
    }

    #[test]
    fn wt_02_parallel_isolation() {
        let repo = setup_repo();
        let root = repo.path();

        let plan_wt = create_plan_worktree(root, "plan-iso").unwrap();
        let plan_branch = plan_wt.1;

        let a = create_worktree(root, "plan-iso", "A", &plan_branch).unwrap();
        let b = create_worktree(root, "plan-iso", "B", &plan_branch).unwrap();

        // A 写 new_a.rs
        std::fs::write(a.path.join("new_a.rs"), "// A").unwrap();
        // B 写 new_b.rs
        std::fs::write(b.path.join("new_b.rs"), "// B").unwrap();

        // A 的 worktree 不应有 B 的文件
        assert!(!a.path.join("new_b.rs").exists(), "A 不应看到 B 的改动");
        // B 的 worktree 不应有 A 的文件
        assert!(!b.path.join("new_a.rs").exists(), "B 不应看到 A 的改动");

        remove_plan_worktrees(root, "plan-iso").unwrap();
    }

    #[test]
    fn wt_03_commit_in_worktree() {
        let repo = setup_repo();
        let root = repo.path();

        let plan_wt = create_plan_worktree(root, "plan-commit").unwrap();
        let a = create_worktree(root, "plan-commit", "A", &plan_wt.1).unwrap();

        // 写文件 + commit
        std::fs::write(a.path.join("feature.rs"), "pub fn feature() {}").unwrap();
        commit_worktree(&a.path, "Add feature").unwrap();

        // 验证分支有新 commit
        let log = Command::new("git")
            .args(["log", "--oneline", "-1"])
            .current_dir(&a.path)
            .output()
            .unwrap();
        let log_str = String::from_utf8_lossy(&log.stdout);
        assert!(log_str.contains("Add feature"), "commit 消息应在 log 中");

        remove_plan_worktrees(root, "plan-commit").unwrap();
    }

    #[test]
    fn wt_04_merge_no_conflict() {
        let repo = setup_repo();
        let root = repo.path();

        let plan_wt = create_plan_worktree(root, "plan-merge").unwrap();
        let plan_wt_path = plan_wt.0.clone();
        let plan_branch = plan_wt.1;

        // A 改 file_a.rs, B 改 file_b.rs(不同文件,无冲突)
        let a = create_worktree(root, "plan-merge", "A", &plan_branch).unwrap();
        std::fs::write(a.path.join("file_a.rs"), "// A").unwrap();
        commit_worktree(&a.path, "A changes").unwrap();

        let b = create_worktree(root, "plan-merge", "B", &plan_branch).unwrap();
        std::fs::write(b.path.join("file_b.rs"), "// B").unwrap();
        commit_worktree(&b.path, "B changes").unwrap();

        // merge A → plan
        let result_a = merge_branch(&plan_wt_path, &a.branch).unwrap();
        assert!(matches!(result_a, MergeResult::Clean), "A merge 应无冲突");

        // merge B → plan
        let result_b = merge_branch(&plan_wt_path, &b.branch).unwrap();
        assert!(matches!(result_b, MergeResult::Clean), "B merge 应无冲突");

        // plan worktree 应同时有 file_a.rs 和 file_b.rs
        assert!(plan_wt_path.join("file_a.rs").exists(), "plan 应含 A 的改动");
        assert!(plan_wt_path.join("file_b.rs").exists(), "plan 应含 B 的改动");

        remove_plan_worktrees(root, "plan-merge").unwrap();
    }

    #[test]
    fn wt_05_merge_conflict_same_file() {
        let repo = setup_repo();
        let root = repo.path();

        // 写一个共享文件
        std::fs::write(root.join("shared.rs"), "line1\nline2\n").unwrap();
        Command::new("git").args(["add", "-A"]).current_dir(root).output().unwrap();
        Command::new("git").args(["commit", "-m", "add shared"]).current_dir(root).output().unwrap();

        let plan_wt = create_plan_worktree(root, "plan-conf").unwrap();
        let plan_wt_path = plan_wt.0.clone();
        let plan_branch = plan_wt.1;

        // A 和 B 改同一文件的同一行
        let a = create_worktree(root, "plan-conf", "A", &plan_branch).unwrap();
        std::fs::write(a.path.join("shared.rs"), "A_version\n").unwrap();
        commit_worktree(&a.path, "A change shared").unwrap();

        let b = create_worktree(root, "plan-conf", "B", &plan_branch).unwrap();
        std::fs::write(b.path.join("shared.rs"), "B_version\n").unwrap();
        commit_worktree(&b.path, "B change shared").unwrap();

        // merge A → 无冲突
        let result_a = merge_branch(&plan_wt_path, &a.branch).unwrap();
        assert!(matches!(result_a, MergeResult::Clean), "首个 merge 应无冲突");

        // merge B → 冲突
        let result_b = merge_branch(&plan_wt_path, &b.branch).unwrap();
        assert!(
            matches!(result_b, MergeResult::Conflict { .. }),
            "同文件同行改应产生冲突"
        );

        remove_plan_worktrees(root, "plan-conf").unwrap();
    }

    #[test]
    fn wt_06_non_git_returns_error() {
        let dir = tempfile::tempdir().unwrap();
        // 不 init git
        let result = create_plan_worktree(dir.path(), "plan-ng");
        assert!(matches!(result, Err(WorktreeError::NotGitRepo)));
    }

    #[test]
    fn wt_07_cleanup_removes_worktree() {
        let repo = setup_repo();
        let root = repo.path();

        let plan_wt = create_plan_worktree(root, "plan-clean").unwrap();
        let a = create_worktree(root, "plan-clean", "A", &plan_wt.1).unwrap();
        assert!(a.path.exists());

        remove_plan_worktrees(root, "plan-clean").unwrap();

        // worktree 目录应被清理
        assert!(!a.path.exists(), "worktree 目录应已清理");
        // plan wt 目录也应被清理
        assert!(!plan_wt.0.exists(), "plan worktree 目录应已清理");
    }
}
