    use kylin_patch_rs::*;
    use std::sync::{Mutex, OnceLock};
    use std::fs;
    use std::path::{Path, PathBuf};
    use std::time::SystemTime;

    fn lock() -> &'static Mutex<()> {
        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
        LOCK.get_or_init(|| Mutex::new(()))
    }

    fn mktemp(tag: &str) -> PathBuf {
        let nanos = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .map(|d| d.as_nanos())
            .unwrap_or(0);
        let p = std::env::temp_dir().join(format!("kylin-patch-it2-{}-{}-{}", tag, std::process::id(), nanos));
        fs::create_dir_all(&p).expect("mkdir");
        p
    }

    fn write_file(path: &Path, content: &str) {
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).expect("mkdir parent");
        }
        fs::write(path, content).expect("write");
    }

    fn base_cfg(patch_path: PathBuf) -> PatchConfig {
        PatchConfig {
            input_patch: Some(patch_path),
            origfile: None,
            output: None,
            dry_run: false,
            strip: None,
            reverse: false,
            reverse_specified: false,
            fuzz: None,
            backup: false,
            backup_suffix: None,
            backup_prefix: None,
            basename_prefix: None,
            version_control: None,
            backup_if_mismatch: true,
            no_backup_if_mismatch: false,
            merge: false,
            force: true,
            batch: false,
            noreverse: false,
            reject_file: None,
            directory: None,
            patch_get: None,
            set_time: false,
            set_utc: false,
            verbose: false,
            quiet: true,
            format_hint: None,
            read_only: None,
            reject_format: None,
            ifdef_name: None,
            remove_empty_files: false,
            ignore_whitespace: false,
            quoting_style: Some("literal".to_string()),
            follow_symlinks: false,
            binary: false,
            posix: false,
        }
    }

    #[test]
    fn it_handles_new_file_from_dev_null() {
        let _g = lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = mktemp("newfile");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(&td).expect("chdir");

        write_file(
            &td.join("p.diff"),
            "--- /dev/null\n+++ n.txt\n@@ -0,0 +1,2 @@\n+hello\n+world\n",
        );
        let exit = apply_patches(&base_cfg(td.join("p.diff")));

        std::env::set_current_dir(old_cwd).expect("restore");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(fs::read_to_string(td.join("n.txt")).expect("read"), "hello\nworld\n");
        let _ = fs::remove_dir_all(&td);
    }

    #[test]
    fn it_skips_dangerous_relative_path() {
        let _g = lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = mktemp("danger");
        write_file(
            &td.join("p.diff"),
            "--- ../../evil\n+++ ../../evil\n@@ -1 +1 @@\n-a\n+b\n",
        );
        let exit = apply_patches(&base_cfg(td.join("p.diff")));
        assert_eq!(exit, PatchExit::SomeFailed);
        let _ = fs::remove_dir_all(&td);
    }

    #[test]
    fn it_reports_missing_target_file() {
        let _g = lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = mktemp("missing");
        write_file(
            &td.join("p.diff"),
            "--- notfound.txt\n+++ notfound.txt\n@@ -1 +1 @@\n-a\n+b\n",
        );
        let exit = apply_patches(&base_cfg(td.join("p.diff")));
        assert_eq!(exit, PatchExit::SomeFailed);
        let _ = fs::remove_dir_all(&td);
    }

    #[test]
    fn it_handles_git_binary_patch_marker() {
        let _g = lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = mktemp("gitbin");
        write_file(
            &td.join("p.diff"),
            "diff --git a/bin.dat b/bin.dat\nindex 1111111..2222222 100644\nGIT binary patch\n",
        );
        let exit = apply_patches(&base_cfg(td.join("p.diff")));
        assert_eq!(exit, PatchExit::SomeFailed);
        let _ = fs::remove_dir_all(&td);
    }
