use std::path::Path;
use std::process::Command;

fn run_git(project_path: &str, args: &[&str]) -> Result<String, String> {
    let output = Command::new("git")
        .args(args)
        .current_dir(project_path)
        .output()
        .map_err(|e| format!("Failed to run git: {}", e))?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        Err(format!("git {} failed: {}", args.join(" "), stderr))
    }
}

pub fn create_worktree(
    project_path: &str,
    base_branch: &str,
    artifact_id: i32,
) -> Result<(String, String), String> {
    let orchai_dir = Path::new(project_path).join(".orchai").join("worktrees");
    std::fs::create_dir_all(&orchai_dir)
        .map_err(|e| format!("Failed to create .orchai/worktrees dir: {}", e))?;

    let worktree_name = format!("orchai-{}", artifact_id);
    let worktree_path = orchai_dir.join(&worktree_name);
    let branch_name = format!("orchai/{}", artifact_id);

    let wt_path_str = worktree_path.to_str().ok_or("Invalid worktree path")?;

    run_git(
        project_path,
        &["worktree", "add", wt_path_str, "-b", &branch_name, base_branch],
    )?;

    Ok((wt_path_str.to_string(), branch_name))
}

pub fn get_diff(project_path: &str, base_branch: &str, branch_name: &str) -> Result<String, String> {
    let range = format!("{}...{}", base_branch, branch_name);
    run_git(project_path, &["diff", &range])
}

pub fn list_commits(
    project_path: &str,
    base_branch: &str,
    branch_name: &str,
) -> Result<Vec<String>, String> {
    let range = format!("{}..{}", base_branch, branch_name);
    let output = run_git(project_path, &["log", &range, "--format=%H", "--reverse"])?;
    Ok(output
        .lines()
        .filter(|l| !l.is_empty())
        .map(String::from)
        .collect())
}

pub fn apply_fix(
    project_path: &str,
    base_branch: &str,
    branch_name: &str,
    target_branch: &str,
) -> Result<(), String> {
    let commits = list_commits(project_path, base_branch, branch_name)?;
    if commits.is_empty() {
        return Err("No commits to cherry-pick".to_string());
    }

    let current = run_git(project_path, &["rev-parse", "--abbrev-ref", "HEAD"])?;
    let current = current.trim();

    run_git(project_path, &["checkout", target_branch])?;

    let mut cherry_args = vec!["cherry-pick"];
    let commit_refs: Vec<&str> = commits.iter().map(|s| s.as_str()).collect();
    cherry_args.extend(&commit_refs);

    let result = run_git(project_path, &cherry_args);

    if let Err(e) = &result {
        let _ = run_git(project_path, &["cherry-pick", "--abort"]);
        let _ = run_git(project_path, &["checkout", current]);
        return Err(format!("Cherry-pick failed (conflict?): {}", e));
    }

    run_git(project_path, &["checkout", current])?;

    Ok(())
}

pub fn delete_worktree(
    project_path: &str,
    worktree_path: &str,
    branch_name: &str,
) -> Result<(), String> {
    run_git(project_path, &["worktree", "remove", worktree_path, "--force"])?;
    let _ = run_git(project_path, &["branch", "-D", branch_name]);
    Ok(())
}

pub fn list_local_branches(project_path: &str) -> Result<Vec<String>, String> {
    let output = run_git(project_path, &["branch", "--format=%(refname:short)"])?;
    Ok(output
        .lines()
        .filter(|l| !l.is_empty())
        .map(String::from)
        .collect())
}

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

    fn setup_test_repo() -> tempfile::TempDir {
        let dir = tempfile::tempdir().expect("create temp dir");
        let path = dir.path().to_str().unwrap();

        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(dir.path().join("README.md"), "# Test").unwrap();
        Command::new("git").args(["add", "."]).current_dir(path).output().unwrap();
        Command::new("git")
            .args(["commit", "-m", "init"])
            .current_dir(path)
            .output()
            .unwrap();

        dir
    }

    fn current_branch(path: &str) -> String {
        let output = Command::new("git")
            .args(["rev-parse", "--abbrev-ref", "HEAD"])
            .current_dir(path)
            .output()
            .unwrap();
        assert!(output.status.success());
        String::from_utf8_lossy(&output.stdout).trim().to_string()
    }

    #[test]
    fn test_create_worktree() {
        let dir = setup_test_repo();
        let path = dir.path().to_str().unwrap();
        let base_branch = current_branch(path);

        let (wt_path, branch) = create_worktree(path, &base_branch, 42).unwrap();
        assert!(wt_path.contains("orchai-42"));
        assert_eq!(branch, "orchai/42");
        assert!(Path::new(&wt_path).exists());
    }

    #[test]
    fn test_get_diff_empty() {
        let dir = setup_test_repo();
        let path = dir.path().to_str().unwrap();
        let base_branch = current_branch(path);

        let (_, branch) = create_worktree(path, &base_branch, 1).unwrap();
        let diff = get_diff(path, &base_branch, &branch).unwrap();
        assert!(diff.is_empty(), "No changes yet, diff should be empty");
    }

    #[test]
    fn test_get_diff_with_changes() {
        let dir = setup_test_repo();
        let path = dir.path().to_str().unwrap();
        let base_branch = current_branch(path);

        let (wt_path, branch) = create_worktree(path, &base_branch, 2).unwrap();

        std::fs::write(Path::new(&wt_path).join("fix.txt"), "fixed").unwrap();
        Command::new("git").args(["add", "."]).current_dir(&wt_path).output().unwrap();
        Command::new("git")
            .args(["commit", "-m", "fix"])
            .current_dir(&wt_path)
            .output()
            .unwrap();

        let diff = get_diff(path, &base_branch, &branch).unwrap();
        assert!(diff.contains("fix.txt"));
        assert!(diff.contains("+fixed"));
    }

    #[test]
    fn test_list_commits() {
        let dir = setup_test_repo();
        let path = dir.path().to_str().unwrap();
        let base_branch = current_branch(path);

        let (wt_path, branch) = create_worktree(path, &base_branch, 3).unwrap();

        std::fs::write(Path::new(&wt_path).join("a.txt"), "a").unwrap();
        Command::new("git").args(["add", "."]).current_dir(&wt_path).output().unwrap();
        Command::new("git")
            .args(["commit", "-m", "first"])
            .current_dir(&wt_path)
            .output()
            .unwrap();

        std::fs::write(Path::new(&wt_path).join("b.txt"), "b").unwrap();
        Command::new("git").args(["add", "."]).current_dir(&wt_path).output().unwrap();
        Command::new("git")
            .args(["commit", "-m", "second"])
            .current_dir(&wt_path)
            .output()
            .unwrap();

        let commits = list_commits(path, &base_branch, &branch).unwrap();
        assert_eq!(commits.len(), 2);
    }

    #[test]
    fn test_list_local_branches() {
        let dir = setup_test_repo();
        let path = dir.path().to_str().unwrap();
        let base_branch = current_branch(path);

        create_worktree(path, &base_branch, 10).unwrap();
        let branches = list_local_branches(path).unwrap();
        assert!(branches.contains(&base_branch));
        assert!(branches.contains(&"orchai/10".to_string()));
    }

    #[test]
    fn test_delete_worktree() {
        let dir = setup_test_repo();
        let path = dir.path().to_str().unwrap();
        let base_branch = current_branch(path);

        let (wt_path, branch) = create_worktree(path, &base_branch, 99).unwrap();
        assert!(Path::new(&wt_path).exists());

        delete_worktree(path, &wt_path, &branch).unwrap();
        assert!(!Path::new(&wt_path).exists());

        let branches = list_local_branches(path).unwrap();
        assert!(!branches.contains(&"orchai/99".to_string()));
    }

    #[test]
    fn test_apply_fix() {
        let dir = setup_test_repo();
        let path = dir.path().to_str().unwrap();
        let base_branch = current_branch(path);

        Command::new("git")
            .args(["branch", "feature/test"])
            .current_dir(path)
            .output()
            .unwrap();

        let (wt_path, branch) = create_worktree(path, &base_branch, 7).unwrap();
        std::fs::write(Path::new(&wt_path).join("fix.txt"), "the fix").unwrap();
        Command::new("git").args(["add", "."]).current_dir(&wt_path).output().unwrap();
        Command::new("git")
            .args(["commit", "-m", "apply fix"])
            .current_dir(&wt_path)
            .output()
            .unwrap();

        apply_fix(path, &base_branch, &branch, "feature/test").unwrap();

        Command::new("git")
            .args(["checkout", "feature/test"])
            .current_dir(path)
            .output()
            .unwrap();
        assert!(Path::new(path).join("fix.txt").exists());

        Command::new("git")
            .args(["checkout", &base_branch])
            .current_dir(path)
            .output()
            .unwrap();
    }
}
