use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
use std::process::Command;

fn wrapper_path() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("scripts/cir/cir-rsync-wrapper")
}

fn real_rsync() -> Option<String> {
    let candidate = "/usr/bin/rsync";
    if std::path::Path::new(candidate).exists() {
        return Some(candidate.to_string());
    }
    None
}

#[test]
fn cir_rsync_wrapper_passes_through_help() {
    let Some(real) = real_rsync() else {
        return;
    };
    let out = Command::new(wrapper_path())
        .env("REAL_RSYNC_BIN", real)
        .arg("-h")
        .output()
        .expect("run wrapper -h");
    assert!(
        out.status.success(),
        "stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    let stdout = String::from_utf8_lossy(&out.stdout);
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(stdout.contains("rsync") || stderr.contains("rsync"));
}

#[test]
fn cir_rsync_wrapper_rewrites_rsync_source_to_mirror_tree() {
    let Some(real) = real_rsync() else {
        return;
    };
    let td = tempfile::tempdir().expect("tempdir");
    let mirror_root = td.path().join("mirror");
    let dest_root = td.path().join("dest");
    let repo_root = mirror_root.join("example.net").join("repo");
    std::fs::create_dir_all(repo_root.join("nested")).expect("mkdirs");
    std::fs::write(repo_root.join("a.roa"), b"roa").expect("write roa");
    std::fs::write(repo_root.join("nested").join("b.txt"), b"txt").expect("write txt");
    std::fs::create_dir_all(&dest_root).expect("mkdir dest");

    let out = Command::new(wrapper_path())
        .env("REAL_RSYNC_BIN", real)
        .env("CIR_MIRROR_ROOT", &mirror_root)
        .args([
            "-rt",
            "--address",
            "127.0.0.1",
            "--contimeout=10",
            "--include=*/",
            "--include=*.roa",
            "--exclude=*",
            "rsync://example.net/repo/",
            dest_root.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run wrapper rewrite");
    assert!(
        out.status.success(),
        "stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    assert_eq!(
        std::fs::read(dest_root.join("a.roa")).expect("read copied roa"),
        b"roa"
    );
    assert!(!dest_root.join("nested").join("b.txt").exists());
}

#[test]
fn cir_rsync_wrapper_rewrites_module_root_without_trailing_slash_as_contents() {
    let Some(real) = real_rsync() else {
        return;
    };
    let td = tempfile::tempdir().expect("tempdir");
    let mirror_root = td.path().join("mirror");
    let dest_root = td.path().join("dest");
    let repo_root = mirror_root.join("example.net").join("repo");
    std::fs::create_dir_all(repo_root.join("sub")).expect("mkdirs");
    std::fs::write(repo_root.join("root.cer"), b"cer").expect("write cer");
    std::fs::write(repo_root.join("sub").join("child.roa"), b"roa").expect("write roa");
    std::fs::create_dir_all(&dest_root).expect("mkdir dest");

    let out = Command::new(wrapper_path())
        .env("REAL_RSYNC_BIN", real)
        .env("CIR_MIRROR_ROOT", &mirror_root)
        .args([
            "-rt",
            "--include=*/",
            "--include=*.cer",
            "--include=*.roa",
            "--exclude=*",
            "rsync://example.net/repo",
            dest_root.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run wrapper rewrite");
    assert!(
        out.status.success(),
        "stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    assert_eq!(
        std::fs::read(dest_root.join("root.cer")).expect("read copied root cer"),
        b"cer"
    );
    assert_eq!(
        std::fs::read(dest_root.join("sub").join("child.roa")).expect("read copied child roa"),
        b"roa"
    );
    assert!(
        !dest_root.join("repo").exists(),
        "module root must not be nested under destination"
    );
}

#[test]
fn cir_rsync_wrapper_requires_mirror_root_for_rsync_source() {
    let Some(real) = real_rsync() else {
        return;
    };
    let td = tempfile::tempdir().expect("tempdir");
    let dest_root = td.path().join("dest");
    std::fs::create_dir_all(&dest_root).expect("mkdir dest");

    let out = Command::new(wrapper_path())
        .env("REAL_RSYNC_BIN", real)
        .args([
            "-rt",
            "rsync://example.net/repo/",
            dest_root.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run wrapper missing env");
    assert!(!out.status.success());
    assert!(String::from_utf8_lossy(&out.stderr).contains("CIR_MIRROR_ROOT"));
}

#[test]
fn cir_rsync_wrapper_leaves_local_source_untouched() {
    let Some(real) = real_rsync() else {
        return;
    };
    let td = tempfile::tempdir().expect("tempdir");
    let src_root = td.path().join("src");
    let dest_root = td.path().join("dest");
    std::fs::create_dir_all(&src_root).expect("mkdir src");
    std::fs::create_dir_all(&dest_root).expect("mkdir dest");
    std::fs::write(src_root.join("x.cer"), b"x").expect("write source");

    let out = Command::new(wrapper_path())
        .env("REAL_RSYNC_BIN", real)
        .args([
            "-rt",
            src_root.to_string_lossy().as_ref(),
            dest_root.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run wrapper local passthrough");
    assert!(
        out.status.success(),
        "stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    assert_eq!(
        std::fs::read(dest_root.join("src").join("x.cer")).expect("read copied file"),
        b"x"
    );
}

#[test]
fn cir_rsync_wrapper_local_link_mode_uses_hardlinks_for_rewritten_sources() {
    let Some(real) = real_rsync() else {
        return;
    };
    let td = tempfile::tempdir().expect("tempdir");
    let mirror_root = td.path().join("mirror");
    let dest_root = td.path().join("dest");
    let repo_root = mirror_root.join("example.net").join("repo");
    std::fs::create_dir_all(repo_root.join("nested")).expect("mkdirs");
    let src_file = repo_root.join("a.roa");
    let src_nested = repo_root.join("nested").join("b.cer");
    std::fs::write(&src_file, b"roa").expect("write roa");
    std::fs::write(&src_nested, b"cer").expect("write cer");
    std::fs::create_dir_all(&dest_root).expect("mkdir dest");

    let out = Command::new(wrapper_path())
        .env("REAL_RSYNC_BIN", real)
        .env("CIR_MIRROR_ROOT", &mirror_root)
        .env("CIR_LOCAL_LINK_MODE", "1")
        .args([
            "-rt",
            "--delete",
            "--include=*/",
            "--include=*.roa",
            "--include=*.cer",
            "--exclude=*",
            "rsync://example.net/repo/",
            dest_root.to_string_lossy().as_ref(),
        ])
        .output()
        .expect("run wrapper local-link mode");
    assert!(
        out.status.success(),
        "stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );

    let dst_file = dest_root.join("a.roa");
    let dst_nested = dest_root.join("nested").join("b.cer");
    assert_eq!(std::fs::read(&dst_file).expect("read dest roa"), b"roa");
    assert_eq!(std::fs::read(&dst_nested).expect("read dest cer"), b"cer");

    let src_meta = std::fs::metadata(&src_file).expect("src metadata");
    let dst_meta = std::fs::metadata(&dst_file).expect("dst metadata");
    assert_eq!(
        src_meta.ino(),
        dst_meta.ino(),
        "expected hardlinked destination file"
    );
}
