//! Coverage for keyboard paste debounce and RPC branches.
//!
//! Scope: include keyboard input source and cover paste debounce/RPC paths.
//! Targets: `client/src/input/keyboard.rs`.
//! Why: paste routing has operator-visible side effects, and keeping it in a
//! focused contract keeps keyboard coverage under the 500 LOC hygiene cap.

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

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

    use serial_test::serial;
    use temp_env::with_var;

    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
    }

    #[test]
    #[serial]
    fn live_modifier_report_honors_configured_staging_delay() {
        let (tx, mut rx) = tokio::sync::broadcast::channel(4);
        with_var("LESAVKA_LIVE_MODIFIER_DELAY_MS", Some("1"), || {
            emit_live_keyboard_report(
                &tx,
                evdev::KeyCode::KEY_A,
                1,
                [0x01, 0, 0x04, 0, 0, 0, 0, 0],
            );
        });

        let staged = rx.try_recv().expect("modifier-only report");
        assert_eq!(staged.data, vec![0x01, 0, 0, 0, 0, 0, 0, 0]);
        let final_report = rx.try_recv().expect("final key report");
        assert_eq!(final_report.data, vec![0x01, 0, 0x04, 0, 0, 0, 0, 0]);
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn try_handle_paste_event_coverage_path_respects_debounce_fallthrough() {
        let Some(dev) = open_any_keyboard_device()
            .or_else(|| build_keyboard("lesavka-include-kbd-coverage-debounce"))
        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);

        let now_ms = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis() as u64;
        LAST_PASTE_MS.store(now_ms, Ordering::Relaxed);

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

        let pkt = rx
            .try_recv()
            .expect("debounced paste should still emit a swallowed empty report");
        assert_eq!(pkt.data, vec![0; 8]);
        assert!(
            rx.try_recv().is_err(),
            "debounced paste should not emit HID reports"
        );
        LAST_PASTE_MS.store(0, Ordering::Relaxed);
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn try_handle_paste_event_coverage_path_invokes_rpc_when_enabled() {
        let Some(dev) = open_any_keyboard_device()
            .or_else(|| build_keyboard("lesavka-include-kbd-coverage-rpc"))
        else {
            return;
        };
        let (paste_tx, mut paste_rx) = tokio::sync::mpsc::unbounded_channel::<String>();
        let (tx, _rx) = tokio::sync::broadcast::channel(32);
        let mut agg = KeyboardAggregator::new(dev, false, tx, Some(paste_tx));
        agg.paste_enabled = true;
        agg.paste_rpc_enabled = true;
        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 'rpc-coverage'"),
            || {
                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 payload = paste_rx.try_recv().expect("rpc payload");
        assert_eq!(payload, "rpc-coverage");
    }
}
