use assert_cmd::cargo::cargo_bin_cmd;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

fn init_git_repo(path: &Path) {
    fs::create_dir_all(path).unwrap();
    let status = std::process::Command::new("git")
        .args(["init"])
        .current_dir(path)
        .status()
        .expect("failed to run git init");
    assert!(status.success(), "git init failed");
}

#[test]
fn dry_run_prints_expected_commands_in_non_tty_mode() {
    let tmp = TempDir::new().unwrap();
    let alpha = tmp.path().join("alpha");
    let beta = tmp.path().join("beta");
    init_git_repo(&alpha);
    init_git_repo(&beta);

    let assert = cargo_bin_cmd!("multipull")
        .arg(tmp.path())
        .arg("--dry-run")
        .assert()
        .success();

    let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
    // Non-TTY stdout in tests should choose the plain-line mode.
    assert!(
        output.contains("Progress: using plain lines"),
        "expected non-interactive progress message"
    );
    // Repos are processed in name order.
    assert!(output.contains("===== alpha ====="), "missing alpha header");
    assert!(output.contains("===== beta ====="), "missing beta header");
    // Dry-run should not execute git commands but show the intended ones.
    assert!(
        output.contains("[dry-run] would run: git status --short --branch"),
        "missing dry-run status line"
    );
    assert!(
        output.contains("[dry-run] would run: git pull --ff-only"),
        "missing dry-run pull line"
    );
}

#[test]
fn confirm_requires_tty() {
    let tmp = TempDir::new().unwrap();
    init_git_repo(&tmp.path().join("repo"));

    cargo_bin_cmd!("multipull")
        .arg(tmp.path())
        .arg("--confirm")
        .assert()
        .failure()
        .stderr(predicates::str::contains("stdin is not a TTY"));
}

#[test]
fn summary_includes_repo_rows_and_exit_codes() {
    let tmp = TempDir::new().unwrap();
    let alpha = tmp.path().join("alpha");
    let beta = tmp.path().join("beta");
    init_git_repo(&alpha);
    init_git_repo(&beta);

    let assert = cargo_bin_cmd!("multipull")
        .arg(tmp.path())
        .arg("--dry-run")
        .assert()
        .success();

    let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
    let summary_start = output
        .find("Summary (sorted by name):")
        .expect("missing summary header");

    let tail = &output[summary_start..];
    assert!(tail.contains("alpha"), "summary missing alpha row");
    assert!(tail.contains("beta"), "summary missing beta row");
    assert!(tail.contains("ok"), "summary missing ok status");
    assert!(tail.contains("exit"), "summary missing exit column");
    assert!(tail.contains("Total time:"), "summary missing total time");
}

#[test]
fn sort_by_duration_orders_by_slowest_last_using_git_wrapper() {
    let tmp = TempDir::new().unwrap();
    let bin_dir = tmp.path().join("bin");
    fs::create_dir_all(&bin_dir).unwrap();

    // Real git path to delegate to.
    let real_git_path = String::from_utf8(
        std::process::Command::new("which")
            .arg("git")
            .output()
            .expect("which git failed")
            .stdout,
    )
    .unwrap()
    .trim()
    .to_string();

    // Wrapper that sleeps in repos whose basename is "slow".
    let wrapper = format!(
        r#"#!/bin/sh
if [ "$(basename "$(pwd)")" = "slow" ]; then
  sleep 0.1
fi
exec "{real_git}" "$@"
"#,
        real_git = real_git_path
    );
    let wrapper_path = bin_dir.join("git");
    fs::write(&wrapper_path, wrapper).unwrap();
    #[cfg(unix)]
    {
        let perms = fs::Permissions::from_mode(0o755);
        fs::set_permissions(&wrapper_path, perms).unwrap();
    }

    // Set up repos.
    let slow = tmp.path().join("slow");
    let fast = tmp.path().join("fast");
    init_git_repo(&slow);
    init_git_repo(&fast);

    let assert = cargo_bin_cmd!("multipull")
        .arg(tmp.path())
        .arg("--sort-by")
        .arg("duration")
        // Prefer our wrapper git.
        .env(
            "PATH",
            format!("{}:{}", bin_dir.display(), std::env::var("PATH").unwrap()),
        )
        .assert()
        .failure(); // pull fails (no remote), but summary should still print.

    let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
    let summary_start = output
        .find("Summary (sorted by duration):")
        .expect("missing duration summary header");
    let tail = &output[summary_start..];

    let pos_fast = tail
        .find("fast")
        .expect("fast repo missing from summary");
    let pos_slow = tail
        .find("slow")
        .expect("slow repo missing from summary");
    assert!(
        pos_fast < pos_slow,
        "expected fast before slow when sorted by duration"
    );
}

