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

    fn test_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-perm-{}-{}-{}", tag, std::process::id(), nanos));
        fs::create_dir_all(&p).expect("mkdir");
        p
    }

    fn cfg(patch_path: &Path) -> PatchConfig {
        PatchConfig {
            input_patch: Some(patch_path.to_path_buf()),
            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 read_only_fail_produces_reject() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = mktemp("ro-fail");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(&td).expect("chdir");

        fs::write("f", "a\n").expect("write");
        fs::write("p.diff", "--- f\n+++ f\n@@ -1 +1 @@\n-a\n+b\n").expect("write diff");
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mut perm = fs::metadata("f").expect("meta").permissions();
            perm.set_mode(0o444);
            fs::set_permissions("f", perm).expect("chmod");
        }

        let mut c = cfg(Path::new("p.diff"));
        c.read_only = Some(ReadOnlyMode::Fail);
        let exit = apply_patches(&c);

        std::env::set_current_dir(old_cwd).expect("restore");
        assert_eq!(exit, PatchExit::SomeFailed);
        assert!(td.join("f.rej").exists());
        let _ = fs::remove_dir_all(&td);
    }

    #[test]
    fn read_only_ignore_allows_patch() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = mktemp("ro-ignore");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(&td).expect("chdir");

        fs::write("f", "a\n").expect("write");
        fs::write("p.diff", "--- f\n+++ f\n@@ -1 +1 @@\n-a\n+b\n").expect("write diff");
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mut perm = fs::metadata("f").expect("meta").permissions();
            perm.set_mode(0o444);
            fs::set_permissions("f", perm).expect("chmod");
        }

        let mut c = cfg(Path::new("p.diff"));
        c.read_only = Some(ReadOnlyMode::Ignore);
        let exit = apply_patches(&c);

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

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

        fs::write("f", "a\n").expect("write");
        fs::write("p.diff", "--- f\n+++ f\n@@ -1 +1 @@\n-a\n+b\n").expect("write diff");

        let mut c = cfg(Path::new("p.diff"));
        c.backup = true;
        c.backup_suffix = Some(".orig".to_string());
        let exit = apply_patches(&c);

        std::env::set_current_dir(old_cwd).expect("restore");
        assert_eq!(exit, PatchExit::Ok);
        assert!(td.join("f.orig").exists() || td.join("./f.orig").exists());
        let _ = fs::remove_dir_all(&td);
    }

    #[test]
    fn delete_missing_file_without_force_is_failed() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = mktemp("del-missing");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(&td).expect("chdir");

        fs::write("p.diff", "--- f\n+++ /dev/null\n@@ -1 +0,0 @@\n-a\n").expect("write diff");
        let mut c = cfg(Path::new("p.diff"));
        c.force = false;
        let exit = apply_patches(&c);

        std::env::set_current_dir(old_cwd).expect("restore");
        assert_eq!(exit, PatchExit::SomeFailed);
        let _ = fs::remove_dir_all(&td);
    }

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

        fs::write("real", "a\n").expect("write");
        #[cfg(unix)]
        {
            std::os::unix::fs::symlink("real", "lnk").expect("symlink");
        }
        fs::write("p.diff", "--- lnk\n+++ lnk\n@@ -1 +1 @@\n-a\n+b\n").expect("write diff");
        let c = cfg(Path::new("p.diff"));
        let exit = apply_patches(&c);

        std::env::set_current_dir(old_cwd).expect("restore");
        assert_eq!(exit, PatchExit::SomeFailed);
        let _ = fs::remove_dir_all(&td);
    }
