//! Keyboard input simulation using Windows SendInput API
//!
//! Injection is **scan-code based** (`KEYEVENTF_SCANCODE`) rather than virtual-key
//! based. Scan codes are layout-independent: the same physical key produces the same
//! scan code regardless of the remote keyboard layout, so the remote machine's active
//! layout (not the technician's) decides what character a key produces. The viewer
//! still carries the virtual-key code for logic that needs it, and we fall back to
//! deriving a scan code from the VK when the wire frame did not supply one.

use anyhow::Result;

#[cfg(windows)]
use windows::Win32::UI::Input::KeyboardAndMouse::{
    MapVirtualKeyW, SendInput, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYBD_EVENT_FLAGS,
    KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, KEYEVENTF_UNICODE,
    MAPVK_VK_TO_VSC_EX,
};

/// Keyboard input controller
pub struct KeyboardController {
    /// Tracks which modifier keys this controller currently holds DOWN on the remote.
    /// Used so a focus-loss / session-end re-sync can release any still-held modifier
    /// and avoid "stuck" Ctrl/Alt/Shift/Win on the remote desktop.
    modifiers: ModifierState,
}

/// Tracks the down/up state of each modifier the agent has injected.
#[derive(Default)]
struct ModifierState {
    ctrl: bool,
    alt: bool,
    shift: bool,
    meta: bool,
}

impl ModifierState {
    /// Record a modifier transition for `vk_code`. Returns `true` if `vk_code` is a
    /// modifier key (and the state was updated), `false` otherwise.
    fn record(&mut self, vk_code: u16, down: bool) -> bool {
        match vk_code {
            // VK_CONTROL / VK_LCONTROL / VK_RCONTROL
            0x11 | 0xA2 | 0xA3 => {
                self.ctrl = down;
                true
            }
            // VK_MENU / VK_LMENU / VK_RMENU (Alt)
            0x12 | 0xA4 | 0xA5 => {
                self.alt = down;
                true
            }
            // VK_SHIFT / VK_LSHIFT / VK_RSHIFT
            0x10 | 0xA0 | 0xA1 => {
                self.shift = down;
                true
            }
            // VK_LWIN / VK_RWIN
            0x5B | 0x5C => {
                self.meta = down;
                true
            }
            _ => false,
        }
    }

    /// Return the VK codes of every modifier currently held down, then clear the state.
    fn drain_held(&mut self) -> Vec<u16> {
        let mut held = Vec::new();
        if self.ctrl {
            held.push(0x11);
        }
        if self.alt {
            held.push(0x12);
        }
        if self.shift {
            held.push(0x10);
        }
        if self.meta {
            held.push(0x5B);
        }
        *self = ModifierState::default();
        held
    }
}

impl KeyboardController {
    /// Create a new keyboard controller
    pub fn new() -> Result<Self> {
        Ok(Self {
            modifiers: ModifierState::default(),
        })
    }

    /// Press a key down by virtual key code (scan code derived from the VK).
    #[cfg(windows)]
    pub fn key_down(&mut self, vk_code: u16) -> Result<()> {
        self.send_key(vk_code, 0, false, true)
    }

    /// Release a key by virtual key code (scan code derived from the VK).
    #[cfg(windows)]
    pub fn key_up(&mut self, vk_code: u16) -> Result<()> {
        self.send_key(vk_code, 0, false, false)
    }

    /// Inject a full-fidelity key event.
    ///
    /// `scan_code` is the hardware scan code captured by the viewer's low-level hook
    /// (0 ⇒ derive it from `vk_code`). `is_extended` is the viewer-captured extended-key
    /// flag (`LLKHF_EXTENDED`); when `false` the agent still derives the flag from the
    /// VK / scan code so older viewers that don't set it stay correct.
    #[cfg(windows)]
    pub fn key_event_full(
        &mut self,
        vk_code: u16,
        scan_code: u16,
        is_extended: bool,
        down: bool,
    ) -> Result<()> {
        self.send_key(vk_code, scan_code, is_extended, down)
    }

    /// Release every modifier this controller currently holds down on the remote.
    ///
    /// Called on viewer focus loss and at session end so a Ctrl/Alt/Shift/Win that was
    /// pressed but whose key-up never arrived (e.g. the technician alt-tabbed away) does
    /// not stay latched on the remote desktop.
    #[cfg(windows)]
    pub fn release_all_modifiers(&mut self) -> Result<()> {
        for vk in self.modifiers.drain_held() {
            // Emit the key-up directly; drain_held already cleared the tracked state.
            if let Err(e) = self.send_key(vk, 0, false, false) {
                tracing::warn!("Failed to release held modifier vk={:#x}: {}", vk, e);
            } else {
                tracing::debug!("Released stuck modifier vk={:#x} on focus loss", vk);
            }
        }
        Ok(())
    }

    /// Send a key event using scan-code injection.
    #[cfg(windows)]
    fn send_key(
        &mut self,
        vk_code: u16,
        scan_code: u16,
        is_extended: bool,
        down: bool,
    ) -> Result<()> {
        // Track modifier state so we can release stuck modifiers later.
        self.modifiers.record(vk_code, down);

        // Prefer the viewer-supplied scan code; fall back to deriving one from the VK.
        // MAPVK_VK_TO_VSC_EX yields a 0xE0-prefixed value for extended keys.
        let mapped = unsafe { MapVirtualKeyW(vk_code as u32, MAPVK_VK_TO_VSC_EX) as u16 };
        let effective_scan = if scan_code != 0 { scan_code } else { mapped };

        let mut flags = KEYBD_EVENT_FLAGS::default() | KEYEVENTF_SCANCODE;

        // Add the extended flag if the viewer flagged it, the VK is inherently
        // extended, or the mapped scan code carries the 0xE0 extended prefix.
        if is_extended || Self::is_extended_key(vk_code) || (mapped >> 8) == 0xE0 {
            flags |= KEYEVENTF_EXTENDEDKEY;
        }

        if !down {
            flags |= KEYEVENTF_KEYUP;
        }

        // For scan-code injection the low byte of the scan code is what Windows uses;
        // the 0xE0 prefix is conveyed via KEYEVENTF_EXTENDEDKEY, not the wScan value.
        let w_scan = (effective_scan & 0x00FF) as u16;

        let input = INPUT {
            r#type: INPUT_KEYBOARD,
            Anonymous: INPUT_0 {
                ki: KEYBDINPUT {
                    wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0),
                    wScan: w_scan,
                    dwFlags: flags,
                    time: 0,
                    dwExtraInfo: 0,
                },
            },
        };

        self.send_input(&[input])
    }

    /// Type a unicode character
    #[allow(dead_code)]
    #[cfg(windows)]
    pub fn type_char(&mut self, ch: char) -> Result<()> {
        let mut inputs = Vec::new();
        let mut buf = [0u16; 2];
        let encoded = ch.encode_utf16(&mut buf);

        // For characters that fit in a single u16
        for &code_unit in encoded.iter() {
            // Key down
            inputs.push(INPUT {
                r#type: INPUT_KEYBOARD,
                Anonymous: INPUT_0 {
                    ki: KEYBDINPUT {
                        wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0),
                        wScan: code_unit,
                        dwFlags: KEYEVENTF_UNICODE,
                        time: 0,
                        dwExtraInfo: 0,
                    },
                },
            });

            // Key up
            inputs.push(INPUT {
                r#type: INPUT_KEYBOARD,
                Anonymous: INPUT_0 {
                    ki: KEYBDINPUT {
                        wVk: windows::Win32::UI::Input::KeyboardAndMouse::VIRTUAL_KEY(0),
                        wScan: code_unit,
                        dwFlags: KEYEVENTF_UNICODE | KEYEVENTF_KEYUP,
                        time: 0,
                        dwExtraInfo: 0,
                    },
                },
            });
        }

        self.send_input(&inputs)
    }

    /// Type a string of text
    #[allow(dead_code)]
    #[cfg(windows)]
    pub fn type_string(&mut self, text: &str) -> Result<()> {
        for ch in text.chars() {
            self.type_char(ch)?;
        }
        Ok(())
    }

    /// Send Secure Attention Sequence (Ctrl+Alt+Delete)
    ///
    /// Ctrl+Alt+Del is the Secure Attention Sequence and **cannot** be injected via
    /// `SendInput` — Windows reserves it. It must be raised by `SendSAS`, which only
    /// works when the caller runs as SYSTEM (or has SeTcbPrivilege) AND the
    /// `SoftwareSASGeneration` Winlogon policy permits software-generated SAS. The
    /// managed installer is responsible for installing the SAS helper service (running
    /// as SYSTEM) and setting that policy. See `set_software_sas_policy` in
    /// `bin/sas_service.rs` and the `// TODO(installer)` note there.
    ///
    /// Tiers, in order:
    /// 1. The GuruConnect SAS helper service (SYSTEM) via named-pipe IPC — the supported path.
    /// 2. Direct `sas.dll!SendSAS` — only succeeds if THIS process is already SYSTEM with the policy.
    /// 3. Fallback key simulation — will NOT reach the secure desktop; logged as a clear failure.
    #[cfg(windows)]
    pub fn send_sas(&mut self) -> Result<()> {
        // Tier 1: Try the SAS service (named pipe IPC to SYSTEM service)
        match crate::sas_client::request_sas() {
            Ok(()) => {
                tracing::info!("SAS sent via GuruConnect SAS Service");
                return Ok(());
            }
            Err(e) => {
                tracing::warn!(
                    "SAS helper service unavailable ({}); trying direct sas.dll",
                    e
                );
            }
        }

        // Tier 2: Try using the sas.dll directly (requires SYSTEM + SoftwareSASGeneration)
        use windows::core::PCWSTR;
        use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};

        unsafe {
            let dll_name: Vec<u16> = "sas.dll\0".encode_utf16().collect();
            let lib = LoadLibraryW(PCWSTR(dll_name.as_ptr()));

            if let Ok(lib) = lib {
                let proc_name = b"SendSAS\0";
                if let Some(proc) = GetProcAddress(lib, windows::core::PCSTR(proc_name.as_ptr())) {
                    // SendSAS takes a BOOL parameter: FALSE for Ctrl+Alt+Del.
                    // It silently no-ops if the caller lacks privilege / the policy is
                    // unset, so we cannot detect success here — but it is the best
                    // effort short of the SYSTEM helper.
                    let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);
                    send_sas(0); // FALSE = Ctrl+Alt+Del
                    tracing::info!("SAS attempted via direct sas.dll call (effective only if SYSTEM + SoftwareSASGeneration policy set)");
                    return Ok(());
                }
            }
        }

        // Tier 3: SAS could not be delivered through any privileged path. A plain
        // SendInput of Ctrl+Alt+Del never reaches the secure desktop, so report a
        // clear, actionable error instead of pretending it worked.
        let msg = "Ctrl+Alt+Del could not be delivered: the GuruConnect SAS helper \
                   service is not running and sas.dll!SendSAS is unavailable. Ensure the \
                   SAS service is installed (runs as SYSTEM) and the SoftwareSASGeneration \
                   policy is enabled by the installer.";
        tracing::error!("{}", msg);
        anyhow::bail!("{}", msg)
    }

    /// Check if a virtual key code is an extended key
    #[cfg(windows)]
    fn is_extended_key(vk: u16) -> bool {
        vk_is_extended(vk)
    }

    /// Send input events
    #[cfg(windows)]
    fn send_input(&self, inputs: &[INPUT]) -> Result<()> {
        let sent = unsafe { SendInput(inputs, std::mem::size_of::<INPUT>() as i32) };

        if sent as usize != inputs.len() {
            anyhow::bail!("SendInput failed: sent {} of {} inputs", sent, inputs.len());
        }

        Ok(())
    }

    #[cfg(not(windows))]
    pub fn key_down(&mut self, _vk_code: u16) -> Result<()> {
        anyhow::bail!("Keyboard input only supported on Windows")
    }

    #[cfg(not(windows))]
    pub fn key_up(&mut self, _vk_code: u16) -> Result<()> {
        anyhow::bail!("Keyboard input only supported on Windows")
    }

    #[cfg(not(windows))]
    pub fn key_event_full(
        &mut self,
        _vk_code: u16,
        _scan_code: u16,
        _is_extended: bool,
        _down: bool,
    ) -> Result<()> {
        anyhow::bail!("Keyboard input only supported on Windows")
    }

    #[cfg(not(windows))]
    pub fn release_all_modifiers(&mut self) -> Result<()> {
        anyhow::bail!("Keyboard input only supported on Windows")
    }

    #[cfg(not(windows))]
    pub fn type_char(&mut self, _ch: char) -> Result<()> {
        anyhow::bail!("Keyboard input only supported on Windows")
    }

    #[cfg(not(windows))]
    pub fn send_sas(&mut self) -> Result<()> {
        anyhow::bail!("SAS only supported on Windows")
    }
}

/// Common Windows virtual key codes
#[allow(dead_code)]
pub mod vk {
    pub const BACK: u16 = 0x08;
    pub const TAB: u16 = 0x09;
    pub const RETURN: u16 = 0x0D;
    pub const SHIFT: u16 = 0x10;
    pub const CONTROL: u16 = 0x11;
    pub const MENU: u16 = 0x12; // Alt
    pub const PAUSE: u16 = 0x13;
    pub const CAPITAL: u16 = 0x14; // Caps Lock
    pub const ESCAPE: u16 = 0x1B;
    pub const SPACE: u16 = 0x20;
    pub const PRIOR: u16 = 0x21; // Page Up
    pub const NEXT: u16 = 0x22; // Page Down
    pub const END: u16 = 0x23;
    pub const HOME: u16 = 0x24;
    pub const LEFT: u16 = 0x25;
    pub const UP: u16 = 0x26;
    pub const RIGHT: u16 = 0x27;
    pub const DOWN: u16 = 0x28;
    pub const INSERT: u16 = 0x2D;
    pub const DELETE: u16 = 0x2E;

    // 0-9 keys
    pub const KEY_0: u16 = 0x30;
    pub const KEY_9: u16 = 0x39;

    // A-Z keys
    pub const KEY_A: u16 = 0x41;
    pub const KEY_Z: u16 = 0x5A;

    // Windows keys
    pub const LWIN: u16 = 0x5B;
    pub const RWIN: u16 = 0x5C;

    // Function keys
    pub const F1: u16 = 0x70;
    pub const F2: u16 = 0x71;
    pub const F3: u16 = 0x72;
    pub const F4: u16 = 0x73;
    pub const F5: u16 = 0x74;
    pub const F6: u16 = 0x75;
    pub const F7: u16 = 0x76;
    pub const F8: u16 = 0x77;
    pub const F9: u16 = 0x78;
    pub const F10: u16 = 0x79;
    pub const F11: u16 = 0x7A;
    pub const F12: u16 = 0x7B;

    // Modifier keys
    pub const LSHIFT: u16 = 0xA0;
    pub const RSHIFT: u16 = 0xA1;
    pub const LCONTROL: u16 = 0xA2;
    pub const RCONTROL: u16 = 0xA3;
    pub const LMENU: u16 = 0xA4; // Left Alt
    pub const RMENU: u16 = 0xA5; // Right Alt
}

/// Whether a Windows virtual-key code is an "extended" key.
///
/// Extended keys must be injected with `KEYEVENTF_EXTENDEDKEY`. This is the
/// platform-independent classifier so the determination can be unit-tested off-Windows;
/// the `#[cfg(windows)]` injection path delegates here. The viewer-captured
/// `LLKHF_EXTENDED` flag is authoritative when present; this is the fallback used when
/// the wire frame did not carry it (older viewers / VK-only synthesis).
pub fn vk_is_extended(vk: u16) -> bool {
    matches!(
        vk,
        0x21..=0x28 | // Page Up, Page Down, End, Home, Arrow keys
        0x2D | 0x2E | // Insert, Delete
        0x5B | 0x5C | // Left/Right Windows keys
        0x5D |        // Applications key
        0x6F |        // Numpad Divide
        0x90 |        // Num Lock
        0x91 |        // Scroll Lock
        0xA3 |        // Right Control
        0xA5 // Right Alt (AltGr)
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn extended_keys_are_flagged() {
        // Arrows / navigation block.
        for vk in [0x21u16, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28] {
            assert!(vk_is_extended(vk), "vk={:#x} should be extended", vk);
        }
        // Insert / Delete.
        assert!(vk_is_extended(0x2D));
        assert!(vk_is_extended(0x2E));
        // Win keys, Apps, NumLock, numpad Divide.
        assert!(vk_is_extended(0x5B));
        assert!(vk_is_extended(0x5C));
        assert!(vk_is_extended(0x5D));
        assert!(vk_is_extended(0x6F));
        assert!(vk_is_extended(0x90));
        // Right Ctrl / Right Alt.
        assert!(vk_is_extended(0xA3));
        assert!(vk_is_extended(0xA5));
    }

    #[test]
    fn non_extended_keys_are_not_flagged() {
        // Letters, digits, space, enter, left modifiers, numpad digits.
        for vk in [
            0x41u16, // A
            0x5A,    // Z
            0x30,    // 0
            0x20,    // Space
            0x0D,    // Enter
            0xA0,    // Left Shift
            0xA2,    // Left Control
            0xA4,    // Left Alt
            0x60,    // Numpad 0
            0x6A,    // Numpad Multiply (NOT extended; only Divide is)
        ] {
            assert!(!vk_is_extended(vk), "vk={:#x} should NOT be extended", vk);
        }
    }

    #[test]
    fn modifier_state_records_ctrl_alt_shift_win() {
        let mut m = ModifierState::default();
        // Each of the VK aliases maps to its modifier flag.
        assert!(m.record(0x11, true)); // VK_CONTROL
        assert!(m.ctrl);
        assert!(m.record(0xA4, true)); // VK_LMENU (Alt)
        assert!(m.alt);
        assert!(m.record(0xA0, true)); // VK_LSHIFT
        assert!(m.shift);
        assert!(m.record(0x5C, true)); // VK_RWIN
        assert!(m.meta);
    }

    #[test]
    fn modifier_state_ignores_non_modifiers() {
        let mut m = ModifierState::default();
        assert!(!m.record(0x41, true)); // 'A' is not a modifier
        assert!(!m.ctrl && !m.alt && !m.shift && !m.meta);
    }

    #[test]
    fn modifier_state_tracks_down_then_up() {
        let mut m = ModifierState::default();
        m.record(0x11, true); // Ctrl down
        assert!(m.ctrl);
        m.record(0x11, false); // Ctrl up
        assert!(!m.ctrl);
    }

    #[test]
    fn drain_held_returns_and_clears_held_modifiers() {
        let mut m = ModifierState::default();
        m.record(0xA2, true); // Left Ctrl -> ctrl
        m.record(0x12, true); // Alt
                              // Shift and Win were never pressed.
        let mut held = m.drain_held();
        held.sort_unstable();
        // Canonical VKs returned: Ctrl(0x11), Alt(0x12).
        assert_eq!(held, vec![0x11u16, 0x12]);
        // State is cleared after draining.
        assert!(!m.ctrl && !m.alt && !m.shift && !m.meta);
        // A second drain yields nothing.
        assert!(m.drain_held().is_empty());
    }

    #[test]
    fn drain_held_empty_when_nothing_pressed() {
        let mut m = ModifierState::default();
        assert!(m.drain_held().is_empty());
    }
}
