//! Git utilities for staged file discovery.

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

use crate::AphoriaError;

/// Find the root of the git repository containing `from`.
///
/// Walks up the directory tree looking for a `.git` directory.
pub fn find_repo_root(from: &Path) -> Result<PathBuf, AphoriaError> {
    let mut current = from.to_path_buf();

    loop {
        if current.join(".git").exists() {
            return Ok(current);
        }

        if !current.pop() {
            return Err(AphoriaError::NotGitRepo);
        }
    }
}

/// Get the list of staged files from git.
///
/// Uses `git diff --cached --name-only --diff-filter=ACMR` to get:
/// - A: Added files
/// - C: Copied files
/// - M: Modified files
/// - R: Renamed files
///
/// Excludes deleted files (D) since we can't scan them.
pub fn get_staged_files(repo_root: &Path) -> Result<Vec<PathBuf>, AphoriaError> {
    let output = Command::new("git")
        .args(["diff", "--cached", "--name-only", "--diff-filter=ACMR"])
        .current_dir(repo_root)
        .output()
        .map_err(|e| {
            AphoriaError::GitCommand(format!("Failed to execute git diff command: {e}"))
        })?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(AphoriaError::GitCommand(stderr.to_string()));
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    let files: Vec<PathBuf> =
        stdout.lines().filter(|line| !line.is_empty()).map(|line| repo_root.join(line)).collect();

    Ok(files)
}

/// Gets the current git commit hash for a repository.
///
/// Returns None if not in a git repo, git not installed, or any error occurs.
/// This gracefully handles all failures without panicking or logging errors.
///
/// The hash is validated to be 40 hexadecimal characters (SHA-1 format).
pub fn get_current_commit_hash(repo_root: &Path) -> Option<String> {
    let output =
        Command::new("git").args(["-C", repo_root.to_str()?, "rev-parse", "HEAD"]).output().ok()?;

    if !output.status.success() {
        return None;
    }

    let hash = String::from_utf8(output.stdout).ok()?;
    let trimmed = hash.trim();

    // Git commit hashes are 40 hex characters (SHA-1)
    // Validate format to protect against malformed output
    if trimmed.len() == 40 && trimmed.chars().all(|c| c.is_ascii_hexdigit()) {
        Some(trimmed.to_string())
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::TempDir;

    #[test]
    fn test_find_repo_root_not_a_repo() {
        let temp_dir = TempDir::new().expect("create temp dir");
        let result = find_repo_root(temp_dir.path());
        assert!(matches!(result, Err(AphoriaError::NotGitRepo)));
    }

    #[test]
    fn test_find_repo_root_from_subdir() {
        let temp_dir = TempDir::new().expect("create temp dir");
        let root = temp_dir.path();

        // Create a fake .git directory
        fs::create_dir(root.join(".git")).expect("create .git");

        // Create a subdirectory
        let subdir = root.join("src").join("lib");
        fs::create_dir_all(&subdir).expect("create subdir");

        // Should find the repo root from the subdirectory
        let found = find_repo_root(&subdir).expect("find repo root");
        assert_eq!(found, root);
    }

    #[test]
    fn test_get_current_commit_hash_not_a_repo() {
        let temp_dir = TempDir::new().expect("create temp dir");
        let result = get_current_commit_hash(temp_dir.path());
        assert!(result.is_none());
    }

    #[test]
    fn test_get_current_commit_hash_real_repo() {
        // This test only runs if we're in the actual stemedb repo
        // Use the current working directory (should be stemedb root)
        let cwd = std::env::current_dir().expect("get cwd");

        // Check if we're in a git repo
        if cwd.join(".git").exists() {
            let hash = get_current_commit_hash(&cwd);
            assert!(hash.is_some(), "Should get commit hash in stemedb repo");

            let hash_str = hash.unwrap();
            assert_eq!(hash_str.len(), 40, "Git commit hash should be 40 chars");
            assert!(hash_str.chars().all(|c| c.is_ascii_hexdigit()), "Hash should be hex");
        }
    }
}
