// Focused coverage for shifted live-key emission.
//
// Scope: verify the keyboard aggregator stages modifier state before shifted
// printable keys so firmware and bootloaders do not miss the modifier bit.
// Targets: `client/src/input/keyboard.rs`.
// Why: modifier chords and overlapping presses must remain trustworthy under
// real evdev timing so remote typing stays usable.

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

#[allow(warnings)]
mod keyboard_shift_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;

    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_LEFTSHIFT,
            evdev::KeyCode::KEY_LEFTCTRL,
            evdev::KeyCode::KEY_LEFTALT,
            evdev::KeyCode::KEY_A,
            evdev::KeyCode::KEY_S,
            evdev::KeyCode::KEY_F,
            evdev::KeyCode::KEY_9,
        ] {
            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))
    }

    fn build_keyboard(name: &str) -> Option<evdev::Device> {
        build_keyboard_pair(name).map(|(_, dev)| dev)
    }

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

    #[test]
    #[serial]
    fn shifted_live_keypress_reasserts_modifier_before_key_usage() {
        let Some(dev) = build_keyboard("lesavka-kbd-shift-stage") else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTSHIFT);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_9);

        with_var("LESAVKA_LIVE_MODIFIER_DELAY_MS", Some("0"), || {
            let report = agg.build_report();
            agg.emit_live_report(evdev::KeyCode::KEY_9, 1, report);
        });

        let staged = rx.try_recv().expect("modifier stage report");
        assert_eq!(staged.data, vec![0x02, 0, 0, 0, 0, 0, 0, 0]);

        let combined = rx.try_recv().expect("combined shifted key report");
        assert_eq!(combined.data, vec![0x02, 0, 0x26, 0, 0, 0, 0, 0]);
    }

    #[test]
    #[serial]
    fn unshifted_live_keypress_stays_single_report() {
        let Some(dev) = build_keyboard("lesavka-kbd-unshifted-single") else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_9);

        with_var("LESAVKA_LIVE_MODIFIER_DELAY_MS", Some("0"), || {
            let report = agg.build_report();
            agg.emit_live_report(evdev::KeyCode::KEY_9, 1, report);
        });

        let combined = rx.try_recv().expect("plain key report");
        assert_eq!(combined.data, vec![0, 0, 0x26, 0, 0, 0, 0, 0]);
        assert!(rx.try_recv().is_err());
    }

    #[test]
    #[serial]
    fn ctrl_chord_reasserts_modifier_before_key_usage() {
        let Some(dev) = build_keyboard("lesavka-kbd-ctrl-stage") else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTCTRL);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_A);

        with_var("LESAVKA_LIVE_MODIFIER_DELAY_MS", Some("0"), || {
            let report = agg.build_report();
            agg.emit_live_report(evdev::KeyCode::KEY_A, 1, report);
        });

        let staged = rx.try_recv().expect("modifier stage report");
        assert_eq!(staged.data, vec![0x01, 0, 0, 0, 0, 0, 0, 0]);

        let combined = rx.try_recv().expect("combined ctrl chord report");
        assert_eq!(combined.data, vec![0x01, 0, 0x04, 0, 0, 0, 0, 0]);
    }

    #[test]
    #[serial]
    fn alt_chord_reasserts_modifier_before_key_usage() {
        let Some(dev) = build_keyboard("lesavka-kbd-alt-stage") else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_LEFTALT);
        agg.pressed_keys.insert(evdev::KeyCode::KEY_F);

        with_var("LESAVKA_LIVE_MODIFIER_DELAY_MS", Some("0"), || {
            let report = agg.build_report();
            agg.emit_live_report(evdev::KeyCode::KEY_F, 1, report);
        });

        let staged = rx.try_recv().expect("modifier stage report");
        assert_eq!(staged.data, vec![0x04, 0, 0, 0, 0, 0, 0, 0]);

        let combined = rx.try_recv().expect("combined alt chord report");
        assert_eq!(combined.data, vec![0x04, 0, 0x09, 0, 0, 0, 0, 0]);
    }

    #[test]
    #[serial]
    fn process_events_emits_shifted_letter_with_modifier_bit() {
        let Some((mut vdev, dev)) = build_keyboard_pair("lesavka-kbd-shift-live") else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);

        vdev.emit(&[
            evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_LEFTSHIFT.0, 1),
            evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_A.0, 1),
        ])
        .expect("emit shifted key");
        thread::sleep(std::time::Duration::from_millis(25));

        with_var("LESAVKA_LIVE_MODIFIER_DELAY_MS", Some("0"), || {
            agg.process_events();
        });

        let mut saw_shifted_a = false;
        while let Ok(pkt) = rx.try_recv() {
            if pkt.data == vec![0x02, 0, 0x04, 0, 0, 0, 0, 0] {
                saw_shifted_a = true;
                break;
            }
        }
        assert!(
            saw_shifted_a,
            "expected shifted A report in live event stream"
        );
    }

    #[test]
    #[serial]
    fn process_events_emits_ctrl_chord_with_modifier_bit() {
        let Some((mut vdev, dev)) = build_keyboard_pair("lesavka-kbd-ctrl-live") else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);

        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_A.0, 1),
        ])
        .expect("emit ctrl chord");
        thread::sleep(std::time::Duration::from_millis(25));

        with_var("LESAVKA_LIVE_MODIFIER_DELAY_MS", Some("0"), || {
            agg.process_events();
        });

        let mut saw_ctrl_a = false;
        while let Ok(pkt) = rx.try_recv() {
            if pkt.data == vec![0x01, 0, 0x04, 0, 0, 0, 0, 0] {
                saw_ctrl_a = true;
                break;
            }
        }
        assert!(saw_ctrl_a, "expected ctrl+A report in live event stream");
    }

    #[test]
    #[serial]
    fn process_events_tracks_overlapping_plain_keys_without_sticking() {
        let Some((mut vdev, dev)) = build_keyboard_pair("lesavka-kbd-overlap-live") else {
            return;
        };
        let (mut agg, mut rx) = new_aggregator(dev);

        vdev.emit(&[
            evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_A.0, 1),
            evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_S.0, 1),
            evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_A.0, 0),
            evdev::InputEvent::new(evdev::EventType::KEY.0, evdev::KeyCode::KEY_S.0, 0),
        ])
        .expect("emit overlapping plain keys");
        thread::sleep(std::time::Duration::from_millis(25));

        agg.process_events();

        let mut reports = Vec::new();
        while let Ok(pkt) = rx.try_recv() {
            reports.push(pkt.data);
        }

        assert!(
            reports.contains(&vec![0, 0, 0x04, 0, 0, 0, 0, 0]),
            "expected A down report, got {reports:?}"
        );
        assert!(
            reports.iter().any(|pkt| {
                let keys = &pkt[2..8];
                keys.contains(&0x04) && keys.contains(&0x16)
            }),
            "expected A+S overlap report, got {reports:?}"
        );
        assert!(
            reports.contains(&vec![0, 0, 0x16, 0, 0, 0, 0, 0]),
            "expected lone S report after A released, got {reports:?}"
        );
        assert!(
            reports.contains(&vec![0; 8]),
            "expected final empty report after both releases, got {reports:?}"
        );
    }
}
