// Integration coverage for the client mouse input contract using uinput.
//
// Scope: exercise `MouseAggregator` with synthetic relative and absolute input
// devices so event translation stays deterministic.
// Targets: `client/src/input/mouse.rs`.
// Why: mouse handling is event-rich and high-risk for regressions without
// end-to-end event-path tests.

use evdev::uinput::VirtualDevice;
use evdev::{
    AbsInfo, AbsoluteAxisCode, AttributeSet, Device, EventType, InputEvent, KeyCode,
    RelativeAxisCode, UinputAbsSetup,
};
use lesavka_client::input::mouse::MouseAggregator;
use serial_test::serial;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use tokio::sync::broadcast;

fn open_virtual_node(vdev: &mut VirtualDevice) -> Option<PathBuf> {
    let mut node = None;
    for _ in 0..40 {
        if let Ok(mut nodes) = vdev.enumerate_dev_nodes_blocking()
            && let Some(Ok(path)) = nodes.next()
        {
            node = Some(path);
            break;
        }
        thread::sleep(Duration::from_millis(10));
    }
    node
}

fn open_virtual_device(vdev: &mut VirtualDevice) -> Option<Device> {
    let node = open_virtual_node(vdev)?;
    let dev = Device::open(node).ok()?;
    dev.set_nonblocking(true).ok()?;
    Some(dev)
}

fn build_relative_mouse(name: &str) -> Option<(VirtualDevice, Device)> {
    let mut keys = AttributeSet::<KeyCode>::new();
    keys.insert(KeyCode::BTN_LEFT);
    keys.insert(KeyCode::BTN_RIGHT);

    let mut rel = AttributeSet::<RelativeAxisCode>::new();
    rel.insert(RelativeAxisCode::REL_X);
    rel.insert(RelativeAxisCode::REL_Y);
    rel.insert(RelativeAxisCode::REL_WHEEL);

    let mut vdev = VirtualDevice::builder()
        .ok()?
        .name(name)
        .with_keys(&keys)
        .ok()?
        .with_relative_axes(&rel)
        .ok()?
        .build()
        .ok()?;

    let dev = open_virtual_device(&mut vdev)?;
    Some((vdev, dev))
}

fn build_absolute_touch_mouse(name: &str) -> Option<(VirtualDevice, Device)> {
    let mut keys = AttributeSet::<KeyCode>::new();
    keys.insert(KeyCode::BTN_TOUCH);

    let abs = AbsInfo::new(0, 0, 1000, 0, 0, 0);
    let mut vdev = VirtualDevice::builder()
        .ok()?
        .name(name)
        .with_keys(&keys)
        .ok()?
        .with_absolute_axis(&UinputAbsSetup::new(AbsoluteAxisCode::ABS_X, abs))
        .ok()?
        .with_absolute_axis(&UinputAbsSetup::new(AbsoluteAxisCode::ABS_Y, abs))
        .ok()?
        .with_absolute_axis(&UinputAbsSetup::new(
            AbsoluteAxisCode::ABS_MT_TRACKING_ID,
            AbsInfo::new(0, -1, 10, 0, 0, 0),
        ))
        .ok()?
        .build()
        .ok()?;

    let dev = open_virtual_device(&mut vdev)?;
    Some((vdev, dev))
}

#[test]
#[serial]
fn relative_mouse_events_emit_expected_packets() {
    let Some((mut vdev, dev)) = build_relative_mouse("lesavka-test-mouse-rel") else {
        return;
    };

    let (tx, mut rx) = broadcast::channel(32);
    let mut agg = MouseAggregator::new(dev, true, tx);

    agg.set_grab(false);
    agg.set_send(true);

    vdev.emit(&[
        InputEvent::new(EventType::KEY.0, KeyCode::BTN_LEFT.0, 1),
        InputEvent::new(EventType::RELATIVE.0, RelativeAxisCode::REL_X.0, 12),
        InputEvent::new(EventType::RELATIVE.0, RelativeAxisCode::REL_Y.0, -5),
        InputEvent::new(EventType::RELATIVE.0, RelativeAxisCode::REL_WHEEL.0, 1),
    ])
    .expect("emit relative press/move events");

    thread::sleep(Duration::from_millis(25));
    agg.process_events();

    let pkt = rx.try_recv().expect("mouse report");
    assert_eq!(pkt.data[0], 1);
    assert_eq!(pkt.data[1], 12);
    assert_eq!(pkt.data[2], (-5_i8) as u8);
    assert_eq!(pkt.data[3], 1);

    vdev.emit(&[InputEvent::new(EventType::KEY.0, KeyCode::BTN_LEFT.0, 0)])
        .expect("emit button release");
    thread::sleep(Duration::from_millis(25));
    agg.process_events();

    let release_pkt = rx.try_recv().expect("release report");
    assert_eq!(release_pkt.data[0], 0);
}

#[test]
#[serial]
fn absolute_touch_events_translate_into_motion_and_reset() {
    let Some((mut vdev, dev)) = build_absolute_touch_mouse("lesavka-test-mouse-abs") else {
        return;
    };

    let (tx, mut rx) = broadcast::channel(32);
    let mut agg = MouseAggregator::new(dev, true, tx);

    vdev.emit(&[
        InputEvent::new(EventType::KEY.0, KeyCode::BTN_TOUCH.0, 1),
        InputEvent::new(EventType::ABSOLUTE.0, AbsoluteAxisCode::ABS_X.0, 100),
        InputEvent::new(EventType::ABSOLUTE.0, AbsoluteAxisCode::ABS_Y.0, 100),
    ])
    .expect("emit initial touch frame");
    thread::sleep(Duration::from_millis(20));
    agg.process_events();

    vdev.emit(&[
        InputEvent::new(EventType::ABSOLUTE.0, AbsoluteAxisCode::ABS_X.0, 300),
        InputEvent::new(EventType::ABSOLUTE.0, AbsoluteAxisCode::ABS_Y.0, 500),
    ])
    .expect("emit movement frame");
    thread::sleep(Duration::from_millis(20));
    agg.process_events();

    let pkt = rx.try_recv().expect("absolute movement report");
    assert_eq!(pkt.data[0], 1);
    assert_ne!(pkt.data[1], 0);
    assert_ne!(pkt.data[2], 0);

    agg.set_send(false);
    agg.reset_state();
    assert!(
        rx.try_recv().is_err(),
        "send-disabled reset should not emit"
    );

    agg.set_send(true);
    agg.reset_state();
    let reset_pkt = rx.try_recv().expect("reset report");
    assert_eq!(reset_pkt.data, vec![0, 0, 0, 0]);
}
