use anyhow::{bail, Context, Result};
use git2::{Repository, Signature, StatusOptions, Config, Oid, ObjectType};
use std::path::{Path, PathBuf, Component};
use std::collections::HashMap;
use tempfile;

pub mod changelog;
pub mod commit;
pub mod tag;


fn normalize_path_for_git2(path: &Path) -> PathBuf {
    let mut normalized = path.to_path_buf();
    
    #[cfg(target_os = "windows")]
    {
        let path_str = path.to_string_lossy();
        if path_str.starts_with(r"\\?\") {
            if let Some(stripped) = path_str.strip_prefix(r"\\?\") {
                normalized = PathBuf::from(stripped);
            }
        }
        if path_str.starts_with(r"\\?\UNC\") {
            if let Some(stripped) = path_str.strip_prefix(r"\\?\UNC\") {
                normalized = PathBuf::from(format!(r"\\{}", stripped));
            }
        }
    }
    
    normalized
}

fn get_absolute_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
    let path = path.as_ref();
    
    if path.is_absolute() {
        return Ok(normalize_path_for_git2(path));
    }
    
    let current_dir = std::env::current_dir()
        .with_context(|| "Failed to get current directory")?;
    
    let absolute = current_dir.join(path);
    Ok(normalize_path_for_git2(&absolute))
}

fn resolve_path_without_canonicalize(path: &Path) -> PathBuf {
    let mut components = Vec::new();
    
    for component in path.components() {
        match component {
            Component::ParentDir => {
                if !components.is_empty() && components.last() != Some(&Component::ParentDir) {
                    components.pop();
                } else {
                    components.push(component);
                }
            }
            Component::CurDir => {}
            _ => components.push(component),
        }
    }
    
    let mut result = PathBuf::new();
    for component in components {
        result.push(component.as_os_str());
    }
    
    normalize_path_for_git2(&result)
}

fn try_open_repo_with_git2(path: &Path) -> Result<Repository> {
    let normalized = normalize_path_for_git2(path);
    
    let discover_opts = git2::RepositoryOpenFlags::empty();
    let ceiling_dirs: [&str; 0] = [];
    
    let repo = Repository::open_ext(&normalized, discover_opts, &ceiling_dirs)
        .or_else(|_| Repository::discover(&normalized))
        .or_else(|_| Repository::open(&normalized));
    
    repo.map_err(|e| anyhow::anyhow!("git2 failed: {}", e))
}

fn try_open_repo_with_git_cli(path: &Path) -> Result<Repository> {
    let output = std::process::Command::new("git")
        .args(&["rev-parse", "--show-toplevel"])
        .current_dir(path)
        .output()
        .context("Failed to execute git command")?;
    
    if !output.status.success() {
        bail!("git CLI failed to find repository");
    }
    
    let stdout = String::from_utf8_lossy(&output.stdout);
    let git_root = stdout.trim();
    
    if git_root.is_empty() {
        bail!("git CLI returned empty path");
    }
    
    let git_root_path = PathBuf::from(git_root);
    let normalized = normalize_path_for_git2(&git_root_path);
    
    Repository::open(&normalized)
        .with_context(|| format!("Failed to open repo from git CLI path: {:?}", normalized))
}

fn diagnose_repo_issue(path: &Path) -> String {
    let mut issues = Vec::new();
    
    if !path.exists() {
        issues.push(format!("Path does not exist: {:?}", path));
    } else if !path.is_dir() {
        issues.push(format!("Path is not a directory: {:?}", path));
    }
    
    let git_dir = path.join(".git");
    if git_dir.exists() {
        if git_dir.is_dir() {
            issues.push("Found .git directory".to_string());
            let config_file = git_dir.join("config");
            if config_file.exists() {
                issues.push("Git config file exists".to_string());
            } else {
                issues.push("WARNING: Git config file missing".to_string());
            }
        } else {
            issues.push("Found .git file (submodule or worktree)".to_string());
        }
    } else {
        issues.push("No .git found in current directory".to_string());
        
        let mut current = path;
        let mut depth = 0;
        while let Some(parent) = current.parent() {
            depth += 1;
            if depth > 20 {
                break;
            }
            let parent_git = parent.join(".git");
            if parent_git.exists() {
                issues.push(format!("Found .git in parent directory: {:?}", parent));
                break;
            }
            current = parent;
        }
    }
    
    #[cfg(target_os = "windows")]
    {
        let path_str = path.to_string_lossy();
        if path_str.starts_with(r"\\?\") {
            issues.push("Path has Windows extended-length prefix (\\\\?\\)".to_string());
        }
        if path_str.contains('\\') && path_str.contains('/') {
            issues.push("WARNING: Path has mixed path separators".to_string());
        }
    }
    
    if let Ok(current_dir) = std::env::current_dir() {
        issues.push(format!("Current working directory: {:?}", current_dir));
    }
    
    issues.join("\n  ")
}

pub struct GitRepo {
    repo: Repository,
    path: PathBuf,
    config: Option<Config>,
}

impl GitRepo {
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref();
        
        let absolute_path = get_absolute_path(path)?;
        let resolved_path = resolve_path_without_canonicalize(&absolute_path);
        
        let repo = try_open_repo_with_git2(&resolved_path)
            .or_else(|git2_err| {
                try_open_repo_with_git_cli(&resolved_path)
                    .map_err(|cli_err| {
                        let diagnosis = diagnose_repo_issue(&resolved_path);
                        anyhow::anyhow!(
                            "Failed to open git repository:\n\
                            \n\
                            === git2 Error ===\n  {}\n\
                            \n\
                            === git CLI Error ===\n  {}\n\
                            \n\
                            === Diagnosis ===\n  {}\n\
                            \n\
                            === Suggestions ===\n\
                            1. Ensure you are inside a git repository\n\
                            2. Run: git status (to verify git works)\n\
                            3. Run: git config --global --add safe.directory \"*\"\n\
                            4. Check file permissions",
                            git2_err, cli_err, diagnosis
                        )
                    })
            })?;
        
        let repo_path = repo.workdir()
            .map(|p| p.to_path_buf())
            .unwrap_or_else(|| resolved_path.clone());
        
        let config = repo.config().ok();
        
        Ok(Self {
            repo,
            path: normalize_path_for_git2(&repo_path),
            config,
        })
    }

    /// Get repository path
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Get internal git2 repository
    pub fn inner(&self) -> &Repository {
        &self.repo
    }

    /// Get repository configuration
    pub fn config(&self) -> Option<&Config> {
        self.config.as_ref()
    }

    /// Get a configuration value
    pub fn get_config(&self, key: &str) -> Result<Option<String>> {
        if let Some(ref config) = self.config {
            config.get_string(key).map(Some).map_err(Into::into)
        } else {
            Ok(None)
        }
    }

    /// Get all configuration values matching a pattern
    pub fn get_config_regex(&self, _pattern: &str) -> Result<HashMap<String, String>> {
        Ok(HashMap::new())
    }

    /// Get the configured user name
    pub fn get_user_name(&self) -> Result<String> {
        self.get_config("user.name")?
            .or_else(|| std::env::var("GIT_AUTHOR_NAME").ok())
            .ok_or_else(|| anyhow::anyhow!("User name not configured. Set it with: git config user.name \"Your Name\""))
    }

    /// Get the configured user email
    pub fn get_user_email(&self) -> Result<String> {
        self.get_config("user.email")?
            .or_else(|| std::env::var("GIT_AUTHOR_EMAIL").ok())
            .ok_or_else(|| anyhow::anyhow!("User email not configured. Set it with: git config user.email \"your.email@example.com\""))
    }

    /// Get the configured GPG signing key
    pub fn get_signing_key(&self) -> Result<Option<String>> {
        Ok(self.get_config("user.signingkey")?
            .or_else(|| std::env::var("GIT_SIGNING_KEY").ok()))
    }

    /// Check if commits should be signed by default
    pub fn should_sign_commits(&self) -> bool {
        self.get_config("commit.gpgsign")
            .ok()
            .flatten()
            .and_then(|v| v.parse::<bool>().ok())
            .unwrap_or(false)
    }

    /// Check if tags should be signed by default
    pub fn should_sign_tags(&self) -> bool {
        self.get_config("tag.gpgsign")
            .ok()
            .flatten()
            .and_then(|v| v.parse::<bool>().ok())
            .unwrap_or(false)
    }

    /// Get the GPG program to use
    pub fn get_gpg_program(&self) -> Result<String> {
        if let Some(program) = self.get_config("gpg.program")? {
            return Ok(program);
        }
        
        let default_gpg = if cfg!(windows) {
            "gpg.exe"
        } else {
            "gpg"
        };
        
        Ok(default_gpg.to_string())
    }

    /// Create a signature using repository configuration
    pub fn create_signature(&self) -> Result<Signature<'_>> {
        let name = self.get_user_name()?;
        let email = self.get_user_email()?;
        let time = git2::Time::new(std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64, 0);
        Signature::new(&name, &email, &time).map_err(Into::into)
    }

    /// Check if this is a valid git repository
    pub fn is_valid(&self) -> bool {
        !self.repo.is_bare()
    }

    /// Check if there are uncommitted changes
    pub fn has_changes(&self) -> Result<bool> {
        let statuses = self.repo.statuses(Some(
            StatusOptions::new()
                .include_untracked(true)
                .renames_head_to_index(true)
                .renames_index_to_workdir(true),
        ))?;

        Ok(!statuses.is_empty())
    }

    /// Get staged diff
    pub fn get_staged_diff(&self) -> Result<String> {
        // Use git CLI to get staged diff for better compatibility
        let output = std::process::Command::new("git")
            .args(&["diff", "--cached"])
            .current_dir(&self.path)
            .output()
            .with_context(|| "Failed to get staged diff with git command")?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            bail!("Failed to get staged diff: {}", stderr);
        }

        let diff_text = String::from_utf8_lossy(&output.stdout).to_string();
        Ok(diff_text)
    }

    /// Get staged diff with files sorted by importance
    /// Important files (source code) come first, then config files like Cargo.toml,
    /// then lock files like Cargo.lock
    pub fn get_staged_diff_sorted(&self) -> Result<String> {
        let diff = self.get_staged_diff()?;
        
        if diff.is_empty() {
            return Ok(diff);
        }

        let mut file_diffs = Vec::new();
        let mut current_file_diff = String::new();
        let mut current_file = String::new();

        for line in diff.lines() {
            if line.starts_with("diff --git") {
                // Save previous file diff if any
                if !current_file_diff.is_empty() && !current_file.is_empty() {
                    file_diffs.push((current_file.clone(), current_file_diff.clone()));
                }
                current_file = extract_file_from_diff_line(line);
                current_file_diff = format!("{}\n", line);
            } else {
                current_file_diff.push_str(line);
                current_file_diff.push('\n');
            }
        }

        // Add the last file diff
        if !current_file_diff.is_empty() && !current_file.is_empty() {
            file_diffs.push((current_file, current_file_diff));
        }

        // Sort by file importance
        file_diffs.sort_by(|a, b| {
            let score_a = file_importance_score(&a.0);
            let score_b = file_importance_score(&b.0);
            score_b.cmp(&score_a) // Descending order
        });

        // Combine sorted diffs
        let sorted_diff: String = file_diffs.into_iter()
            .map(|(_, diff)| diff)
            .collect();

        Ok(sorted_diff)
    }
}

/// Extract filename from diff --git line
fn extract_file_from_diff_line(line: &str) -> String {
    // Format: "diff --git a/path/to/file b/path/to/file"
    let parts: Vec<&str> = line.split_whitespace().collect();
    if parts.len() >= 3 {
        // Return the second path (after b/)
        if let Some(path) = parts[2].strip_prefix("b/") {
            return path.to_string();
        }
        // Fallback to first path (after a/)
        if let Some(path) = parts[1].strip_prefix("a/") {
            return path.to_string();
        }
    }
    line.to_string()
}

/// Calculate file importance score
/// Higher score = more important
fn file_importance_score(filename: &str) -> i32 {
    // Priority list for important file types
    let important_extensions = [
        ".rs", ".py", ".js", ".ts", ".tsx", ".jsx", ".go", ".java", ".cpp", ".c", ".rust",
        ".vue", ".svelte", ".html", ".css", ".scss", ".sass", ".less",
    ];
    
    // Config files that are important but less than source code
    let config_files = [
        "Cargo.toml", "package.json", "go.mod", "go.sum", "pom.xml",
        "Makefile", "CMakeLists.txt", "build.gradle", "gradle.properties",
    ];
    
    // Lock files - lowest priority
    let lock_files = [
        "Cargo.lock", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
        "Gemfile.lock", "composer.lock",
    ];

    // Check lock files first (lowest priority)
    for lock in lock_files.iter() {
        if filename.ends_with(lock) {
            return 1;
        }
    }

    // Check config files (medium priority)
    for config in config_files.iter() {
        if filename.ends_with(config) {
            return 2;
        }
    }

    // Check important source files (highest priority)
    for ext in important_extensions.iter() {
        if filename.ends_with(ext) {
            return 3;
        }
    }

    // Default priority for other files
    2
}

impl GitRepo {
    /// Get unstaged diff
    pub fn get_unstaged_diff(&self) -> Result<String> {
        let diff = self.repo.diff_index_to_workdir(None, None)?;

        let mut diff_text = String::new();
        diff.print(git2::DiffFormat::Patch, |_delta, _hunk, line| {
            if let Ok(content) = std::str::from_utf8(line.content()) {
                diff_text.push_str(content);
            }
            true
        })?;

        Ok(diff_text)
    }

    /// Get complete diff (staged + unstaged)
    pub fn get_full_diff(&self) -> Result<String> {
        let staged = self.get_staged_diff().unwrap_or_default();
        let unstaged = self.get_unstaged_diff().unwrap_or_default();

        Ok(format!("{}{}", staged, unstaged))
    }

    /// Get list of changed files
    pub fn get_changed_files(&self) -> Result<Vec<String>> {
        let statuses = self.repo.statuses(Some(
            StatusOptions::new()
                .include_untracked(true)
                .renames_head_to_index(true)
                .renames_index_to_workdir(true),
        ))?;

        let mut files = vec![];
        for entry in statuses.iter() {
            if let Some(path) = entry.path() {
                files.push(path.to_string());
            }
        }

        Ok(files)
    }

    /// Get list of staged files
    pub fn get_staged_files(&self) -> Result<Vec<String>> {
        let statuses = self.repo.statuses(Some(
            StatusOptions::new()
                .include_untracked(false),
        ))?;

        let mut files = vec![];
        for entry in statuses.iter() {
            let status = entry.status();
            if status.is_index_new() || status.is_index_modified() || status.is_index_deleted() || status.is_index_renamed() || status.is_index_typechange() {
                if let Some(path) = entry.path() {
                    files.push(path.to_string());
                }
            }
        }

        Ok(files)
    }

    /// Stage files
    pub fn stage_files<P: AsRef<Path>>(&self, paths: &[P]) -> Result<()> {
        let mut index = self.repo.index()?;

        for path in paths {
            let path = path.as_ref();
            if path.is_absolute() {
                if let Ok(rel_path) = path.strip_prefix(&self.path) {
                    index.add_path(rel_path)?;
                }
            } else {
                index.add_path(path)?;
            }
        }

        index.write()?;
        Ok(())
    }

    /// Stage all changes including subdirectories
    pub fn stage_all(&self) -> Result<()> {
        // Use git command for reliable staging (handles all edge cases)
        let output = std::process::Command::new("git")
            .args(&["add", "-A"])
            .current_dir(&self.path)
            .output()
            .with_context(|| "Failed to stage changes with git command")?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            bail!("Failed to stage changes: {}", stderr);
        }

        // Force refresh the git2 index to pick up changes from git CLI
        let _ = self.repo.index()?.write();

        Ok(())
    }

    /// Unstage files
    pub fn unstage_files<P: AsRef<Path>>(&self, paths: &[P]) -> Result<()> {
        let head = self.repo.head()?;
        let head_commit = head.peel_to_commit()?;
        let head_tree = head_commit.tree()?;

        let mut index = self.repo.index()?;

        for path in paths {
            let path = path.as_ref();
            let rel_path = if path.is_absolute() {
                path.strip_prefix(&self.path)?
            } else {
                path
            };

            if let Ok(_tree_entry) = head_tree.get_path(rel_path) {
                let tree_id = head_tree.id();
                let tree_obj = self.repo.find_object(tree_id, Some(ObjectType::Tree))?;
                let tree = tree_obj.peel_to_tree()?;
                index.read_tree(&tree)?;
            } else {
                index.remove_path(rel_path)?;
            }
        }

        index.write()?;
        Ok(())
    }

    /// Create a commit
    pub fn commit(&self, message: &str, sign: bool) -> Result<Oid> {
        let signature = self.create_signature()?;
        let head = self.repo.head().ok();

        let parents = if let Some(ref head) = head {
            vec![head.peel_to_commit()?]
        } else {
            vec![]
        };

        let parent_refs: Vec<&git2::Commit> = parents.iter().collect();

        let oid = if sign {
            self.commit_signed_with_git2(message, &signature)?
        } else {
            let tree_id = self.repo.index()?.write_tree()?;
            let tree = self.repo.find_tree(tree_id)?;

            self.repo.commit(
                Some("HEAD"),
                &signature,
                &signature,
                message,
                &tree,
                &parent_refs,
            )?
        };

        Ok(oid)
    }

    /// Create a signed commit using git CLI
    fn commit_signed_with_git2(&self, message: &str, _signature: &Signature) -> Result<Oid> {
        let temp_file = tempfile::NamedTempFile::new()?;
        std::fs::write(temp_file.path(), message)?;

        let output = std::process::Command::new("git")
            .args(&["commit", "-S", "-F", temp_file.path().to_str().unwrap()])
            .current_dir(&self.path)
            .output()?;

        if !output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            let stderr = String::from_utf8_lossy(&output.stderr);
            
            let error_msg = if stderr.is_empty() {
                if stdout.is_empty() {
                    "GPG signing failed. Please check:\n\
                     1. GPG signing key is configured (git config --get user.signingkey)\n\
                     2. GPG agent is running\n\
                     3. You can sign commits manually (try: git commit -S -m 'test')".to_string()
                } else {
                    stdout.to_string()
                }
            } else {
                stderr.to_string()
            };
            
            bail!("Failed to create signed commit: {}", error_msg);
        }

        let head = self.repo.head()?;
        Ok(head.target().unwrap())
    }

    /// Get current branch name
    pub fn current_branch(&self) -> Result<String> {
        let head = self.repo.head()?;

        if head.is_branch() {
            let name = head.shorthand()
                .ok_or_else(|| anyhow::anyhow!("Invalid branch name"))?;
            Ok(name.to_string())
        } else {
            bail!("HEAD is not pointing to a branch")
        }
    }

    /// Get current commit hash (short)
    pub fn current_commit_short(&self) -> Result<String> {
        let head = self.repo.head()?;
        let oid = head.target()
            .ok_or_else(|| anyhow::anyhow!("No target for HEAD"))?;
        Ok(oid.to_string()[..8].to_string())
    }

    /// Get current commit hash (full)
    pub fn current_commit(&self) -> Result<String> {
        let head = self.repo.head()?;
        let oid = head.target()
            .ok_or_else(|| anyhow::anyhow!("No target for HEAD"))?;
        Ok(oid.to_string())
    }

    /// Get commit history
    pub fn get_commits(&self, count: usize) -> Result<Vec<CommitInfo>> {
        let mut revwalk = self.repo.revwalk()?;
        revwalk.push_head()?;

        let mut commits = vec![];
        for (i, oid) in revwalk.enumerate() {
            if i >= count {
                break;
            }

            let oid = oid?;
            let commit = self.repo.find_commit(oid)?;

            commits.push(CommitInfo {
                id: oid.to_string(),
                short_id: oid.to_string()[..8].to_string(),
                message: commit.message().unwrap_or("").to_string(),
                author: commit.author().name().unwrap_or("").to_string(),
                email: commit.author().email().unwrap_or("").to_string(),
                time: commit.time().seconds(),
            });
        }

        Ok(commits)
    }

    /// Get commits between two references
    pub fn get_commits_between(&self, from: &str, to: &str) -> Result<Vec<CommitInfo>> {
        let from_obj = self.repo.revparse_single(from)?;
        let to_obj = self.repo.revparse_single(to)?;

        let from_commit = from_obj.peel_to_commit()?;
        let to_commit = to_obj.peel_to_commit()?;

        let mut revwalk = self.repo.revwalk()?;
        revwalk.push(to_commit.id())?;
        revwalk.hide(from_commit.id())?;

        let mut commits = vec![];
        for oid in revwalk {
            let oid = oid?;
            let commit = self.repo.find_commit(oid)?;

            commits.push(CommitInfo {
                id: oid.to_string(),
                short_id: oid.to_string()[..8].to_string(),
                message: commit.message().unwrap_or("").to_string(),
                author: commit.author().name().unwrap_or("").to_string(),
                email: commit.author().email().unwrap_or("").to_string(),
                time: commit.time().seconds(),
            });
        }

        Ok(commits)
    }

    /// Get tags
    pub fn get_tags(&self) -> Result<Vec<TagInfo>> {
        let mut tags = vec![];

        self.repo.tag_foreach(|oid, name| {
            let name = String::from_utf8_lossy(name);
            let name = name.strip_prefix("refs/tags/").unwrap_or(&name);

            if let Ok(commit) = self.repo.find_commit(oid) {
                tags.push(TagInfo {
                    name: name.to_string(),
                    target: oid.to_string(),
                    message: commit.message().unwrap_or("").to_string(),
                    time: commit.time().seconds(),
                });
            }

            true
        })?;

        // Sort tags by time (newest first)
        tags.sort_by(|a, b| b.time.cmp(&a.time));

        Ok(tags)
    }

    /// Create a tag
    pub fn create_tag(&self, name: &str, message: Option<&str>, sign: bool) -> Result<()> {
        let head = self.repo.head()?;
        let target = head.peel_to_commit()?;

        if let Some(msg) = message {
            let sig = self.create_signature()?;

            if sign {
                self.create_signed_tag_with_git2(name, msg, &sig, target.id())?;
            } else {
                self.repo.tag(
                    name,
                    target.as_object(),
                    &sig,
                    msg,
                    false,
                )?;
            }
        } else {
            self.repo.tag(
                name,
                target.as_object(),
                &self.create_signature()?,
                "",
                false,
            )?;
        }

        Ok(())
    }

    /// Create signed tag using git CLI
    fn create_signed_tag_with_git2(&self, name: &str, message: &str, _signature: &Signature, _target_id: Oid) -> Result<()> {
        let output = std::process::Command::new("git")
            .args(&["tag", "-s", name, "-m", message])
            .current_dir(&self.path)
            .output()?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            bail!("Failed to create signed tag: {}", stderr);
        }

        Ok(())
    }

    /// Create GPG signature for arbitrary content
    fn create_gpg_signature_for_content(&self, _content: &str, _gpg_program: &str, _signing_key: &str) -> Result<String> {
        Ok(String::new())
    }

    /// Delete a tag
    pub fn delete_tag(&self, name: &str) -> Result<()> {
        self.repo.tag_delete(name)?;
        Ok(())
    }

    /// Push to remote
    pub fn push(&self, remote: &str, refspec: &str) -> Result<()> {
        let output = std::process::Command::new("git")
            .args(&["push", remote, refspec])
            .current_dir(&self.path)
            .output()?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            bail!("Push failed: {}", stderr);
        }

        Ok(())
    }

    /// Get remote URL
    pub fn get_remote_url(&self, remote: &str) -> Result<String> {
        let remote_obj = self.repo.find_remote(remote)?;
        let url = remote_obj.url()
            .ok_or_else(|| anyhow::anyhow!("Remote has no URL"))?;
        Ok(url.to_string())
    }

    /// Check if working directory is clean
    pub fn is_clean(&self) -> Result<bool> {
        Ok(!self.has_changes()?)
    }

    /// Get repository status summary
    pub fn status_summary(&self) -> Result<StatusSummary> {
        // Use git CLI for more reliable status detection
        let output = std::process::Command::new("git")
            .args(&["status", "--porcelain"])
            .current_dir(&self.path)
            .output()
            .with_context(|| "Failed to get status with git command")?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            bail!("Failed to get status: {}", stderr);
        }

        let stdout = String::from_utf8_lossy(&output.stdout);
        let mut staged = 0;
        let mut unstaged = 0;
        let mut untracked = 0;
        let mut conflicted = 0;

        for line in stdout.lines() {
            if line.len() >= 2 {
                let index_status = line.chars().next().unwrap();
                let worktree_status = line.chars().nth(1).unwrap();

                // Staged changes (first column not space)
                if index_status != ' ' && index_status != '?' {
                    staged += 1;
                }

                // Unstaged changes (second column not space)
                if worktree_status != ' ' && worktree_status != '?' {
                    unstaged += 1;
                }

                // Untracked files (both columns are ?)
                if index_status == '?' && worktree_status == '?' {
                    untracked += 1;
                }

                // Conflicted files (both columns are U or DD, AA, etc.)
                if (index_status == 'U' || worktree_status == 'U') ||
                   (index_status == 'A' && worktree_status == 'A') ||
                   (index_status == 'D' && worktree_status == 'D') {
                    conflicted += 1;
                }
            }
        }

        Ok(StatusSummary {
            staged,
            unstaged,
            untracked,
            conflicted,
            clean: staged == 0 && unstaged == 0 && untracked == 0 && conflicted == 0,
        })
    }
}

/// Commit information
#[derive(Debug, Clone)]
pub struct CommitInfo {
    pub id: String,
    pub short_id: String,
    pub message: String,
    pub author: String,
    pub email: String,
    pub time: i64,
}

impl CommitInfo {
    /// Get commit message subject (first line)
    pub fn subject(&self) -> &str {
        self.message.lines().next().unwrap_or("")
    }

    /// Get commit type from conventional commit
    pub fn commit_type(&self) -> Option<String> {
        let subject = self.subject();
        if let Some(colon) = subject.find(':') {
            let type_part = &subject[..colon];
            let type_name = type_part.split('(').next()?;
            Some(type_name.to_string())
        } else {
            None
        }
    }
}

/// Tag information
#[derive(Debug, Clone)]
pub struct TagInfo {
    pub name: String,
    pub target: String,
    pub message: String,
    pub time: i64,
}

/// Repository status summary
#[derive(Debug, Clone)]
pub struct StatusSummary {
    pub staged: usize,
    pub unstaged: usize,
    pub untracked: usize,
    pub conflicted: usize,
    pub clean: bool,
}

impl StatusSummary {
    /// Format as human-readable string
    pub fn format(&self) -> String {
        if self.clean {
            "working tree clean".to_string()
        } else {
            let mut parts = vec![];
            if self.staged > 0 {
                parts.push(format!("{} staged", self.staged));
            }
            if self.unstaged > 0 {
                parts.push(format!("{} unstaged", self.unstaged));
            }
            if self.untracked > 0 {
                parts.push(format!("{} untracked", self.untracked));
            }
            if self.conflicted > 0 {
                parts.push(format!("{} conflicted", self.conflicted));
            }
            parts.join(", ")
        }
    }
}

pub fn find_repo<P: AsRef<Path>>(start_path: P) -> Result<GitRepo> {
    let start_path = start_path.as_ref();
    
    let absolute_start = get_absolute_path(start_path)?;
    let resolved_start = resolve_path_without_canonicalize(&absolute_start);
    
    if let Ok(repo) = GitRepo::open(&resolved_start) {
        return Ok(repo);
    }
    
    let mut current = resolved_start.as_path();
    let mut attempted_paths = vec![current.to_string_lossy().to_string()];
    
    let max_depth = 50;
    let mut depth = 0;
    
    while let Some(parent) = current.parent() {
        depth += 1;
        if depth > max_depth {
            break;
        }
        
        attempted_paths.push(parent.to_string_lossy().to_string());
        
        if let Ok(repo) = GitRepo::open(parent) {
            return Ok(repo);
        }
        current = parent;
    }
    
    if let Ok(output) = std::process::Command::new("git")
        .args(&["rev-parse", "--show-toplevel"])
        .current_dir(&resolved_start)
        .output()
    {
        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            let git_root = stdout.trim();
            if !git_root.is_empty() {
                if let Ok(repo) = GitRepo::open(git_root) {
                    return Ok(repo);
                }
            }
        }
    }
    
    let diagnosis = diagnose_repo_issue(&resolved_start);
    
    bail!(
        "No git repository found.\n\
        \n\
        === Starting Path ===\n  {:?}\n\
        \n\
        === Paths Attempted ===\n  {}\n\
        \n\
        === Current Directory ===\n  {:?}\n\
        \n\
        === Diagnosis ===\n  {}\n\
        \n\
        === Suggestions ===\n\
        1. Ensure you are inside a git repository (run: git status)\n\
        2. Initialize a new repo: git init\n\
        3. Clone an existing repo: git clone <url>\n\
        4. Check if .git directory exists and is accessible",
        resolved_start,
        attempted_paths.join("\n  "),
        std::env::current_dir().unwrap_or_default(),
        diagnosis
    )
}

/// Check if path is inside a git repository
pub fn is_git_repo<P: AsRef<Path>>(path: P) -> bool {
    find_repo(path).is_ok()
}

/// Git configuration helper for managing user settings
pub struct GitConfigHelper<'a> {
    repo: Option<&'a Repository>,
    global: bool,
}

impl<'a> GitConfigHelper<'a> {
    /// Create a helper for repository-level configuration
    pub fn for_repo(repo: &'a Repository) -> Self {
        Self {
            repo: Some(repo),
            global: false,
        }
    }

    /// Create a helper for global configuration
    pub fn for_global() -> Result<Self> {
        let _config = git2::Config::open_default()?;
        Ok(Self {
            repo: None,
            global: true,
        })
    }

    /// Get configuration value
    pub fn get(&self, key: &str) -> Result<Option<String>> {
        let config = if self.global {
            git2::Config::open_default()?
        } else if let Some(repo) = self.repo {
            repo.config()?
        } else {
            return Ok(None);
        };

        config.get_string(key).map(Some).map_err(Into::into)
    }

    /// Set configuration value
    pub fn set(&self, key: &str, value: &str) -> Result<()> {
        let mut config = if self.global {
            git2::Config::open_default()?
        } else if let Some(repo) = self.repo {
            repo.config()?
        } else {
            bail!("No configuration available");
        };

        config.set_str(key, value)?;
        Ok(())
    }

    /// Remove configuration value
    pub fn remove(&self, key: &str) -> Result<()> {
        let mut config = if self.global {
            git2::Config::open_default()?
        } else if let Some(repo) = self.repo {
            repo.config()?
        } else {
            bail!("No configuration available");
        };

        config.remove(key)?;
        Ok(())
    }

    /// Get all user configuration
    pub fn get_user_config(&self) -> Result<UserConfig> {
        Ok(UserConfig {
            name: self.get("user.name")?,
            email: self.get("user.email")?,
            signing_key: self.get("user.signingkey")?,
            ssh_command: self.get("core.sshCommand")?,
        })
    }

    /// Set all user configuration
    pub fn set_user_config(&self, config: &UserConfig) -> Result<()> {
        if let Some(ref name) = config.name {
            self.set("user.name", name)?;
        }
        if let Some(ref email) = config.email {
            self.set("user.email", email)?;
        }
        if let Some(ref key) = config.signing_key {
            self.set("user.signingkey", key)?;
        }
        if let Some(ref cmd) = config.ssh_command {
            self.set("core.sshCommand", cmd)?;
        }
        Ok(())
    }
}

/// Configuration source indicator
#[derive(Debug, Clone, PartialEq)]
pub enum ConfigSource {
    Local,
    Global,
    NotSet,
}

impl std::fmt::Display for ConfigSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConfigSource::Local => write!(f, "local"),
            ConfigSource::Global => write!(f, "global"),
            ConfigSource::NotSet => write!(f, "not set"),
        }
    }
}

/// Single configuration entry with source information
#[derive(Debug, Clone)]
pub struct ConfigEntry {
    pub value: Option<String>,
    pub source: ConfigSource,
    pub local_value: Option<String>,
    pub global_value: Option<String>,
}

impl ConfigEntry {
    pub fn new(local: Option<String>, global: Option<String>) -> Self {
        let (value, source) = match (&local, &global) {
            (Some(_), _) => (local.clone(), ConfigSource::Local),
            (None, Some(_)) => (global.clone(), ConfigSource::Global),
            (None, None) => (None, ConfigSource::NotSet),
        };
        Self {
            value,
            source,
            local_value: local,
            global_value: global,
        }
    }

    pub fn is_set(&self) -> bool {
        self.value.is_some()
    }

    pub fn is_local(&self) -> bool {
        self.source == ConfigSource::Local
    }

    pub fn is_global(&self) -> bool {
        self.source == ConfigSource::Global
    }
}

/// Merged user configuration with local/global source tracking
#[derive(Debug, Clone)]
pub struct MergedUserConfig {
    pub name: ConfigEntry,
    pub email: ConfigEntry,
    pub signing_key: ConfigEntry,
    pub ssh_command: ConfigEntry,
    pub commit_gpgsign: ConfigEntry,
    pub tag_gpgsign: ConfigEntry,
}

impl MergedUserConfig {
    pub fn from_repo(repo: &Repository) -> Result<Self> {
        let local_config = repo.config().ok();
        let global_config = git2::Config::open_default().ok();

        let get_entry = |key: &str| -> ConfigEntry {
            let local = local_config.as_ref().and_then(|c| c.get_string(key).ok());
            let global = global_config.as_ref().and_then(|c| c.get_string(key).ok());
            ConfigEntry::new(local, global)
        };

        Ok(Self {
            name: get_entry("user.name"),
            email: get_entry("user.email"),
            signing_key: get_entry("user.signingkey"),
            ssh_command: get_entry("core.sshCommand"),
            commit_gpgsign: get_entry("commit.gpgsign"),
            tag_gpgsign: get_entry("tag.gpgsign"),
        })
    }

    pub fn is_complete(&self) -> bool {
        self.name.is_set() && self.email.is_set()
    }

    pub fn has_local_overrides(&self) -> bool {
        self.name.is_local() || self.email.is_local() || self.signing_key.is_local() || self.ssh_command.is_local()
    }
}

/// User configuration for git
#[derive(Debug, Clone)]
pub struct UserConfig {
    pub name: Option<String>,
    pub email: Option<String>,
    pub signing_key: Option<String>,
    pub ssh_command: Option<String>,
}

impl UserConfig {
    /// Check if configuration is complete
    pub fn is_complete(&self) -> bool {
        self.name.is_some() && self.email.is_some()
    }

    /// Compare with another configuration
    pub fn compare(&self, other: &UserConfig) -> Vec<ConfigDiff> {
        let mut diffs = vec![];

        if self.name != other.name {
            diffs.push(ConfigDiff {
                key: "user.name".to_string(),
                left: self.name.clone().unwrap_or_else(|| "<not set>".to_string()),
                right: other.name.clone().unwrap_or_else(|| "<not set>".to_string()),
            });
        }

        if self.email != other.email {
            diffs.push(ConfigDiff {
                key: "user.email".to_string(),
                left: self.email.clone().unwrap_or_else(|| "<not set>".to_string()),
                right: other.email.clone().unwrap_or_else(|| "<not set>".to_string()),
            });
        }

        if self.signing_key != other.signing_key {
            diffs.push(ConfigDiff {
                key: "user.signingkey".to_string(),
                left: self.signing_key.clone().unwrap_or_else(|| "<not set>".to_string()),
                right: other.signing_key.clone().unwrap_or_else(|| "<not set>".to_string()),
            });
        }

        diffs
    }
}

/// Configuration difference
#[derive(Debug, Clone)]
pub struct ConfigDiff {
    pub key: String,
    pub left: String,
    pub right: String,
}
