// Keyboard event-processing coverage for swallowed and live-report paths.
//
// Scope: include keyboard aggregation and drive synthetic evdev updates through
// `process_events`/`drain_key_updates`.
// Targets: `client/src/input/keyboard.rs`.
// Why: paste chords must be swallowed cleanly while normal keys keep flowing.

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

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

    use evdev::AttributeSet;
    use evdev::uinput::VirtualDevice;
    use serial_test::serial;
    use std::thread;
    use temp_env::{with_var, with_vars};

    fn open_virtual_device(vdev: &mut VirtualDevice) -> Option<evdev::Device> {
        for _ in 0..40 {
            if let Ok(mut nodes) = vdev.enumerate_dev_nodes_blocking() {
                if let Some(Ok(path)) = nodes.next() {
                    if let Ok(dev) = evdev::Device::open(path) {
                        let _ = dev.set_nonblocking(true);
                        return Some(dev);
                    }
                }
            }
            thread::sleep(std::time::Duration::from_millis(10));
        }
        None
    }

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

        let mut vdev = VirtualDevice::builder()
            .ok()?
            .name(name)
            .with_keys(&keys)
            .ok()?
            .build()
            .ok()?;
        let dev = open_virtual_device(&mut vdev)?;
        Some((vdev, dev))
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn process_events_skips_swallowed_paste_chord_updates() {
        let Some((mut vdev, dev)) = build_keyboard_pair("lesavka-kbd-process-paste") else {
            return;
        };
        let (tx, mut rx) = tokio::sync::broadcast::channel(64);
        let mut agg = KeyboardAggregator::new(dev, true, tx, None);
        agg.paste_enabled = true;
        agg.paste_rpc_enabled = false;

        with_vars(
            [
                ("LESAVKA_CLIPBOARD_CHORD", Some("ctrl+alt+v")),
                ("LESAVKA_CLIPBOARD_DEBOUNCE_MS", Some("0")),
                ("LESAVKA_CLIPBOARD_CMD", Some("printf 'a'")),
            ],
            || {
                vdev.emit(&[
                    evdev::InputEvent::new(
                        evdev::EventType::KEY.0,
                        evdev::KeyCode::KEY_LEFTCTRL.0,
                        1,
                    ),
                    evdev::InputEvent::new(
                        evdev::EventType::KEY.0,
                        evdev::KeyCode::KEY_LEFTALT.0,
                        1,
                    ),
                    evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_V.0, 1),
                ])
                .expect("emit paste chord");
                thread::sleep(std::time::Duration::from_millis(25));
                agg.process_events();
            },
        );

        let reports: Vec<Vec<u8>> =
            std::iter::from_fn(|| rx.try_recv().ok().map(|pkt| pkt.data)).collect();
        assert!(
            reports.iter().any(|report| report == &vec![0; 8]),
            "swallowed paste chord should publish empty guard reports"
        );
        assert!(
            reports
                .iter()
                .all(|report| report.get(2).copied() != Some(evdev::KeyCode::KEY_V.0 as u8)),
            "literal V should not leak after the paste chord is swallowed"
        );
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn drain_key_updates_covers_dev_mode_logging_and_env_disable() {
        let Some((mut vdev, dev)) = build_keyboard_pair("lesavka-kbd-process-live") else {
            return;
        };
        let (tx, _rx) = tokio::sync::broadcast::channel(16);

        with_var("LESAVKA_CLIPBOARD_PASTE", Some("0"), || {
            let mut agg = KeyboardAggregator::new(dev, true, tx, None);
            assert!(!agg.paste_enabled);
            vdev.emit(&[evdev::InputEvent::new(
                evdev::EventType::KEY.0,
                evdev::KeyCode::KEY_A.0,
                1,
            )])
            .expect("emit live key");
            thread::sleep(std::time::Duration::from_millis(25));

            let updates = agg.drain_key_updates();
            assert_eq!(updates.len(), 1);
            assert!(!updates[0].swallowed);
        });
    }
}
