//! Unit coverage for mouse event state transitions.
//!
//! Scope: exercise the private event decoder used by `MouseAggregator`.
//! Targets: `client/src/input/mouse.rs`.
//! Why: the quality gate tracks the real crate source, so the core event
//! handling needs deterministic coverage without depending on `/dev/uinput`.

use super::*;
use serial_test::serial;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;

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

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

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

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

    let mut vdev = evdev::uinput::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))
}

/// Returns whether this run may emit virtual mouse events into the OS input stack.
///
/// The default is false because local test gates often share the user's active
/// desktop, where a uinput mouse can steal focus or move the pointer.
fn disruptive_input_tests_enabled() -> bool {
    std::env::var("LESAVKA_ALLOW_DISRUPTIVE_INPUT_TESTS").as_deref() == Ok("1")
}

struct StateHarness {
    buttons: u8,
    dx: i8,
    dy: i8,
    wheel: i8,
    last_abs_x: Option<i32>,
    last_abs_y: Option<i32>,
    abs_scale: i32,
    abs_jump_x: i32,
    abs_jump_y: i32,
    has_touch_state: bool,
    touch_guarded: bool,
    touch_active: bool,
}

impl StateHarness {
    fn new() -> Self {
        Self {
            buttons: 0,
            dx: 0,
            dy: 0,
            wheel: 0,
            last_abs_x: None,
            last_abs_y: None,
            abs_scale: 8,
            abs_jump_x: 120,
            abs_jump_y: 120,
            has_touch_state: false,
            touch_guarded: false,
            touch_active: true,
        }
    }

    fn with_touch_state() -> Self {
        Self {
            has_touch_state: true,
            ..Self::new()
        }
    }

    fn state(&mut self) -> MouseEventState<'_> {
        MouseEventState {
            buttons: &mut self.buttons,
            dx: &mut self.dx,
            dy: &mut self.dy,
            wheel: &mut self.wheel,
            last_abs_x: &mut self.last_abs_x,
            last_abs_y: &mut self.last_abs_y,
            abs_scale: self.abs_scale,
            abs_jump_x: self.abs_jump_x,
            abs_jump_y: self.abs_jump_y,
            has_touch_state: self.has_touch_state,
            touch_guarded: &mut self.touch_guarded,
            touch_active: &mut self.touch_active,
        }
    }
}

#[test]
fn key_events_update_button_bits_and_touch_release_clears_origins() {
    let mut harness = StateHarness::with_touch_state();
    harness.last_abs_x = Some(100);
    harness.last_abs_y = Some(120);

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::BTN_LEFT.0, 1));
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::BTN_RIGHT.0, 1));
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::BTN_MIDDLE.0, 1));
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::KEY_A.0, 1));
    }

    assert_eq!(harness.buttons & 0b111, 0b111);

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::BTN_LEFT.0, 0));
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::BTN_RIGHT.0, 0));
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::BTN_MIDDLE.0, 0));
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::BTN_TOUCH.0, 1));
    }

    assert_eq!(harness.buttons & 0b111, 0);
    assert!(harness.touch_guarded);
    assert!(harness.touch_active);

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::BTN_TOUCH.0, 0));
    }

    assert!(!harness.touch_active);
    assert!(harness.last_abs_x.is_none());
    assert!(harness.last_abs_y.is_none());
    assert_eq!(harness.buttons & 0b111, 0);
}

#[test]
fn touch_contact_tracks_motion_without_pressing_left_button() {
    let mut harness = StateHarness::with_touch_state();

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(EventType::KEY.0, KeyCode::BTN_TOUCH.0, 1));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_POSITION_X.0,
            1000,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_POSITION_Y.0,
            1000,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_POSITION_X.0,
            1040,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_POSITION_Y.0,
            984,
        ));
    }

    assert!(harness.touch_guarded);
    assert!(harness.touch_active);
    assert_eq!(harness.buttons & 0b001, 0);
    assert_eq!(harness.dx, 5);
    assert_eq!(harness.dy, -2);
}

#[test]
fn relative_events_accumulate_motion_and_ignore_unknown_codes() {
    let mut harness = StateHarness::new();

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(
            EventType::RELATIVE.0,
            RelativeAxisCode::REL_X.0,
            11,
        ));
        state.apply_event(&InputEvent::new(
            EventType::RELATIVE.0,
            RelativeAxisCode::REL_Y.0,
            -7,
        ));
        state.apply_event(&InputEvent::new(
            EventType::RELATIVE.0,
            RelativeAxisCode::REL_WHEEL.0,
            1,
        ));
        state.apply_event(&InputEvent::new(
            EventType::RELATIVE.0,
            RelativeAxisCode::REL_RX.0,
            42,
        ));
    }

    assert_eq!(harness.dx, 11);
    assert_eq!(harness.dy, -7);
    assert_eq!(harness.wheel, 1);
}

#[test]
fn absolute_events_apply_scaled_delta_and_ignore_large_jumps_without_touch_state() {
    let mut harness = StateHarness::new();
    harness.last_abs_x = Some(100);
    harness.last_abs_y = Some(100);

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_X.0,
            140,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_Y.0,
            180,
        ));
    }

    assert_eq!(harness.dx, 5);
    assert_eq!(harness.dy, 10);
    assert_eq!(harness.last_abs_x, Some(140));
    assert_eq!(harness.last_abs_y, Some(180));

    harness.dx = 0;
    harness.dy = 0;

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_X.0,
            900,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_Y.0,
            900,
        ));
    }

    assert_eq!(harness.dx, 0);
    assert_eq!(harness.dy, 0);
    assert_eq!(harness.last_abs_x, Some(900));
    assert_eq!(harness.last_abs_y, Some(900));
}

#[test]
fn first_absolute_samples_only_seed_origin_without_motion() {
    let mut harness = StateHarness::new();

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_X.0,
            320,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_Y.0,
            640,
        ));
    }

    assert_eq!(harness.dx, 0);
    assert_eq!(harness.dy, 0);
    assert_eq!(harness.last_abs_x, Some(320));
    assert_eq!(harness.last_abs_y, Some(640));
}

#[test]
fn touch_guarded_absolute_updates_only_refresh_origins_until_touch_returns() {
    let mut harness = StateHarness::with_touch_state();
    harness.touch_guarded = true;
    harness.touch_active = false;
    harness.last_abs_x = Some(10);
    harness.last_abs_y = Some(20);

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_POSITION_X.0,
            50,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_POSITION_Y.0,
            70,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_TRACKING_ID.0,
            3,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_POSITION_X.0,
            82,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_POSITION_Y.0,
            102,
        ));
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_PRESSURE.0,
            999,
        ));
    }

    assert!(harness.touch_guarded);
    assert!(harness.touch_active);
    assert_eq!(harness.last_abs_x, Some(82));
    assert_eq!(harness.last_abs_y, Some(102));
    assert_eq!(harness.dx, 4);
    assert_eq!(harness.dy, 4);
}

#[test]
fn tracking_release_and_unhandled_event_types_stay_safe() {
    let mut harness = StateHarness::with_touch_state();
    harness.last_abs_x = Some(33);
    harness.last_abs_y = Some(44);

    {
        let mut state = harness.state();
        state.apply_event(&InputEvent::new(
            EventType::ABSOLUTE.0,
            AbsoluteAxisCode::ABS_MT_TRACKING_ID.0,
            -1,
        ));
        state.apply_event(&InputEvent::new(EventType::SWITCH.0, 0, 1));
    }

    assert!(harness.touch_guarded);
    assert!(!harness.touch_active);
    assert!(harness.last_abs_x.is_none());
    assert!(harness.last_abs_y.is_none());
}

#[test]
fn replay_events_applies_regular_events_and_flushes_on_sync_boundaries() {
    let mut harness = StateHarness::new();
    let (tx, mut rx) = tokio::sync::broadcast::channel(8);
    let mut next_send = Instant::now() - Duration::from_millis(5);
    let mut last_buttons = 0_u8;

    MouseRuntime {
        tx: &tx,
        dev_mode: true,
        sending_disabled: false,
        next_send: &mut next_send,
        last_buttons: &mut last_buttons,
        event_state: harness.state(),
    }
    .replay_events(vec![
        InputEvent::new(EventType::KEY.0, KeyCode::BTN_LEFT.0, 1),
        InputEvent::new(EventType::RELATIVE.0, RelativeAxisCode::REL_X.0, 9),
        InputEvent::new(EventType::SYNCHRONIZATION.0, 0, 0),
    ]);

    let pkt = rx.try_recv().expect("sync flush packet");
    assert_eq!(pkt.data[0], 1);
    assert_eq!(pkt.data[1], 9);
    assert_eq!(pkt.data[2], 0);
    assert_eq!(pkt.data[3], 0);
    assert_eq!(harness.dx, 0);
    assert_eq!(harness.dy, 0);
    assert_eq!(harness.wheel, 0);
    assert_eq!(last_buttons, 1);
}

#[test]
fn collect_fetched_events_handles_ok_would_block_and_other_errors() {
    let ok_events = collect_fetched_events(
        Ok(vec![InputEvent::new(
            EventType::RELATIVE.0,
            RelativeAxisCode::REL_X.0,
            1,
        )]),
        false,
    )
    .expect("ok result should yield an event batch");
    assert_eq!(ok_events.len(), 1);

    let would_block = collect_fetched_events::<Vec<InputEvent>>(
        Err(std::io::Error::new(
            std::io::ErrorKind::WouldBlock,
            "synthetic would-block",
        )),
        false,
    );
    assert!(would_block.is_none());

    let other_error = collect_fetched_events::<Vec<InputEvent>>(
        Err(std::io::Error::other("synthetic read failure")),
        true,
    );
    assert!(other_error.is_none());
}

#[test]
fn log_event_batch_tolerates_empty_and_populated_batches() {
    log_event_batch(false, Some("quiet-mouse"), &[]);
    log_event_batch(
        true,
        Some("trace-mouse"),
        &[InputEvent::new(
            EventType::RELATIVE.0,
            RelativeAxisCode::REL_Y.0,
            4,
        )],
    );
}

#[test]
#[serial]
/// Exercises the live uinput read path only when an isolated test worker opts in.
fn process_events_emits_live_relative_packets_for_the_real_crate_path() {
    if !disruptive_input_tests_enabled() {
        return;
    }

    let Some((mut vdev, dev)) = build_relative_mouse("lesavka-unit-mouse-rel") else {
        return;
    };

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

    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 frame");

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

    let pkt = rx.try_recv().expect("mouse packet");
    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);
}

#[test]
#[serial]
/// Verifies disconnect recovery only when virtual-device events are safe to emit.
fn process_events_handles_disconnected_virtual_devices_without_panicking() {
    if !disruptive_input_tests_enabled() {
        return;
    }

    let Some((vdev, dev)) = build_relative_mouse("lesavka-unit-mouse-err") else {
        return;
    };

    let (tx, _rx) = tokio::sync::broadcast::channel(4);
    let mut agg = MouseAggregator::new(dev, true, tx);

    drop(vdev);
    thread::sleep(Duration::from_millis(40));
    agg.process_events();
}
