use std::{fs, io, ops::Not, path::{Path, PathBuf}};


pub fn determine_git_root(start_path: &Path) -> io::Result<PathBuf> {

    let mut path = start_path;
    let mut errors = Vec::new();

    loop {
        let mut dir_contents = fs::read_dir(path)?;

        let contains_git_dir = dir_contents.any(|entry| {
            match entry {
                Ok(entry) => {
                    entry.file_name() == GIT_DIR
                }
                Err(cause) => {
                    errors.push(format!(
                        "Error when reading entry inside directory {path:?}: {cause}"
                    ));
                    false
                }
            }
        });

        if contains_git_dir {
            return Ok(path.to_path_buf());
        }

        if let Some(parent) = path.parent() {
            path = parent;
        } else {
            let mut message = String::from("No parent directory found with a .git directory inside. Are you in a Git repository?");

            if errors.is_empty().not() { //only report errors, if we don't find a .git directory, to avoid noisy logging and the need for a logging dependency

                message.push_str("\n\nThe following errors have occurred while looking for the .git directory:\n");

                for error in errors {
                    message.push_str(&format!("  - {error}\n"));
                }
            }

            return Err(io::Error::other(message));
        }
    }
}

const GIT_DIR: &str = ".git";


#[cfg(test)]
mod tests {
    use super::*;
    use assert_fs::prelude::*;

    #[test]
    fn should_determine_git_root() -> anyhow::Result<()> {
        let temp = assert_fs::TempDir::new()?;

        let git_root = temp.child("git_root/");

        git_root.child(GIT_DIR)
            .create_dir_all()?;

        let subdir = git_root.child("sub-dir");
        subdir.create_dir_all()?;

        let result = determine_git_root(subdir.path())?;

        assert_eq!(git_root.to_path_buf(), result);

        Ok(())
    }
}
