use std::process::Command;

fn cli(home: &std::path::Path, args: &[&str]) -> std::process::Output {
    Command::new(env!("CARGO_BIN_EXE_bds-cli"))
        .env("HOME", home)
        .args(args)
        .output()
        .unwrap()
}

#[cfg(target_os = "linux")]
#[test]
fn built_cli_runs_without_cargo_loader_paths() {
    let output = Command::new(env!("CARGO_BIN_EXE_bds-cli"))
        .env_remove("LD_LIBRARY_PATH")
        .arg("--help")
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "{}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert!(String::from_utf8_lossy(&output.stdout).contains("rebuild"));
}

#[test]
fn process_exit_codes_help_and_shared_state_roundtrip() {
    let root = tempfile::tempdir().unwrap();
    let home = root.path().join("home");
    let project = root.path().join("project");
    std::fs::create_dir_all(&home).unwrap();
    std::fs::create_dir_all(&project).unwrap();

    let help = cli(&home, &["--help"]);
    assert!(help.status.success());
    assert!(String::from_utf8_lossy(&help.stdout).contains("rebuild"));

    let invalid = cli(&home, &["not-a-command"]);
    assert_eq!(invalid.status.code(), Some(1));
    assert!(!invalid.stderr.is_empty());

    let added = cli(
        &home,
        &[
            "project",
            "add",
            project.to_str().unwrap(),
            "--name",
            "Process Blog",
        ],
    );
    assert!(
        added.status.success(),
        "{}",
        String::from_utf8_lossy(&added.stderr)
    );
    assert!(
        cli(&home, &["project", "switch", "process-blog"])
            .status
            .success()
    );

    let rebuilt = cli(&home, &["rebuild"]);
    assert!(rebuilt.status.success());
    assert!(String::from_utf8_lossy(&rebuilt.stdout).contains("%]"));

    let rebuilt_json = cli(&home, &["--json", "rebuild", "--incremental"]);
    assert!(rebuilt_json.status.success());
    let rebuilt_json: serde_json::Value = serde_json::from_slice(&rebuilt_json.stdout).unwrap();
    assert!(
        rebuilt_json["progress"]
            .as_array()
            .is_some_and(|items| !items.is_empty())
    );

    let created = Command::new(env!("CARGO_BIN_EXE_bds-cli"))
        .env("HOME", &home)
        .args(["--json", "post", "--stdin", "--no-translate"])
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .and_then(|mut child| {
            use std::io::Write as _;
            child
                .stdin
                .take()
                .unwrap()
                .write_all(br#"{"title":"Process post","content":"Body","language":"en"}"#)?;
            child.wait_with_output()
        })
        .unwrap();
    assert!(
        created.status.success(),
        "{}",
        String::from_utf8_lossy(&created.stderr)
    );
    let value: serde_json::Value = serde_json::from_slice(&created.stdout).unwrap();
    assert_eq!(value["ok"], true);
    assert_eq!(value["data"]["slug"], "process-post");

    let get_missing = cli(&home, &["config", "get", "missing"]);
    assert_eq!(get_missing.status.code(), Some(1));
}

#[cfg(unix)]
#[test]
fn process_upload_push_and_pull_dispatch_successfully() {
    use std::os::unix::fs::PermissionsExt as _;

    let root = tempfile::tempdir().unwrap();
    let home = root.path().join("home");
    let project = root.path().join("project");
    let remote = root.path().join("remote.git");
    let peer = root.path().join("peer");
    let fake_bin = root.path().join("bin");
    for directory in [&home, &project, &fake_bin] {
        std::fs::create_dir_all(directory).unwrap();
    }
    assert!(
        cli(
            &home,
            &[
                "project",
                "add",
                project.to_str().unwrap(),
                "--name",
                "External Blog",
            ],
        )
        .status
        .success()
    );
    assert!(
        cli(&home, &["project", "switch", "external-blog"])
            .status
            .success()
    );

    std::fs::write(
        project.join("meta/publishing.json"),
        r#"{"sshHost":"example.test","sshUser":"deploy","sshRemotePath":"/srv/blog","sshMode":"rsync"}"#,
    )
    .unwrap();
    let rsync = fake_bin.join("rsync");
    std::fs::write(&rsync, "#!/bin/sh\nexit 0\n").unwrap();
    let mut permissions = std::fs::metadata(&rsync).unwrap().permissions();
    permissions.set_mode(0o755);
    std::fs::set_permissions(&rsync, permissions).unwrap();
    let upload = Command::new(env!("CARGO_BIN_EXE_bds-cli"))
        .env("HOME", &home)
        .env("PATH", &fake_bin)
        .env("SSH_AUTH_SOCK", root.path().join("agent.sock"))
        .arg("upload")
        .output()
        .unwrap();
    assert!(
        upload.status.success(),
        "{}",
        String::from_utf8_lossy(&upload.stderr)
    );

    git(&project, &["init", "-b", "main"]);
    git(&project, &["config", "user.email", "cli@example.test"]);
    git(&project, &["config", "user.name", "CLI Test"]);
    git(&project, &["add", "."]);
    git(&project, &["commit", "-m", "Initial"]);
    git(root.path(), &["init", "--bare", remote.to_str().unwrap()]);
    git(
        &project,
        &["remote", "add", "origin", remote.to_str().unwrap()],
    );
    git(&project, &["push", "-u", "origin", "main"]);
    git(&remote, &["symbolic-ref", "HEAD", "refs/heads/main"]);

    let pushed = cli(&home, &["push"]);
    assert!(
        pushed.status.success(),
        "{}",
        String::from_utf8_lossy(&pushed.stderr)
    );

    git(
        root.path(),
        &["clone", remote.to_str().unwrap(), peer.to_str().unwrap()],
    );
    git(&peer, &["config", "user.email", "peer@example.test"]);
    git(&peer, &["config", "user.name", "Peer Test"]);
    std::fs::write(peer.join("remote-change.txt"), "change").unwrap();
    git(&peer, &["add", "remote-change.txt"]);
    git(&peer, &["commit", "-m", "Remote change"]);
    git(&peer, &["push"]);

    let pulled = cli(&home, &["pull"]);
    assert!(
        pulled.status.success(),
        "{}",
        String::from_utf8_lossy(&pulled.stderr)
    );
    assert!(project.join("remote-change.txt").is_file());
}

#[cfg(unix)]
fn git(cwd: &std::path::Path, args: &[&str]) {
    let output = Command::new("git")
        .current_dir(cwd)
        .args(args)
        .output()
        .unwrap();
    assert!(
        output.status.success(),
        "git {} failed:\n{}",
        args.join(" "),
        String::from_utf8_lossy(&output.stderr)
    );
}
