use serde::{Deserialize, Serialize};
use std::process::Command;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitStatus {
    pub is_repo: bool,
    pub branch: Option<String>,
    pub upstream: Option<String>,
    pub ahead: u32,
    pub behind: u32,
    pub staged: Vec<GitFileChange>,
    pub unstaged: Vec<GitFileChange>,
    pub untracked: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitFileChange {
    pub path: String,
    pub status: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitBranch {
    pub name: String,
    pub is_current: bool,
    pub is_remote: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GitLogEntry {
    pub hash: String,
    pub short_hash: String,
    pub author: String,
    pub date: String,
    pub message: String,
}

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

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        Err(String::from_utf8_lossy(&output.stderr).to_string())
    }
}

#[tauri::command]
pub fn git_status(working_dir: String) -> Result<GitStatus, String> {
    // Check if it's a git repo
    let is_repo = run_git_command(&working_dir, &["rev-parse", "--git-dir"]).is_ok();

    if !is_repo {
        return Ok(GitStatus {
            is_repo: false,
            branch: None,
            upstream: None,
            ahead: 0,
            behind: 0,
            staged: vec![],
            unstaged: vec![],
            untracked: vec![],
        });
    }

    // Get current branch
    let branch = run_git_command(&working_dir, &["rev-parse", "--abbrev-ref", "HEAD"])
        .ok()
        .map(|s| s.trim().to_string());

    // Get upstream branch
    let upstream = run_git_command(
        &working_dir,
        &["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
    )
    .ok()
    .map(|s| s.trim().to_string());

    // Get ahead/behind counts
    let (ahead, behind) = if upstream.is_some() {
        let rev_list =
            run_git_command(&working_dir, &["rev-list", "--left-right", "--count", "@{u}...HEAD"])
                .unwrap_or_default();
        let parts: Vec<&str> = rev_list.trim().split('\t').collect();
        if parts.len() == 2 {
            (
                parts[1].parse().unwrap_or(0),
                parts[0].parse().unwrap_or(0),
            )
        } else {
            (0, 0)
        }
    } else {
        (0, 0)
    };

    // Get status with porcelain format
    let status_output =
        run_git_command(&working_dir, &["status", "--porcelain=v1"]).unwrap_or_default();

    let mut staged = vec![];
    let mut unstaged = vec![];
    let mut untracked = vec![];

    for line in status_output.lines() {
        if line.len() < 3 {
            continue;
        }

        let index_status = line.chars().next().unwrap_or(' ');
        let worktree_status = line.chars().nth(1).unwrap_or(' ');
        let path = line[3..].to_string();

        // Untracked files
        if index_status == '?' && worktree_status == '?' {
            untracked.push(path);
            continue;
        }

        // Staged changes (index status)
        if index_status != ' ' && index_status != '?' {
            staged.push(GitFileChange {
                path: path.clone(),
                status: match index_status {
                    'M' => "modified".to_string(),
                    'A' => "added".to_string(),
                    'D' => "deleted".to_string(),
                    'R' => "renamed".to_string(),
                    'C' => "copied".to_string(),
                    _ => "unknown".to_string(),
                },
            });
        }

        // Unstaged changes (worktree status)
        if worktree_status != ' ' && worktree_status != '?' {
            unstaged.push(GitFileChange {
                path,
                status: match worktree_status {
                    'M' => "modified".to_string(),
                    'D' => "deleted".to_string(),
                    _ => "unknown".to_string(),
                },
            });
        }
    }

    Ok(GitStatus {
        is_repo: true,
        branch,
        upstream,
        ahead,
        behind,
        staged,
        unstaged,
        untracked,
    })
}

#[tauri::command]
pub fn git_diff(working_dir: String, file_path: Option<String>, staged: bool) -> Result<String, String> {
    let mut args = vec!["diff"];

    if staged {
        args.push("--cached");
    }

    if let Some(ref path) = file_path {
        args.push("--");
        args.push(path);
    }

    run_git_command(&working_dir, &args)
}

#[tauri::command]
pub fn git_branches(working_dir: String) -> Result<Vec<GitBranch>, String> {
    let output = run_git_command(&working_dir, &["branch", "-a", "--format=%(refname:short)\t%(HEAD)"])?;

    let branches: Vec<GitBranch> = output
        .lines()
        .filter_map(|line| {
            let parts: Vec<&str> = line.split('\t').collect();
            if parts.is_empty() {
                return None;
            }

            let name = parts[0].to_string();
            let is_current = parts.get(1).map(|s| *s == "*").unwrap_or(false);
            let is_remote = name.starts_with("remotes/") || name.starts_with("origin/");

            Some(GitBranch {
                name,
                is_current,
                is_remote,
            })
        })
        .collect();

    Ok(branches)
}

#[tauri::command]
pub fn git_checkout(working_dir: String, branch: String) -> Result<String, String> {
    run_git_command(&working_dir, &["checkout", &branch])
}

#[tauri::command]
pub fn git_stage(working_dir: String, file_path: String) -> Result<String, String> {
    run_git_command(&working_dir, &["add", &file_path])
}

#[tauri::command]
pub fn git_unstage(working_dir: String, file_path: String) -> Result<String, String> {
    run_git_command(&working_dir, &["restore", "--staged", &file_path])
}

#[tauri::command]
pub fn git_stage_all(working_dir: String) -> Result<String, String> {
    run_git_command(&working_dir, &["add", "-A"])
}

#[tauri::command]
pub fn git_commit(working_dir: String, message: String) -> Result<String, String> {
    run_git_command(&working_dir, &["commit", "-m", &message])
}

#[tauri::command]
pub fn git_push(working_dir: String) -> Result<String, String> {
    run_git_command(&working_dir, &["push"])
}

#[tauri::command]
pub fn git_pull(working_dir: String) -> Result<String, String> {
    run_git_command(&working_dir, &["pull"])
}

#[tauri::command]
pub fn git_fetch(working_dir: String) -> Result<String, String> {
    run_git_command(&working_dir, &["fetch", "--all"])
}

#[tauri::command]
pub fn git_log(working_dir: String, limit: Option<u32>) -> Result<Vec<GitLogEntry>, String> {
    let limit_str = limit.unwrap_or(10).to_string();
    let output = run_git_command(
        &working_dir,
        &[
            "log",
            &format!("-{}", limit_str),
            "--pretty=format:%H\t%h\t%an\t%ar\t%s",
        ],
    )?;

    let entries: Vec<GitLogEntry> = output
        .lines()
        .filter_map(|line| {
            let parts: Vec<&str> = line.split('\t').collect();
            if parts.len() < 5 {
                return None;
            }

            Some(GitLogEntry {
                hash: parts[0].to_string(),
                short_hash: parts[1].to_string(),
                author: parts[2].to_string(),
                date: parts[3].to_string(),
                message: parts[4..].join("\t"),
            })
        })
        .collect();

    Ok(entries)
}

#[tauri::command]
pub fn git_discard(working_dir: String, file_path: String) -> Result<String, String> {
    run_git_command(&working_dir, &["checkout", "--", &file_path])
}

#[tauri::command]
pub fn git_create_branch(working_dir: String, branch_name: String) -> Result<String, String> {
    run_git_command(&working_dir, &["checkout", "-b", &branch_name])
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::{self, File};
    use std::io::Write;
    use tempfile::TempDir;

    // Helper to create a git repository in a temp directory
    fn create_test_repo() -> TempDir {
        let temp_dir = TempDir::new().unwrap();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Initialize git repo
        run_git_command(&working_dir, &["init"]).unwrap();

        // Configure git user for commits
        run_git_command(&working_dir, &["config", "user.email", "test@example.com"]).unwrap();
        run_git_command(&working_dir, &["config", "user.name", "Test User"]).unwrap();

        // Disable GPG signing for tests (user may have it enabled globally)
        run_git_command(&working_dir, &["config", "commit.gpgsign", "false"]).unwrap();

        temp_dir
    }

    // Helper to create a file in the test repo
    fn create_file(dir: &TempDir, name: &str, content: &str) {
        let file_path = dir.path().join(name);
        let mut file = File::create(file_path).unwrap();
        file.write_all(content.as_bytes()).unwrap();
    }

    // ==================== GitStatus struct tests ====================

    #[test]
    fn test_git_status_serialization() {
        let status = GitStatus {
            is_repo: true,
            branch: Some("main".to_string()),
            upstream: Some("origin/main".to_string()),
            ahead: 2,
            behind: 1,
            staged: vec![GitFileChange {
                path: "file.txt".to_string(),
                status: "modified".to_string(),
            }],
            unstaged: vec![],
            untracked: vec!["new_file.txt".to_string()],
        };

        let json = serde_json::to_string(&status).unwrap();
        assert!(json.contains("\"is_repo\":true"));
        assert!(json.contains("\"branch\":\"main\""));
        assert!(json.contains("\"ahead\":2"));
        assert!(json.contains("\"behind\":1"));
    }

    #[test]
    fn test_git_status_not_a_repo() {
        let status = GitStatus {
            is_repo: false,
            branch: None,
            upstream: None,
            ahead: 0,
            behind: 0,
            staged: vec![],
            unstaged: vec![],
            untracked: vec![],
        };

        let json = serde_json::to_string(&status).unwrap();
        let deserialized: GitStatus = serde_json::from_str(&json).unwrap();
        assert!(!deserialized.is_repo);
        assert!(deserialized.branch.is_none());
    }

    // ==================== GitFileChange struct tests ====================

    #[test]
    fn test_git_file_change_serialization() {
        let change = GitFileChange {
            path: "src/main.rs".to_string(),
            status: "added".to_string(),
        };

        let json = serde_json::to_string(&change).unwrap();
        assert!(json.contains("src/main.rs"));
        assert!(json.contains("added"));

        let deserialized: GitFileChange = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.path, "src/main.rs");
        assert_eq!(deserialized.status, "added");
    }

    // ==================== GitBranch struct tests ====================

    #[test]
    fn test_git_branch_serialization() {
        let branch = GitBranch {
            name: "feature/new-feature".to_string(),
            is_current: true,
            is_remote: false,
        };

        let json = serde_json::to_string(&branch).unwrap();
        assert!(json.contains("feature/new-feature"));
        assert!(json.contains("\"is_current\":true"));
        assert!(json.contains("\"is_remote\":false"));
    }

    #[test]
    fn test_git_branch_remote() {
        let branch = GitBranch {
            name: "origin/main".to_string(),
            is_current: false,
            is_remote: true,
        };

        let json = serde_json::to_string(&branch).unwrap();
        let deserialized: GitBranch = serde_json::from_str(&json).unwrap();
        assert!(deserialized.is_remote);
        assert!(!deserialized.is_current);
    }

    // ==================== GitLogEntry struct tests ====================

    #[test]
    fn test_git_log_entry_serialization() {
        let entry = GitLogEntry {
            hash: "abc123def456".to_string(),
            short_hash: "abc123d".to_string(),
            author: "Hikari".to_string(),
            date: "2 hours ago".to_string(),
            message: "feat: add new feature".to_string(),
        };

        let json = serde_json::to_string(&entry).unwrap();
        assert!(json.contains("abc123def456"));
        assert!(json.contains("Hikari"));
        assert!(json.contains("feat: add new feature"));
    }

    // ==================== git_status integration tests ====================

    #[test]
    fn test_git_status_not_a_git_repo() {
        let temp_dir = TempDir::new().unwrap();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        let result = git_status(working_dir);
        assert!(result.is_ok());

        let status = result.unwrap();
        assert!(!status.is_repo);
        assert!(status.branch.is_none());
        assert!(status.staged.is_empty());
    }

    #[test]
    fn test_git_status_empty_repo() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        let result = git_status(working_dir);
        assert!(result.is_ok());

        let status = result.unwrap();
        assert!(status.is_repo);
        assert!(status.staged.is_empty());
        assert!(status.unstaged.is_empty());
        assert!(status.untracked.is_empty());
    }

    #[test]
    fn test_git_status_with_untracked_file() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Create an untracked file
        create_file(&temp_dir, "untracked.txt", "hello");

        let result = git_status(working_dir);
        assert!(result.is_ok());

        let status = result.unwrap();
        assert!(status.is_repo);
        assert!(status.untracked.contains(&"untracked.txt".to_string()));
    }

    #[test]
    fn test_git_status_with_staged_file() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Create and stage a file
        create_file(&temp_dir, "staged.txt", "hello");
        run_git_command(&working_dir, &["add", "staged.txt"]).unwrap();

        let result = git_status(working_dir);
        assert!(result.is_ok());

        let status = result.unwrap();
        assert!(status.is_repo);
        assert!(!status.staged.is_empty());
        assert_eq!(status.staged[0].path, "staged.txt");
        assert_eq!(status.staged[0].status, "added");
    }

    #[test]
    fn test_git_status_with_modified_file() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Create, stage, and commit a file
        create_file(&temp_dir, "file.txt", "initial content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial commit"]).unwrap();

        // Modify the file
        create_file(&temp_dir, "file.txt", "modified content");

        let result = git_status(working_dir);
        assert!(result.is_ok());

        let status = result.unwrap();
        assert!(status.is_repo);
        assert!(!status.unstaged.is_empty());
        assert_eq!(status.unstaged[0].path, "file.txt");
        assert_eq!(status.unstaged[0].status, "modified");
    }

    // ==================== git_diff integration tests ====================

    #[test]
    fn test_git_diff_no_changes() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        let result = git_diff(working_dir, None, false);
        assert!(result.is_ok());
        assert!(result.unwrap().is_empty());
    }

    #[test]
    fn test_git_diff_with_changes() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Create and commit a file
        create_file(&temp_dir, "file.txt", "initial content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        // Modify the file
        create_file(&temp_dir, "file.txt", "modified content");

        let result = git_diff(working_dir, None, false);
        assert!(result.is_ok());
        let diff = result.unwrap();
        assert!(diff.contains("diff"));
        assert!(diff.contains("file.txt"));
    }

    #[test]
    fn test_git_diff_staged() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Create and commit a file
        create_file(&temp_dir, "file.txt", "initial content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        // Modify and stage the file
        create_file(&temp_dir, "file.txt", "modified content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();

        let result = git_diff(working_dir, None, true);
        assert!(result.is_ok());
        let diff = result.unwrap();
        assert!(diff.contains("diff"));
    }

    #[test]
    fn test_git_diff_specific_file() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Create and commit files
        create_file(&temp_dir, "file1.txt", "content1");
        create_file(&temp_dir, "file2.txt", "content2");
        run_git_command(&working_dir, &["add", "-A"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        // Modify both files
        create_file(&temp_dir, "file1.txt", "modified1");
        create_file(&temp_dir, "file2.txt", "modified2");

        // Get diff for only file1.txt
        let result = git_diff(working_dir, Some("file1.txt".to_string()), false);
        assert!(result.is_ok());
        let diff = result.unwrap();
        assert!(diff.contains("file1.txt"));
        assert!(!diff.contains("file2.txt"));
    }

    // ==================== git_branches integration tests ====================

    #[test]
    fn test_git_branches_single_branch() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Need at least one commit for branches to show
        create_file(&temp_dir, "file.txt", "content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        let result = git_branches(working_dir);
        assert!(result.is_ok());

        let branches = result.unwrap();
        assert!(!branches.is_empty());
        // Should have at least one branch (main or master)
    }

    #[test]
    fn test_git_branches_multiple_branches() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Initial commit
        create_file(&temp_dir, "file.txt", "content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        // Create additional branch
        run_git_command(&working_dir, &["branch", "feature-branch"]).unwrap();

        let result = git_branches(working_dir);
        assert!(result.is_ok());

        let branches = result.unwrap();
        assert!(branches.len() >= 2);
        assert!(branches.iter().any(|b| b.name == "feature-branch"));
    }

    // ==================== git_stage and git_unstage tests ====================

    #[test]
    fn test_git_stage_file() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        create_file(&temp_dir, "file.txt", "content");

        let result = git_stage(working_dir.clone(), "file.txt".to_string());
        assert!(result.is_ok());

        // Verify file is staged
        let status = git_status(working_dir).unwrap();
        assert!(status.staged.iter().any(|f| f.path == "file.txt"));
    }

    #[test]
    fn test_git_unstage_file() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // First, commit a file so we have a HEAD to restore from
        create_file(&temp_dir, "file.txt", "initial content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        // Modify and stage the file
        create_file(&temp_dir, "file.txt", "modified content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();

        let result = git_unstage(working_dir.clone(), "file.txt".to_string());
        assert!(result.is_ok());

        // Verify file is unstaged (should now be in unstaged/modified, not staged)
        let status = git_status(working_dir).unwrap();
        assert!(!status.staged.iter().any(|f| f.path == "file.txt"));
        assert!(status.unstaged.iter().any(|f| f.path == "file.txt"));
    }

    #[test]
    fn test_git_stage_all() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        create_file(&temp_dir, "file1.txt", "content1");
        create_file(&temp_dir, "file2.txt", "content2");

        let result = git_stage_all(working_dir.clone());
        assert!(result.is_ok());

        // Verify all files are staged
        let status = git_status(working_dir).unwrap();
        assert_eq!(status.staged.len(), 2);
    }

    // ==================== git_commit tests ====================

    #[test]
    fn test_git_commit() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        create_file(&temp_dir, "file.txt", "content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();

        let result = git_commit(working_dir.clone(), "test commit message".to_string());
        assert!(result.is_ok());

        // Verify commit was made
        let log = git_log(working_dir, Some(1)).unwrap();
        assert!(!log.is_empty());
        assert!(log[0].message.contains("test commit message"));
    }

    #[test]
    fn test_git_commit_nothing_to_commit() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Need initial commit first
        create_file(&temp_dir, "file.txt", "content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        // Try to commit with nothing staged
        let result = git_commit(working_dir, "empty commit".to_string());
        assert!(result.is_err()); // Should fail because nothing to commit
    }

    // ==================== git_log tests ====================

    #[test]
    fn test_git_log_empty_repo() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        let result = git_log(working_dir, Some(10));
        // May fail on empty repo or return empty
        if let Ok(commits) = result {
            assert!(commits.is_empty());
        }
    }

    #[test]
    fn test_git_log_with_commits() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Make multiple commits
        for i in 1..=3 {
            create_file(&temp_dir, &format!("file{}.txt", i), "content");
            run_git_command(&working_dir, &["add", "-A"]).unwrap();
            run_git_command(&working_dir, &["commit", "-m", &format!("commit {}", i)]).unwrap();
        }

        let result = git_log(working_dir, Some(10));
        assert!(result.is_ok());

        let log = result.unwrap();
        assert_eq!(log.len(), 3);
        assert!(log[0].message.contains("commit 3")); // Most recent first
        assert!(log[2].message.contains("commit 1"));
    }

    #[test]
    fn test_git_log_limit() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Make 5 commits
        for i in 1..=5 {
            create_file(&temp_dir, &format!("file{}.txt", i), "content");
            run_git_command(&working_dir, &["add", "-A"]).unwrap();
            run_git_command(&working_dir, &["commit", "-m", &format!("commit {}", i)]).unwrap();
        }

        // Only get last 2
        let result = git_log(working_dir, Some(2));
        assert!(result.is_ok());

        let log = result.unwrap();
        assert_eq!(log.len(), 2);
    }

    // ==================== git_discard tests ====================

    #[test]
    fn test_git_discard_changes() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Create and commit a file
        create_file(&temp_dir, "file.txt", "original content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        // Modify the file
        create_file(&temp_dir, "file.txt", "modified content");

        // Discard changes
        let result = git_discard(working_dir.clone(), "file.txt".to_string());
        assert!(result.is_ok());

        // Verify file contents are restored
        let content = fs::read_to_string(temp_dir.path().join("file.txt")).unwrap();
        assert_eq!(content, "original content");
    }

    // ==================== git_create_branch tests ====================

    #[test]
    fn test_git_create_branch() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Initial commit required
        create_file(&temp_dir, "file.txt", "content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        let result = git_create_branch(working_dir.clone(), "new-branch".to_string());
        assert!(result.is_ok());

        // Verify branch exists and is current
        let branches = git_branches(working_dir).unwrap();
        assert!(branches.iter().any(|b| b.name == "new-branch" && b.is_current));
    }

    // ==================== git_checkout tests ====================

    #[test]
    fn test_git_checkout() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // Initial commit required
        create_file(&temp_dir, "file.txt", "content");
        run_git_command(&working_dir, &["add", "file.txt"]).unwrap();
        run_git_command(&working_dir, &["commit", "-m", "initial"]).unwrap();

        // Create a branch
        run_git_command(&working_dir, &["branch", "other-branch"]).unwrap();

        // Checkout the branch
        let result = git_checkout(working_dir.clone(), "other-branch".to_string());
        assert!(result.is_ok());

        // Verify current branch
        let branches = git_branches(working_dir).unwrap();
        let current = branches.iter().find(|b| b.is_current);
        assert!(current.is_some());
        assert_eq!(current.unwrap().name, "other-branch");
    }

    // ==================== run_git_command tests ====================

    #[test]
    fn test_run_git_command_success() {
        let temp_dir = create_test_repo();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        let result = run_git_command(&working_dir, &["status"]);
        assert!(result.is_ok());
    }

    #[test]
    fn test_run_git_command_failure() {
        let temp_dir = TempDir::new().unwrap();
        let working_dir = temp_dir.path().to_string_lossy().to_string();

        // This should fail because it's not a git repo
        let result = run_git_command(&working_dir, &["log"]);
        assert!(result.is_err());
    }

    #[test]
    fn test_run_git_command_invalid_dir() {
        let result = run_git_command("/nonexistent/path", &["status"]);
        assert!(result.is_err());
    }
}
