use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use std::io::BufRead;
use std::io::BufReader;
use std::path::Path;
use std::process::Command;
use std::process::Stdio;

pub fn build_push_command(repo_path: &Path, remote: &str, branch: &str) -> Command {
    let mut cmd = Command::new("git");
    cmd.current_dir(repo_path)
        .arg("push")
        .arg("--progress")
        .arg(remote)
        .arg(format!("HEAD:refs/heads/{branch}"));
    cmd
}

fn print_progress_line(line: &str) {
    if line.starts_with("To ")
        || line.starts_with("Everything up-to-date")
        || line.contains('%')
        || line.starts_with("remote:")
        || line.contains("Counting objects")
    {
        println!("    {}", line);
    }
}

pub fn push_current_branch(repo_path: &Path, remote: &str, branch: &str) -> Result<()> {
    which::which("git").context("git executable not found in PATH")?;

    let mut cmd = build_push_command(repo_path, remote, branch);
    let mut child = cmd
        .stdout(Stdio::null())
        .stderr(Stdio::piped())
        .spawn()
        .context("Failed to spawn git push")?;

    if let Some(stderr) = child.stderr.take() {
        let reader = BufReader::new(stderr);
        for line in reader.lines() {
            match line {
                Ok(l) => print_progress_line(&l),
                Err(_) => break,
            }
        }
    }

    let status = child.wait().context("Failed to wait for git push")?;
    if !status.success() {
        bail!("git push failed with exit code {:?}", status.code());
    }
    Ok(())
}

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

    #[test]
    fn build_push_cmd_has_expected_args() {
        let cmd = build_push_command(Path::new("/tmp/repo"), "origin", "main");
        let args: Vec<String> = cmd
            .get_args()
            .map(|s| s.to_string_lossy().into_owned())
            .collect();
        assert_eq!(
            args,
            vec!["push", "--progress", "origin", "HEAD:refs/heads/main"]
        );
        assert_eq!(cmd.get_current_dir(), Some(Path::new("/tmp/repo")));
    }
}
