use super::{
    build_keyboard_report, debounce_gate, is_modifier, is_paste_modifier, keycode_to_usage,
    live_modifier_delay, modifier_only_report, paste_key_available_from_env, paste_rpc_enabled,
    read_clipboard_text, should_stage_modifier_report, update_pressed_keys,
};
use evdev::KeyCode;
use std::{
    collections::HashSet,
    sync::atomic::{AtomicU64, Ordering},
    time::Duration,
};
use tempfile::tempdir;

#[test]
fn paste_rpc_disabled_when_env_off() {
    assert!(!paste_rpc_enabled(false, false));
    assert!(!paste_rpc_enabled(false, true));
}

#[test]
fn paste_rpc_disabled_without_key() {
    assert!(!paste_rpc_enabled(true, false));
}

#[test]
fn paste_rpc_enabled_with_key() {
    assert!(paste_rpc_enabled(true, true));
}

#[test]
fn paste_key_detection_accepts_explicit_key_file() {
    let dir = tempdir().expect("tempdir");
    let path = dir.path().join("paste-key");
    std::fs::write(
        &path,
        "hex:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
    )
    .expect("write key file");
    temp_env::with_vars(
        [
            ("LESAVKA_PASTE_KEY", None::<&str>),
            ("LESAVKA_PASTE_KEY_FILE", path.to_str()),
        ],
        || {
            assert!(paste_key_available_from_env());
        },
    );
}

#[test]
fn paste_modifier_recognizes_ctrl_alt_only() {
    assert!(is_paste_modifier(KeyCode::KEY_LEFTCTRL));
    assert!(is_paste_modifier(KeyCode::KEY_RIGHTCTRL));
    assert!(is_paste_modifier(KeyCode::KEY_LEFTALT));
    assert!(is_paste_modifier(KeyCode::KEY_RIGHTALT));
    assert!(!is_paste_modifier(KeyCode::KEY_V));
    assert!(!is_paste_modifier(KeyCode::KEY_LEFTSHIFT));
}

#[test]
fn keyboard_report_sorts_deduplicates_and_keeps_modifiers() {
    let report = build_keyboard_report([
        KeyCode::KEY_B,
        KeyCode::KEY_LEFTSHIFT,
        KeyCode::KEY_A,
        KeyCode::KEY_A,
        KeyCode::KEY_RIGHTCTRL,
    ]);

    assert_eq!(
        report[0],
        is_modifier(KeyCode::KEY_LEFTSHIFT).unwrap() | is_modifier(KeyCode::KEY_RIGHTCTRL).unwrap()
    );
    assert_eq!(report[2], keycode_to_usage(KeyCode::KEY_A).unwrap());
    assert_eq!(report[3], keycode_to_usage(KeyCode::KEY_B).unwrap());
    assert_eq!(&report[4..], &[0, 0, 0, 0]);
}

#[test]
fn modifier_staging_requires_modified_non_modifier_keydown() {
    let mut report = [0; 8];
    report[0] = is_modifier(KeyCode::KEY_LEFTCTRL).unwrap();
    report[2] = keycode_to_usage(KeyCode::KEY_V).unwrap();

    assert!(should_stage_modifier_report(KeyCode::KEY_V, 1, report));
    assert!(!should_stage_modifier_report(KeyCode::KEY_V, 0, report));
    assert!(!should_stage_modifier_report(
        KeyCode::KEY_LEFTCTRL,
        1,
        report
    ));

    let mut unmodified = report;
    unmodified[0] = 0;
    assert!(!should_stage_modifier_report(KeyCode::KEY_V, 1, unmodified));
}

#[test]
fn modifier_only_report_clears_non_modifier_slots() {
    assert_eq!(
        modifier_only_report(is_modifier(KeyCode::KEY_LEFTALT).unwrap()),
        [
            is_modifier(KeyCode::KEY_LEFTALT).unwrap(),
            0,
            0,
            0,
            0,
            0,
            0,
            0
        ]
    );
}

#[test]
fn live_modifier_delay_honors_zero_override() {
    temp_env::with_var("LESAVKA_LIVE_MODIFIER_DELAY_MS", Some("0"), || {
        assert_eq!(live_modifier_delay(), Duration::ZERO);
    });
}

#[test]
fn clipboard_reader_uses_successful_command_output() {
    temp_env::with_var(
        "LESAVKA_CLIPBOARD_CMD",
        Some("printf clipboard-data"),
        || {
            assert_eq!(read_clipboard_text().as_deref(), Some("clipboard-data"));
        },
    );
}

#[test]
fn debounce_gate_updates_timestamp_even_when_blocked() {
    let last = AtomicU64::new(0);

    assert!(debounce_gate(&last, 1_000, 250));
    assert_eq!(last.load(Ordering::Relaxed), 1_000);
    assert!(!debounce_gate(&last, 1_100, 250));
    assert_eq!(last.load(Ordering::Relaxed), 1_100);
    assert!(debounce_gate(&last, 1_400, 250));
}

#[test]
fn pressed_key_shadow_tracks_press_release_only() {
    let mut pressed = HashSet::new();

    update_pressed_keys(&mut pressed, KeyCode::KEY_A, 1);
    assert!(pressed.contains(&KeyCode::KEY_A));
    update_pressed_keys(&mut pressed, KeyCode::KEY_B, -1);
    assert!(!pressed.contains(&KeyCode::KEY_B));
    update_pressed_keys(&mut pressed, KeyCode::KEY_A, 0);
    assert!(!pressed.contains(&KeyCode::KEY_A));
}
