use regex::Regex;
use std::error::Error;
use std::fmt;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;

// Define a custom error type for git operations
#[derive(Debug)]
pub enum GitError {
    GitNotInstalled(std::io::Error),
    InvalidUrl(String),
    InvalidBasePath(String),
    HomeDirectoryNotFound(std::env::VarError),
    FileSystemError(std::io::Error),
    GitCommandFailed(String),
    CommandExecutionError(std::io::Error),
    NoRepositoriesFound,
    RepositoryNotFound(String),
    MultipleRepositoriesFound(String, usize),
    NotAGitRepository(String),
    LocalChangesExist(String),
}

// Implement Display for GitError
impl fmt::Display for GitError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            GitError::GitNotInstalled(e) => write!(f, "Git is not installed: {}", e),
            GitError::InvalidUrl(url) => write!(f, "Could not parse git URL: {}", url),
            GitError::InvalidBasePath(path) => write!(f, "Invalid base path: {}", path),
            GitError::HomeDirectoryNotFound(e) => {
                write!(f, "Could not determine home directory: {}", e)
            }
            GitError::FileSystemError(e) => write!(f, "Error creating directory structure: {}", e),
            GitError::GitCommandFailed(e) => write!(f, "{}", e),
            GitError::CommandExecutionError(e) => write!(f, "Error executing command: {}", e),
            GitError::NoRepositoriesFound => write!(f, "No repositories found"),
            GitError::RepositoryNotFound(pattern) => {
                write!(f, "No repositories found matching '{}'", pattern)
            }
            GitError::MultipleRepositoriesFound(pattern, count) => write!(
                f,
                "Multiple repositories ({}) found matching '{}'. Use '*' suffix for multiple matches.",
                count, pattern
            ),
            GitError::NotAGitRepository(path) => write!(f, "Not a git repository at {}", path),
            GitError::LocalChangesExist(path) => {
                write!(f, "Repository at {} has local changes", path)
            }
        }
    }
}

// Implement Error trait for GitError
impl Error for GitError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            GitError::GitNotInstalled(e) => Some(e),
            GitError::HomeDirectoryNotFound(e) => Some(e),
            GitError::FileSystemError(e) => Some(e),
            GitError::CommandExecutionError(e) => Some(e),
            _ => None,
        }
    }
}

/// Parsed git URL information including optional branch and subpath.
#[derive(Debug, Clone, Default)]
pub struct ParsedGitUrl {
    /// The server name (e.g., "github.com")
    pub server: String,
    /// The account or organization name (e.g., "username")
    pub account: String,
    /// The repository name (e.g., "repo")
    pub repo: String,
    /// The branch name if specified in URL (e.g., "main", "development")
    pub branch: Option<String>,
    /// The subpath within the repository if specified (e.g., "docs", "src/lib")
    pub subpath: Option<String>,
}

impl ParsedGitUrl {
    /// Returns the base git URL for cloning (without branch/subpath info).
    pub fn clone_url(&self) -> String {
        format!("https://{}/{}/{}", self.server, self.account, self.repo)
    }

    /// Returns true if this parsed URL is valid (has server, account, and repo).
    pub fn is_valid(&self) -> bool {
        !self.server.is_empty() && !self.account.is_empty() && !self.repo.is_empty()
    }
}

/// Parses a git URL to extract the server, account, and repository name.
///
/// # Arguments
///
/// * `url` - The URL of the git repository to parse. Can be in HTTPS format
///   (https://github.com/username/repo.git) or SSH format (git@github.com:username/repo.git).
///
/// # Returns
///
/// A tuple containing:
/// * `server` - The server name (e.g., "github.com")
/// * `account` - The account or organization name (e.g., "username")
/// * `repo` - The repository name (e.g., "repo")
///
/// If the URL cannot be parsed, all three values will be empty strings.
pub fn parse_git_url(url: &str) -> (String, String, String) {
    let parsed = parse_git_url_extended(url);
    (parsed.server, parsed.account, parsed.repo)
}

/// Parses a git URL to extract full information including branch and subpath.
///
/// # Arguments
///
/// * `url` - The URL of the git repository to parse. Supports multiple formats:
///   - Basic HTTPS: `https://github.com/username/repo`
///   - Basic SSH: `git@github.com:username/repo.git`
///   - Gitea/Forgejo with branch: `https://server/account/repo/src/branch/main`
///   - Gitea/Forgejo with branch and path: `https://server/account/repo/src/branch/main/docs`
///   - GitHub with branch: `https://github.com/account/repo/tree/main`
///   - GitHub with branch and path: `https://github.com/account/repo/tree/main/src/lib`
///   - GitLab with branch: `https://gitlab.com/account/repo/-/tree/main`
///   - GitLab with branch and path: `https://gitlab.com/account/repo/-/tree/main/src`
///
/// # Returns
///
/// A `ParsedGitUrl` struct containing server, account, repo, and optionally branch and subpath.
pub fn parse_git_url_extended(url: &str) -> ParsedGitUrl {
    let mut result = ParsedGitUrl::default();

    // Gitea/Forgejo format: https://server/account/repo/src/branch/BRANCH/path
    let gitea_re =
        Regex::new(r"https?://([^/]+)/([^/]+)/([^/]+)/src/branch/([^/]+)(?:/(.+))?").unwrap();

    // GitHub format: https://github.com/account/repo/tree/BRANCH/path
    let github_re = Regex::new(r"https?://([^/]+)/([^/]+)/([^/]+)/tree/([^/]+)(?:/(.+))?").unwrap();

    // GitLab format: https://gitlab.com/account/repo/-/tree/BRANCH/path
    let gitlab_re =
        Regex::new(r"https?://([^/]+)/([^/]+)/([^/]+)/-/tree/([^/]+)(?:/(.+))?").unwrap();

    // Basic HTTP(S) URL format: https://github.com/username/repo.git
    let https_re = Regex::new(r"https?://([^/]+)/([^/]+)/([^/\.]+)(?:\.git)?").unwrap();

    // SSH URL format: git@github.com:username/repo.git
    let ssh_re = Regex::new(r"git@([^:]+):([^/]+)/([^/\.]+)(?:\.git)?").unwrap();

    // SSH URL format with ssh:// prefix: ssh://git@server/account/repo.git
    let ssh_url_re = Regex::new(r"ssh://git@([^/]+)/([^/]+)/([^/\.]+)(?:\.git)?").unwrap();

    // Try Gitea/Forgejo format first (most specific)
    if let Some(caps) = gitea_re.captures(url) {
        result.server = caps.get(1).map_or("", |m| m.as_str()).to_string();
        result.account = caps.get(2).map_or("", |m| m.as_str()).to_string();
        result.repo = caps.get(3).map_or("", |m| m.as_str()).to_string();
        result.branch = caps.get(4).map(|m| m.as_str().to_string());
        result.subpath = caps.get(5).map(|m| m.as_str().to_string());
        return result;
    }

    // Try GitHub format
    if let Some(caps) = github_re.captures(url) {
        result.server = caps.get(1).map_or("", |m| m.as_str()).to_string();
        result.account = caps.get(2).map_or("", |m| m.as_str()).to_string();
        result.repo = caps.get(3).map_or("", |m| m.as_str()).to_string();
        result.branch = caps.get(4).map(|m| m.as_str().to_string());
        result.subpath = caps.get(5).map(|m| m.as_str().to_string());
        return result;
    }

    // Try GitLab format
    if let Some(caps) = gitlab_re.captures(url) {
        result.server = caps.get(1).map_or("", |m| m.as_str()).to_string();
        result.account = caps.get(2).map_or("", |m| m.as_str()).to_string();
        result.repo = caps.get(3).map_or("", |m| m.as_str()).to_string();
        result.branch = caps.get(4).map(|m| m.as_str().to_string());
        result.subpath = caps.get(5).map(|m| m.as_str().to_string());
        return result;
    }

    // Try basic HTTPS format
    if let Some(caps) = https_re.captures(url) {
        result.server = caps.get(1).map_or("", |m| m.as_str()).to_string();
        result.account = caps.get(2).map_or("", |m| m.as_str()).to_string();
        result.repo = caps.get(3).map_or("", |m| m.as_str()).to_string();
        return result;
    }

    // Try SSH format (git@server:account/repo)
    if let Some(caps) = ssh_re.captures(url) {
        result.server = caps.get(1).map_or("", |m| m.as_str()).to_string();
        result.account = caps.get(2).map_or("", |m| m.as_str()).to_string();
        result.repo = caps.get(3).map_or("", |m| m.as_str()).to_string();
        return result;
    }

    // Try SSH URL format (ssh://git@server/account/repo)
    if let Some(caps) = ssh_url_re.captures(url) {
        result.server = caps.get(1).map_or("", |m| m.as_str()).to_string();
        result.account = caps.get(2).map_or("", |m| m.as_str()).to_string();
        result.repo = caps.get(3).map_or("", |m| m.as_str()).to_string();
        return result;
    }

    result
}

/// Checks if git is installed on the system.
///
/// # Returns
///
/// * `Ok(())` - If git is installed
/// * `Err(GitError)` - If git is not installed
fn check_git_installed() -> Result<(), GitError> {
    Command::new("git")
        .arg("--version")
        .output()
        .map_err(GitError::GitNotInstalled)?;
    Ok(())
}

/// Builder for cloning git repositories with advanced options.
///
/// Use this builder to clone repositories with specific branches, reset local changes,
/// and get the path to a specific subdirectory within the repository.
///
/// # Examples
///
/// ```rust,no_run
/// use herolib_git::{GitTree, GitCloneBuilder};
///
/// let git_tree = GitTree::new("/tmp/repos").unwrap();
///
/// // Clone and get path to docs folder on main branch
/// let docs_path = git_tree
///     .get_path("https://forge.ourworld.tf/geomind_research/herolib_rust/src/branch/main/docs")
///     .unwrap();
///
/// // Clone with explicit branch override
/// let path = git_tree
///     .clone_repo("https://github.com/user/repo")
///     .branch("development")
///     .reset()  // Discard any local changes
///     .pull()   // Pull latest changes
///     .execute()
///     .unwrap();
///
/// // Get path to specific subdirectory
/// let src_path = git_tree
///     .clone_repo("https://github.com/user/repo")
///     .branch("feature-branch")
///     .subpath("src/lib")
///     .reset()
///     .execute()
///     .unwrap();
/// ```
pub struct GitCloneBuilder<'a> {
    git_tree: &'a GitTree,
    url: String,
    branch: Option<String>,
    subpath: Option<String>,
    reset: bool,
    pull: bool,
}

impl<'a> GitCloneBuilder<'a> {
    /// Creates a new GitCloneBuilder.
    fn new(git_tree: &'a GitTree, url: &str) -> Self {
        // Parse extended URL to extract branch and subpath
        let parsed = parse_git_url_extended(url);

        GitCloneBuilder {
            git_tree,
            url: if parsed.is_valid() {
                parsed.clone_url()
            } else {
                url.to_string()
            },
            branch: parsed.branch,
            subpath: parsed.subpath,
            reset: false,
            pull: false,
        }
    }

    /// Sets the branch to checkout.
    ///
    /// This overrides any branch specified in the URL.
    pub fn branch(mut self, branch: &str) -> Self {
        self.branch = Some(branch.to_string());
        self
    }

    /// Sets the subpath within the repository to return.
    ///
    /// This overrides any subpath specified in the URL.
    pub fn subpath(mut self, subpath: &str) -> Self {
        self.subpath = Some(subpath.to_string());
        self
    }

    /// Enables reset mode - discards all local changes before operations.
    ///
    /// This performs a `git reset --hard HEAD` and `git clean -fd` to ensure
    /// the repository is in a clean state.
    pub fn reset(mut self) -> Self {
        self.reset = true;
        self
    }

    /// Enables pull mode - pulls latest changes after clone/checkout.
    pub fn pull(mut self) -> Self {
        self.pull = true;
        self
    }

    /// Executes the clone/checkout operation and returns the path.
    ///
    /// # Returns
    ///
    /// * `Ok(String)` - The path to the repository (or subpath if specified)
    /// * `Err(GitError)` - If the operation failed
    pub fn execute(self) -> Result<String, GitError> {
        let parsed = parse_git_url_extended(&self.url);
        if !parsed.is_valid() {
            return Err(GitError::InvalidUrl(self.url));
        }

        // Determine the clone path
        let clone_path = format!(
            "{}/{}/{}/{}",
            self.git_tree.base_path, parsed.server, parsed.account, parsed.repo
        );
        let clone_dir = Path::new(&clone_path);

        // Check if repo already exists
        let repo_exists = clone_dir.exists();

        if !repo_exists {
            // Create parent directory
            if let Some(parent) = clone_dir.parent() {
                fs::create_dir_all(parent).map_err(GitError::FileSystemError)?;
            }

            // Clone the repository with branch if specified
            let mut args = vec!["clone", "--depth", "1"];

            if let Some(ref branch) = self.branch {
                args.push("-b");
                args.push(branch);
            }

            args.push(&self.url);
            args.push(&clone_path);

            let output = Command::new("git")
                .args(&args)
                .output()
                .map_err(GitError::CommandExecutionError)?;

            if !output.status.success() {
                let error = String::from_utf8_lossy(&output.stderr);
                return Err(GitError::GitCommandFailed(format!(
                    "Git clone error: {}",
                    error
                )));
            }
        } else {
            // Repository exists - handle branch checkout and reset/pull

            // If reset is requested, do it first
            if self.reset {
                let repo = GitRepo::new(clone_path.clone());
                repo.reset()?;
            }

            // If a specific branch is requested, checkout that branch
            if let Some(ref branch) = self.branch {
                // Fetch the branch first (in case it's new)
                let _ = Command::new("git")
                    .args(["-C", &clone_path, "fetch", "origin", branch])
                    .output();

                // Checkout the branch
                let output = Command::new("git")
                    .args(["-C", &clone_path, "checkout", branch])
                    .output()
                    .map_err(GitError::CommandExecutionError)?;

                if !output.status.success() {
                    // Try to checkout as a new tracking branch
                    let output = Command::new("git")
                        .args([
                            "-C",
                            &clone_path,
                            "checkout",
                            "-b",
                            branch,
                            &format!("origin/{}", branch),
                        ])
                        .output()
                        .map_err(GitError::CommandExecutionError)?;

                    if !output.status.success() {
                        let error = String::from_utf8_lossy(&output.stderr);
                        return Err(GitError::GitCommandFailed(format!(
                            "Git checkout error: {}",
                            error
                        )));
                    }
                }
            }

            // If pull is requested
            if self.pull {
                let repo = GitRepo::new(clone_path.clone());
                // If reset was done, we can safely pull
                if self.reset || !repo.has_changes()? {
                    let output = Command::new("git")
                        .args(["-C", &clone_path, "pull"])
                        .output()
                        .map_err(GitError::CommandExecutionError)?;

                    if !output.status.success() {
                        let error = String::from_utf8_lossy(&output.stderr);
                        return Err(GitError::GitCommandFailed(format!(
                            "Git pull error: {}",
                            error
                        )));
                    }
                } else {
                    return Err(GitError::LocalChangesExist(clone_path));
                }
            }
        }

        // Build the final path (including subpath if specified)
        let final_path = if let Some(ref subpath) = self.subpath {
            let mut path_buf = PathBuf::from(&clone_path);
            path_buf.push(subpath);
            path_buf.to_string_lossy().to_string()
        } else {
            clone_path
        };

        Ok(final_path)
    }
}

/// Represents a collection of git repositories under a base path.
#[derive(Clone)]
pub struct GitTree {
    base_path: String,
}

impl GitTree {
    /// Creates a new GitTree with the specified base path.
    ///
    /// # Arguments
    ///
    /// * `base_path` - The base path where all git repositories are located
    ///
    /// # Returns
    ///
    /// * `Ok(GitTree)` - A new GitTree instance
    /// * `Err(GitError)` - If the base path is invalid or cannot be created
    pub fn new(base_path: &str) -> Result<Self, GitError> {
        // Check if git is installed
        check_git_installed()?;

        // Validate the base path
        let path = Path::new(base_path);
        if !path.exists() {
            fs::create_dir_all(path).map_err(GitError::FileSystemError)?;
        } else if !path.is_dir() {
            return Err(GitError::InvalidBasePath(base_path.to_string()));
        }

        Ok(GitTree {
            base_path: base_path.to_string(),
        })
    }

    /// Lists all git repositories under the base path.
    ///
    /// # Returns
    ///
    /// * `Ok(Vec<String>)` - A vector of paths to git repositories
    /// * `Err(GitError)` - If the operation failed
    pub fn list(&self) -> Result<Vec<String>, GitError> {
        let base_path = Path::new(&self.base_path);

        if !base_path.exists() || !base_path.is_dir() {
            return Ok(Vec::new());
        }

        let mut repos = Vec::new();

        // Find all directories with .git subdirectories
        let output = Command::new("find")
            .args([&self.base_path, "-type", "d", "-name", ".git"])
            .output()
            .map_err(GitError::CommandExecutionError)?;

        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            for line in stdout.lines() {
                // Get the parent directory of .git which is the repo root
                if let Some(parent) = Path::new(line).parent() {
                    if let Some(path_str) = parent.to_str() {
                        repos.push(path_str.to_string());
                    }
                }
            }
        } else {
            let error = String::from_utf8_lossy(&output.stderr);
            return Err(GitError::GitCommandFailed(format!(
                "Failed to find git repositories: {}",
                error
            )));
        }

        Ok(repos)
    }

    /// Finds repositories matching a pattern or partial path.
    ///
    /// # Arguments
    ///
    /// * `pattern` - The pattern to match against repository paths
    ///   - If the pattern ends with '*', all matching repositories are returned
    ///   - Otherwise, exactly one matching repository must be found
    ///
    /// # Returns
    ///
    /// * `Ok(Vec<String>)` - A vector of paths to matching repositories
    /// * `Err(GitError)` - If no matching repositories are found,
    ///   or if multiple repositories match a non-wildcard pattern
    pub fn find(&self, pattern: &str) -> Result<Vec<GitRepo>, GitError> {
        let repo_names = self.list()?; // list() already ensures these are git repo names

        if repo_names.is_empty() {
            return Ok(Vec::new()); // If no repos listed, find results in an empty list
        }

        let mut matched_repos: Vec<GitRepo> = Vec::new();

        if pattern == "*" {
            for name in repo_names {
                let full_path = format!("{}/{}", self.base_path, name);
                matched_repos.push(GitRepo::new(full_path));
            }
        } else if pattern.ends_with('*') {
            let prefix = &pattern[0..pattern.len() - 1];
            for name in repo_names {
                if name.starts_with(prefix) {
                    let full_path = format!("{}/{}", self.base_path, name);
                    matched_repos.push(GitRepo::new(full_path));
                }
            }
        } else {
            // Exact match for the name
            for name in repo_names {
                if name == pattern {
                    let full_path = format!("{}/{}", self.base_path, name);
                    matched_repos.push(GitRepo::new(full_path));
                    // `find` returns all exact matches. If names aren't unique (unlikely from `list`),
                    // it could return more than one. For an exact name, typically one is expected.
                }
            }
        }

        Ok(matched_repos)
    }

    /// Creates a builder for cloning a repository with advanced options.
    ///
    /// # Arguments
    ///
    /// * `url` - The URL of the git repository to clone
    ///
    /// # Returns
    ///
    /// A `GitCloneBuilder` that can be configured with branch, subpath, reset, and pull options.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use herolib_git::GitTree;
    ///
    /// let git_tree = GitTree::new("/tmp/repos").unwrap();
    ///
    /// // Clone with specific branch and reset
    /// let path = git_tree
    ///     .clone_repo("https://github.com/user/repo")
    ///     .branch("development")
    ///     .reset()
    ///     .execute()
    ///     .unwrap();
    /// ```
    pub fn clone_repo(&self, url: &str) -> GitCloneBuilder<'_> {
        GitCloneBuilder::new(self, url)
    }

    /// Gets the path to a cloned repository (or subpath) from a URL.
    ///
    /// This is a convenience method that parses the URL (including branch and subpath
    /// from Gitea/GitHub/GitLab formats), clones the repository if needed, and returns
    /// the path directly.
    ///
    /// # Arguments
    ///
    /// * `url` - The URL of the git repository. Supports extended formats:
    ///   - Gitea/Forgejo: `https://server/account/repo/src/branch/main/docs`
    ///   - GitHub: `https://github.com/account/repo/tree/main/src`
    ///   - GitLab: `https://gitlab.com/account/repo/-/tree/main/lib`
    ///
    /// # Returns
    ///
    /// * `Ok(String)` - The path to the repository or subpath
    /// * `Err(GitError)` - If the operation failed
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use herolib_git::GitTree;
    ///
    /// let git_tree = GitTree::new("/tmp/repos").unwrap();
    ///
    /// // Get path to docs folder, cloning with the 'main' branch
    /// let docs_path = git_tree
    ///     .get_path("https://forge.ourworld.tf/geomind_research/herolib_rust/src/branch/main/docs")
    ///     .unwrap();
    ///
    /// // docs_path will be something like:
    /// // "/tmp/repos/forge.ourworld.tf/geomind_research/herolib_rust/docs"
    /// ```
    pub fn get_path(&self, url: &str) -> Result<String, GitError> {
        self.clone_repo(url).execute()
    }

    /// Gets one or more GitRepo objects based on a path pattern or URL.
    ///
    /// # Arguments
    ///
    /// * `path_or_url` - The path pattern to match against repository paths or a git URL
    ///   - If it's a URL, the repository will be cloned if it doesn't exist
    ///   - If it's a path pattern, it will find matching repositories
    ///
    /// # Returns
    ///
    /// * `Ok(Vec<GitRepo>)` - A vector of GitRepo objects
    /// * `Err(GitError)` - If no matching repositories are found or the clone operation failed
    pub fn get(&self, path_or_url: &str) -> Result<Vec<GitRepo>, GitError> {
        // Check if it's a URL
        if path_or_url.starts_with("http") || path_or_url.starts_with("git@") {
            // Parse the URL
            let (server, account, repo) = parse_git_url(path_or_url);
            if server.is_empty() || account.is_empty() || repo.is_empty() {
                return Err(GitError::InvalidUrl(path_or_url.to_string()));
            }

            // Create the target directory
            let clone_path = format!("{}/{}/{}/{}", self.base_path, server, account, repo);
            let clone_dir = Path::new(&clone_path);

            // Check if repo already exists
            if clone_dir.exists() {
                return Ok(vec![GitRepo::new(clone_path)]);
            }

            // Create parent directory
            if let Some(parent) = clone_dir.parent() {
                fs::create_dir_all(parent).map_err(GitError::FileSystemError)?;
            }

            // Clone the repository
            let output = Command::new("git")
                .args(["clone", "--depth", "1", path_or_url, &clone_path])
                .output()
                .map_err(GitError::CommandExecutionError)?;

            if output.status.success() {
                Ok(vec![GitRepo::new(clone_path)])
            } else {
                let error = String::from_utf8_lossy(&output.stderr);
                Err(GitError::GitCommandFailed(format!(
                    "Git clone error: {}",
                    error
                )))
            }
        } else {
            // It's a path pattern, find matching repositories using the updated self.find()
            // which now directly returns Result<Vec<GitRepo>, GitError>.
            let repos = self.find(path_or_url)?;
            Ok(repos)
        }
    }
}

/// Represents a git repository.
pub struct GitRepo {
    path: String,
}

impl GitRepo {
    /// Creates a new GitRepo with the specified path.
    ///
    /// # Arguments
    ///
    /// * `path` - The path to the git repository
    pub fn new(path: String) -> Self {
        GitRepo { path }
    }

    /// Gets the path of the repository.
    ///
    /// # Returns
    ///
    /// * The path to the git repository
    pub fn path(&self) -> &str {
        &self.path
    }

    /// Checks if the repository has uncommitted changes.
    ///
    /// # Returns
    ///
    /// * `Ok(bool)` - True if the repository has uncommitted changes, false otherwise
    /// * `Err(GitError)` - If the operation failed
    pub fn has_changes(&self) -> Result<bool, GitError> {
        let output = Command::new("git")
            .args(["-C", &self.path, "status", "--porcelain"])
            .output()
            .map_err(GitError::CommandExecutionError)?;

        Ok(!output.stdout.is_empty())
    }

    /// Pulls the latest changes from the remote repository.
    ///
    /// # Returns
    ///
    /// * `Ok(Self)` - The GitRepo object for method chaining
    /// * `Err(GitError)` - If the pull operation failed
    pub fn pull(&self) -> Result<Self, GitError> {
        // Check if repository exists and is a git repository
        let git_dir = Path::new(&self.path).join(".git");
        if !git_dir.exists() || !git_dir.is_dir() {
            return Err(GitError::NotAGitRepository(self.path.clone()));
        }

        // Check for local changes
        if self.has_changes()? {
            return Err(GitError::LocalChangesExist(self.path.clone()));
        }

        // Pull the latest changes
        let output = Command::new("git")
            .args(["-C", &self.path, "pull"])
            .output()
            .map_err(GitError::CommandExecutionError)?;

        if output.status.success() {
            Ok(self.clone())
        } else {
            let error = String::from_utf8_lossy(&output.stderr);
            Err(GitError::GitCommandFailed(format!(
                "Git pull error: {}",
                error
            )))
        }
    }

    /// Resets any local changes in the repository.
    ///
    /// # Returns
    ///
    /// * `Ok(Self)` - The GitRepo object for method chaining
    /// * `Err(GitError)` - If the reset operation failed
    pub fn reset(&self) -> Result<Self, GitError> {
        // Check if repository exists and is a git repository
        let git_dir = Path::new(&self.path).join(".git");
        if !git_dir.exists() || !git_dir.is_dir() {
            return Err(GitError::NotAGitRepository(self.path.clone()));
        }

        // Reset any local changes
        let reset_output = Command::new("git")
            .args(["-C", &self.path, "reset", "--hard", "HEAD"])
            .output()
            .map_err(GitError::CommandExecutionError)?;

        if !reset_output.status.success() {
            let error = String::from_utf8_lossy(&reset_output.stderr);
            return Err(GitError::GitCommandFailed(format!(
                "Git reset error: {}",
                error
            )));
        }

        // Clean untracked files
        let clean_output = Command::new("git")
            .args(["-C", &self.path, "clean", "-fd"])
            .output()
            .map_err(GitError::CommandExecutionError)?;

        if !clean_output.status.success() {
            let error = String::from_utf8_lossy(&clean_output.stderr);
            return Err(GitError::GitCommandFailed(format!(
                "Git clean error: {}",
                error
            )));
        }

        Ok(self.clone())
    }

    /// Commits changes in the repository.
    ///
    /// # Arguments
    ///
    /// * `message` - The commit message
    ///
    /// # Returns
    ///
    /// * `Ok(Self)` - The GitRepo object for method chaining
    /// * `Err(GitError)` - If the commit operation failed
    pub fn commit(&self, message: &str) -> Result<Self, GitError> {
        // Check if repository exists and is a git repository
        let git_dir = Path::new(&self.path).join(".git");
        if !git_dir.exists() || !git_dir.is_dir() {
            return Err(GitError::NotAGitRepository(self.path.clone()));
        }

        // Check for local changes
        if !self.has_changes()? {
            return Ok(self.clone());
        }

        // Add all changes
        let add_output = Command::new("git")
            .args(["-C", &self.path, "add", "."])
            .output()
            .map_err(GitError::CommandExecutionError)?;

        if !add_output.status.success() {
            let error = String::from_utf8_lossy(&add_output.stderr);
            return Err(GitError::GitCommandFailed(format!(
                "Git add error: {}",
                error
            )));
        }

        // Commit the changes
        let commit_output = Command::new("git")
            .args(["-C", &self.path, "commit", "-m", message])
            .output()
            .map_err(GitError::CommandExecutionError)?;

        if !commit_output.status.success() {
            let error = String::from_utf8_lossy(&commit_output.stderr);
            return Err(GitError::GitCommandFailed(format!(
                "Git commit error: {}",
                error
            )));
        }

        Ok(self.clone())
    }

    /// Pushes changes to the remote repository.
    ///
    /// # Returns
    ///
    /// * `Ok(Self)` - The GitRepo object for method chaining
    /// * `Err(GitError)` - If the push operation failed
    pub fn push(&self) -> Result<Self, GitError> {
        // Check if repository exists and is a git repository
        let git_dir = Path::new(&self.path).join(".git");
        if !git_dir.exists() || !git_dir.is_dir() {
            return Err(GitError::NotAGitRepository(self.path.clone()));
        }

        // Push the changes
        let push_output = Command::new("git")
            .args(["-C", &self.path, "push"])
            .output()
            .map_err(GitError::CommandExecutionError)?;

        if push_output.status.success() {
            Ok(self.clone())
        } else {
            let error = String::from_utf8_lossy(&push_output.stderr);
            Err(GitError::GitCommandFailed(format!(
                "Git push error: {}",
                error
            )))
        }
    }
}

// Implement Clone for GitRepo to allow for method chaining
impl Clone for GitRepo {
    fn clone(&self) -> Self {
        GitRepo {
            path: self.path.clone(),
        }
    }
}
