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

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

    struct TempDirGuard {
        path: PathBuf,
    }

    impl TempDirGuard {
        fn new(tag: &str) -> Self {
            let nanos = SystemTime::now()
                .duration_since(SystemTime::UNIX_EPOCH)
                .map(|d| d.as_nanos())
                .unwrap_or(0);
            let path = std::env::temp_dir().join(format!("kylin-patch-it-{}-{}-{}", tag, std::process::id(), nanos));
            fs::create_dir_all(&path).expect("create temp dir");
            Self { path }
        }

        fn path(&self) -> &Path {
            &self.path
        }
    }

    impl Drop for TempDirGuard {
        fn drop(&mut self) {
            let _ = fs::remove_dir_all(&self.path);
        }
    }

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

    fn read_text(path: &Path) -> String {
        fs::read_to_string(path).expect("read file")
    }

    fn cfg_with_patch(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 it_applies_unified_patch_main_flow() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("unified");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("t.txt"), "a\nb\n");
        write_text(
            Path::new("p.diff"),
            "--- t.txt\n+++ t.txt\n@@ -1,2 +1,2 @@\n-a\n+x\n b\n",
        );
        let cfg = cfg_with_patch(Path::new("p.diff"));
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("t.txt")), "x\nb\n");
    }

    #[test]
    fn it_applies_context_patch() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("context");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("a"), "a
");
        write_text(
            Path::new("ab.diff"),
            "*** a
--- b
***************
*** 1 ****
- a
--- 1 ----
+ x
",
        );
        let cfg = cfg_with_patch(Path::new("ab.diff"));
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("a")), "x
");
    }

    #[test]
    fn it_applies_normal_patch() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("normal");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("f"), "a\nold\nc\n");
        write_text(Path::new("n.diff"), "2c2\n< old\n---\n> new\n");

        let mut cfg = cfg_with_patch(Path::new("n.diff"));
        cfg.origfile = Some(PathBuf::from("f"));
        cfg.format_hint = Some(DiffFormatHint::Normal);
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("f")), "a\nnew\nc\n");
    }

    #[test]
    fn it_honors_dry_run() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("dryrun");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("f"), "one\n");
        write_text(Path::new("p.diff"), "--- f\n+++ f\n@@ -1 +1 @@\n-one\n+two\n");

        let mut cfg = cfg_with_patch(Path::new("p.diff"));
        cfg.dry_run = true;
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("f")), "one\n");
    }

    #[test]
    fn it_supports_reverse_mode() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("reverse");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("f"), "one\n");
        write_text(Path::new("p.diff"), "--- f\n+++ f\n@@ -1 +1 @@\n-one\n+two\n");

        let cfg = cfg_with_patch(Path::new("p.diff"));
        assert_eq!(apply_patches(&cfg), PatchExit::Ok);

        let mut rev = cfg_with_patch(Path::new("p.diff"));
        rev.reverse = true;
        rev.reverse_specified = true;
        let exit = apply_patches(&rev);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("f")), "one\n");
    }

    #[test]
    fn it_uses_directory_option() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("directory");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        write_text(&td.path().join("sub/f"), "a\n");
        write_text(&td.path().join("sub/p.diff"), "--- f\n+++ f\n@@ -1 +1 @@\n-a\n+b\n");

        let mut cfg = cfg_with_patch(&td.path().join("sub/p.diff"));
        cfg.directory = Some(td.path().join("sub"));
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("sub/f")), "b\n");
    }

    #[test]
    fn it_handles_git_style_default_strip() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("gitstrip");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("dir/f"), "a\n");
        write_text(
            Path::new("p.diff"),
            "diff --git a/dir/f b/dir/f\n--- a/dir/f\n+++ b/dir/f\n@@ -1 +1 @@\n-a\n+b\n",
        );

        let cfg = cfg_with_patch(Path::new("p.diff"));
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("dir/f")), "b\n");
    }

    #[test]
    fn it_honors_explicit_strip_count() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("stripn");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("f"), "a\n");
        write_text(
            Path::new("p.diff"),
            "--- x/y/f\n+++ x/y/f\n@@ -1 +1 @@\n-a\n+b\n",
        );

        let mut cfg = cfg_with_patch(Path::new("p.diff"));
        cfg.strip = Some(2);
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("f")), "b\n");
    }

    #[test]
    fn it_writes_reject_file_on_failed_hunk() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("reject");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("f"), "one\n");
        write_text(Path::new("p.diff"), "--- f\n+++ f\n@@ -1 +1 @@\n-two\n+three\n");

        let mut cfg = cfg_with_patch(Path::new("p.diff"));
        cfg.reject_file = Some(PathBuf::from("rej.out"));
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::SomeFailed);
        let rej = td.path().join("rej.out");
        assert!(rej.exists());
        assert!(!read_text(&rej).is_empty());
    }

    #[test]
    fn it_can_copy_orig_to_output_on_empty_patch() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("emptycopy");

        write_text(&td.path().join("orig.txt"), "hello\nworld\n");
        write_text(&td.path().join("empty.diff"), "");

        let mut cfg = cfg_with_patch(&td.path().join("empty.diff"));
        cfg.origfile = Some(td.path().join("orig.txt"));
        cfg.output = Some(td.path().join("out.txt"));
        let exit = apply_patches(&cfg);

        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("out.txt")), "hello\nworld\n");
    }

    #[test]
    fn it_respects_ignore_whitespace_matching() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("ws");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("f"), "a  b\n");
        write_text(Path::new("p.diff"), "--- f\n+++ f\n@@ -1 +1 @@\n-a\tb\n+ok\n");

        let mut cfg = cfg_with_patch(Path::new("p.diff"));
        cfg.ignore_whitespace = true;
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert_eq!(read_text(&td.path().join("f")), "ok\n");
    }

    #[test]
    fn it_can_remove_empty_files_option() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("emptyrm");
        let old_cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
        std::env::set_current_dir(td.path()).expect("chdir temp");

        write_text(Path::new("f"), "one\n");
        write_text(Path::new("p.diff"), "--- f\n+++ f\n@@ -1 +0,0 @@\n-one\n");

        let mut cfg = cfg_with_patch(Path::new("p.diff"));
        cfg.remove_empty_files = true;
        let exit = apply_patches(&cfg);

        std::env::set_current_dir(old_cwd).expect("restore cwd");
        assert_eq!(exit, PatchExit::Ok);
        assert!(!td.path().join("f").exists());
    }

    #[test]
    fn it_reports_trouble_for_garbage_patch() {
        let _guard = test_lock().lock().unwrap_or_else(|e| e.into_inner());
        let td = TempDirGuard::new("garbage");
        write_text(&td.path().join("g.diff"), "this is not a patch\n");

        let cfg = cfg_with_patch(&td.path().join("g.diff"));
        let exit = apply_patches(&cfg);

        assert_eq!(exit, PatchExit::Trouble);
    }
