// Extra integration coverage for client keyboard aggregator helpers.
//
// Scope: include keyboard input source and cover reset-state, clipboard
// fallback, send-toggle, and auxiliary paste branches without blowing the
// primary keyboard contract past the 500 LOC cap.
// Targets: `client/src/input/keyboard.rs`.
// Why: keyboard helper branches are numerous enough to merit a paired
// contract file for hygiene-gate modularity.

mod keymap {
    pub use lesavka_client::input::keymap::*;
}

#[allow(warnings)]
mod keyboard_contract_extra {
    include!(env!("LESAVKA_CLIENT_KEYBOARD_SRC"));

    use serial_test::serial;
    use std::fs;
    use std::os::unix::fs::PermissionsExt;
    use std::path::Path;
    use temp_env::{with_var, with_vars};
    use tempfile::tempdir;

    fn write_executable(dir: &Path, name: &str, body: &str) {
        let path = dir.join(name);
        fs::write(&path, body).expect("write script");
        let mut perms = fs::metadata(&path).expect("metadata").permissions();
        perms.set_mode(0o755);
        fs::set_permissions(path, perms).expect("chmod");
    }

    fn with_fake_path_command(name: &str, script_body: &str, f: impl FnOnce()) {
        let dir = tempdir().expect("tempdir");
        write_executable(dir.path(), name, script_body);
        let prior = std::env::var("PATH").unwrap_or_default();
        let merged = if prior.is_empty() {
            dir.path().display().to_string()
        } else {
            format!("{}:{prior}", dir.path().display())
        };
        with_var("PATH", Some(merged), f);
    }

    fn open_any_keyboard_device() -> Option<evdev::Device> {
        let entries = std::fs::read_dir("/dev/input").ok()?;
        for entry in entries.flatten() {
            let path = entry.path();
            let name = path.file_name()?.to_string_lossy();
            if !name.starts_with("event") {
                continue;
            }
            let dev = evdev::Device::open(path).ok()?;
            let _ = dev.set_nonblocking(true);
            let looks_like_keyboard = dev
                .supported_keys()
                .map(|keys| {
                    keys.contains(evdev::KeyCode::KEY_A)
                        && keys.contains(evdev::KeyCode::KEY_ENTER)
                        && keys.contains(evdev::KeyCode::KEY_LEFTCTRL)
                })
                .unwrap_or(false);
            if looks_like_keyboard {
                return Some(dev);
            }
        }
        None
    }

    fn build_keyboard(name: &str) -> Option<evdev::Device> {
        let mut keys = evdev::AttributeSet::<evdev::KeyCode>::new();
        for key in [
            evdev::KeyCode::KEY_A,
            evdev::KeyCode::KEY_B,
            evdev::KeyCode::KEY_V,
            evdev::KeyCode::KEY_LEFTCTRL,
            evdev::KeyCode::KEY_LEFTALT,
        ] {
            keys.insert(key);
        }

        let mut vdev = evdev::uinput::VirtualDevice::builder()
            .ok()?
            .name(name)
            .with_keys(&keys)
            .ok()?
            .build()
            .ok()?;
        for _ in 0..40 {
            if let Ok(mut nodes) = vdev.enumerate_dev_nodes_blocking() {
                if let Some(Ok(path)) = nodes.next() {
                    let dev = evdev::Device::open(path).ok()?;
                    dev.set_nonblocking(true).ok()?;
                    return Some(dev);
                }
            }
            std::thread::sleep(std::time::Duration::from_millis(10));
        }
        None
    }

    fn new_aggregator(
        dev: evdev::Device,
    ) -> (
        KeyboardAggregator,
        tokio::sync::broadcast::Receiver<KeyboardReport>,
    ) {
        let (tx, rx) = tokio::sync::broadcast::channel(64);
        (KeyboardAggregator::new(dev, true, tx, None), rx)
    }

    #[test]
    #[serial]
    fn reset_state_when_idle_still_emits_an_empty_report() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-reset-idle"))
        else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.reset_state();
        let pkt = rx
            .try_recv()
            .expect("idle reset should still publish empty report");
        assert_eq!(pkt.data, vec![0; 8]);
    }

    #[test]
    #[serial]
    fn pressed_keys_snapshot_returns_the_current_keyset() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-snapshot"))
        else {
            return;
        };
        let (mut agg, _) = new_aggregator(dev);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_A);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTCTRL);
        let snapshot = agg.pressed_keys_snapshot();
        assert!(snapshot.contains(&evdev::KeyCode::KEY_A));
        assert!(snapshot.contains(&evdev::KeyCode::KEY_LEFTCTRL));
        assert_eq!(snapshot.len(), 2);
    }

    #[test]
    fn update_pressed_keys_tracks_press_and_release_values() {
        let mut pressed = HashSet::new();
        update_pressed_keys(&mut pressed, evdev::KeyCode::KEY_A, 1);
        assert!(pressed.contains(&evdev::KeyCode::KEY_A));

        update_pressed_keys(&mut pressed, evdev::KeyCode::KEY_A, 0);
        assert!(!pressed.contains(&evdev::KeyCode::KEY_A));

        update_pressed_keys(&mut pressed, evdev::KeyCode::KEY_B, 2);
        assert!(pressed.contains(&evdev::KeyCode::KEY_B));
    }

    #[test]
    fn debounce_gate_honors_zero_and_nonzero_windows() {
        let last = AtomicU64::new(123);
        assert!(debounce_gate(&last, 500, 0));
        assert_eq!(last.load(Ordering::Relaxed), 500);

        last.store(9_900, Ordering::Relaxed);
        assert!(!debounce_gate(&last, 10_000, 500));
        assert_eq!(last.load(Ordering::Relaxed), 10_000);

        assert!(debounce_gate(&last, 10_700, 500));
        assert_eq!(last.load(Ordering::Relaxed), 10_700);
    }

    #[test]
    #[serial]
    fn paste_rpc_enabled_from_env_honors_flag_and_key_variants() {
        let home = tempdir().expect("tempdir");
        temp_env::with_vars(
            [
                ("HOME", home.path().to_str()),
                ("LESAVKA_PASTE_KEY_FILE", None::<&str>),
                ("LESAVKA_PASTE_RPC", Some("0")),
                ("LESAVKA_PASTE_KEY", Some("shared-key")),
            ],
            || {
                assert!(!paste_rpc_enabled_from_env());
            },
        );

        temp_env::with_vars(
            [
                ("HOME", home.path().to_str()),
                ("LESAVKA_PASTE_KEY_FILE", None::<&str>),
                ("LESAVKA_PASTE_RPC", Some("1")),
                ("LESAVKA_PASTE_KEY", Some("   ")),
            ],
            || {
                assert!(!paste_rpc_enabled_from_env());
            },
        );

        temp_env::with_vars(
            [
                ("HOME", home.path().to_str()),
                ("LESAVKA_PASTE_KEY_FILE", None::<&str>),
                ("LESAVKA_PASTE_RPC", Some("1")),
                ("LESAVKA_PASTE_KEY", Some("shared-key")),
            ],
            || {
                assert!(paste_rpc_enabled_from_env());
            },
        );
    }

    #[test]
    #[serial]
    fn set_send_false_blocks_manual_empty_report() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-nosend"))
        else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.set_send(false);
        agg.send_empty_report();
        assert!(rx.try_recv().is_err());
    }

    #[test]
    #[serial]
    fn paste_chord_active_supports_ctrl_v_variant() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-ctrl-v"))
        else {
            return;
        };
        let (mut agg, _) = new_aggregator(dev);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTCTRL);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_V);
        with_var("LESAVKA_CLIPBOARD_CHORD", Some("ctrl+v"), || {
            assert!(agg.paste_chord_active());
        });
    }

    #[test]
    #[serial]
    fn ctrl_v_reaches_remote_when_lesavka_paste_chord_requires_alt() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-ctrl-v-app"))
        else {
            return;
        };
        let (mut agg, _) = new_aggregator(dev);
        agg.paste_enabled = true;
        agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTCTRL);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_V);

        with_var("LESAVKA_CLIPBOARD_CHORD", Some("ctrl+alt+v"), || {
            assert!(
                !agg.try_handle_paste_event(evdev::KeyCode::KEY_V, 1),
                "plain Ctrl+V should relay as an app shortcut, not trigger Lesavka paste"
            );
        });
        assert!(!agg.paste_chord_armed);
        assert!(!agg.paste_chord_consumed);
    }

    #[test]
    #[serial]
    fn paste_via_rpc_returns_false_without_sender() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-rpc-none"))
        else {
            return;
        };
        let (tx, _rx) = tokio::sync::broadcast::channel(8);
        let agg = KeyboardAggregator::new(dev, false, tx, None);
        assert!(!agg.paste_via_rpc());
    }

    #[test]
    #[serial]
    fn paste_via_rpc_returns_true_for_empty_clipboard_payload() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-rpc-empty"))
        else {
            return;
        };
        let (paste_tx, mut paste_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
        let (kbd_tx, _rx) = tokio::sync::broadcast::channel(8);
        let agg = KeyboardAggregator::new(dev, false, kbd_tx, Some(paste_tx));
        let wl_paste_empty = "#!/usr/bin/env sh\nexit 0\n";
        with_var("LESAVKA_CLIPBOARD_CMD", None::<&str>, || {
            with_fake_path_command("wl-paste", wl_paste_empty, || {
                assert!(
                    agg.paste_via_rpc(),
                    "empty clipboard should still consume the chord"
                );
                assert!(
                    paste_rx.try_recv().is_err(),
                    "empty clipboard should not enqueue payload"
                );
            });
        });
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn trigger_clipboard_paste_covers_disabled_rpc_and_hid_fallback_paths() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-trigger-1"))
        else {
            return;
        };
        let (tx, mut rx) = tokio::sync::broadcast::channel(32);
        let mut disabled = KeyboardAggregator::new(dev, false, tx, None);
        disabled.paste_enabled = false;
        disabled.trigger_clipboard_paste();
        assert!(rx.try_recv().is_err());

        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-trigger-2"))
        else {
            return;
        };
        let (paste_tx, mut paste_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
        let (tx, mut rx) = tokio::sync::broadcast::channel(32);
        let mut rpc = KeyboardAggregator::new(dev, false, tx, Some(paste_tx));
        rpc.paste_enabled = true;
        rpc.paste_rpc_enabled = true;
        with_var(
            "LESAVKA_CLIPBOARD_CMD",
            Some("printf 'trigger-rpc'"),
            || {
                rpc.trigger_clipboard_paste();
            },
        );
        assert_eq!(paste_rx.try_recv().expect("rpc payload"), "trigger-rpc");
        assert!(rx.try_recv().is_err());

        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-trigger-3"))
        else {
            return;
        };
        let (tx, mut rx) = tokio::sync::broadcast::channel(32);
        let mut hid = KeyboardAggregator::new(dev, false, tx, None);
        hid.paste_enabled = true;
        hid.paste_rpc_enabled = false;
        with_var("LESAVKA_CLIPBOARD_CMD", Some("printf 'b'"), || {
            with_var("LESAVKA_CLIPBOARD_DELAY_MS", Some("0"), || {
                hid.trigger_clipboard_paste();
            });
        });
        assert!(
            rx.try_recv().is_ok(),
            "HID fallback should emit keyboard reports"
        );
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn disabled_send_suppresses_direct_reports() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-send-off"))
        else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.set_send(false);
        agg.send_empty_report();
        agg.emit_live_report(evdev::KeyCode::KEY_A, 1, [0, 0, 4, 0, 0, 0, 0, 0]);
        assert!(rx.try_recv().is_err());
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn paste_clipboard_skips_unsupported_chars_in_coverage() {
        let Some(dev) = open_any_keyboard_device()
            .or_else(|| build_keyboard("lesavka-include-kbd-coverage-unsupported"))
        else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.paste_enabled = true;

        with_var("LESAVKA_CLIPBOARD_CMD", Some("printf '🚀'"), || {
            agg.paste_clipboard();
        });

        assert!(
            rx.try_recv().is_err(),
            "unsupported clipboard characters should not emit HID reports"
        );
    }

    #[test]
    #[serial]
    fn set_grab_path_is_non_panicking() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-grab"))
        else {
            return;
        };
        let (mut agg, _) = new_aggregator(dev);
        agg.set_grab(false);
        agg.set_grab(true);
    }

    #[test]
    #[serial]
    fn try_handle_paste_event_swallows_incomplete_chord_sequences() {
        let Some(dev) =
            open_any_keyboard_device().or_else(|| build_keyboard("lesavka-include-kbd-incomplete"))
        else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.paste_enabled = true;
        agg.paste_chord_armed = true;
        agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTCTRL);

        assert!(agg.try_handle_paste_event(evdev::KeyCode::KEY_LEFTCTRL, 1));
        let pkt = rx.try_recv().expect("swallow report");
        assert_eq!(pkt.data, vec![0; 8]);
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn try_handle_paste_event_coverage_path_runs_hid_paste_and_empty_report() {
        let Some(dev) = open_any_keyboard_device()
            .or_else(|| build_keyboard("lesavka-include-kbd-coverage-paste"))
        else {
            return;
        };
        let (tx, mut rx) = tokio::sync::broadcast::channel(32);
        let mut agg = KeyboardAggregator::new(dev, false, tx, None);
        agg.paste_enabled = true;
        agg.paste_rpc_enabled = false;
        agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTCTRL);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTALT);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_V);

        with_var("LESAVKA_CLIPBOARD_CMD", Some("printf 'a'"), || {
            with_var("LESAVKA_CLIPBOARD_CHORD", Some("ctrl+alt+v"), || {
                with_var("LESAVKA_CLIPBOARD_DEBOUNCE_MS", Some("0"), || {
                    assert!(agg.try_handle_paste_event(evdev::KeyCode::KEY_V, 1));
                });
            });
        });

        let mut saw_hid_payload = false;
        let mut saw_empty = false;
        while let Ok(pkt) = rx.try_recv() {
            if pkt.data == vec![0; 8] {
                saw_empty = true;
            }
            if pkt.data.len() == 8 && pkt.data.iter().any(|byte| *byte != 0) {
                saw_hid_payload = true;
            }
        }
        assert!(
            saw_hid_payload,
            "coverage paste path should emit HID reports"
        );
        assert!(
            saw_empty,
            "coverage paste path should end with an empty report"
        );
    }
}
