//! GuruConnect SAS Service
//!
//! Windows Service running as SYSTEM to handle Ctrl+Alt+Del (Secure Attention Sequence).
//! The agent communicates with this service via named pipe IPC.

use std::ffi::OsString;
use std::sync::mpsc;
use std::time::Duration;

use anyhow::{Context, Result};
use windows::core::{s, w};
use windows::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryW};
use windows_service::{
    define_windows_service,
    service::{
        ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, ServiceExitCode,
        ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, ServiceType,
    },
    service_control_handler::{self, ServiceControlHandlerResult},
    service_dispatcher,
    service_manager::{ServiceManager, ServiceManagerAccess},
};

// Service configuration
const SERVICE_NAME: &str = "GuruConnectSAS";
const SERVICE_DISPLAY_NAME: &str = "GuruConnect SAS Service";
const SERVICE_DESCRIPTION: &str =
    "Handles Secure Attention Sequence (Ctrl+Alt+Del) for GuruConnect remote sessions";
const PIPE_NAME: &str = r"\\.\pipe\guruconnect-sas";
const INSTALL_DIR: &str = r"C:\Program Files\GuruConnect";

// Windows named pipe constants
const PIPE_ACCESS_DUPLEX: u32 = 0x00000003;
const PIPE_TYPE_MESSAGE: u32 = 0x00000004;
const PIPE_READMODE_MESSAGE: u32 = 0x00000002;
const PIPE_WAIT: u32 = 0x00000000;
const PIPE_UNLIMITED_INSTANCES: u32 = 255;
const INVALID_HANDLE_VALUE: isize = -1;
/// SDDL revision passed to `ConvertStringSecurityDescriptorToSecurityDescriptorW`
/// (`SDDL_REVISION_1`).
const SDDL_REVISION_1: u32 = 1;

/// Restrictive DACL for the SAS named pipe, in SDDL form.
///
/// `D:` introduces the DACL; `(A;;GA;;;AU)` is an ACE granting GENERIC_ALL (`GA`) to
/// Authenticated Users (`AU`). Anonymous / null-session callers are NOT authenticated and
/// are therefore denied — closing the original NULL-DACL hole where any local process
/// (Everyone) could connect and make this SYSTEM service raise the secure-attention
/// screen. The agent runs in the interactive logon session and IS an authenticated user,
/// so it can still connect and request a SAS.
const PIPE_SDDL: &str = "D:(A;;GA;;;AU)";

// FFI declarations for named pipe operations
#[link(name = "kernel32")]
extern "system" {
    fn CreateNamedPipeW(
        lpName: *const u16,
        dwOpenMode: u32,
        dwPipeMode: u32,
        nMaxInstances: u32,
        nOutBufferSize: u32,
        nInBufferSize: u32,
        nDefaultTimeOut: u32,
        lpSecurityAttributes: *mut SECURITY_ATTRIBUTES,
    ) -> isize;

    fn ConnectNamedPipe(hNamedPipe: isize, lpOverlapped: *mut std::ffi::c_void) -> i32;
    fn DisconnectNamedPipe(hNamedPipe: isize) -> i32;
    fn CloseHandle(hObject: isize) -> i32;
    fn ReadFile(
        hFile: isize,
        lpBuffer: *mut u8,
        nNumberOfBytesToRead: u32,
        lpNumberOfBytesRead: *mut u32,
        lpOverlapped: *mut std::ffi::c_void,
    ) -> i32;
    fn WriteFile(
        hFile: isize,
        lpBuffer: *const u8,
        nNumberOfBytesToWrite: u32,
        lpNumberOfBytesWritten: *mut u32,
        lpOverlapped: *mut std::ffi::c_void,
    ) -> i32;
    fn FlushFileBuffers(hFile: isize) -> i32;
    fn LocalFree(hMem: *mut std::ffi::c_void) -> *mut std::ffi::c_void;
}

#[link(name = "advapi32")]
extern "system" {
    /// Build a self-relative security descriptor from an SDDL string. The descriptor is
    /// allocated with `LocalAlloc` and must be released with `LocalFree`.
    fn ConvertStringSecurityDescriptorToSecurityDescriptorW(
        StringSecurityDescriptor: *const u16,
        StringSDRevision: u32,
        SecurityDescriptor: *mut *mut std::ffi::c_void,
        SecurityDescriptorSize: *mut u32,
    ) -> i32;
}

// Field names mirror the Win32 SECURITY_ATTRIBUTES ABI struct.
#[allow(non_snake_case)]
#[repr(C)]
struct SECURITY_ATTRIBUTES {
    nLength: u32,
    lpSecurityDescriptor: *mut u8,
    bInheritHandle: i32,
}

fn main() {
    // Set up logging
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .with_target(false)
        .init();

    match std::env::args().nth(1).as_deref() {
        Some("install") => {
            if let Err(e) = install_service() {
                eprintln!("Failed to install service: {}", e);
                std::process::exit(1);
            }
        }
        Some("uninstall") => {
            if let Err(e) = uninstall_service() {
                eprintln!("Failed to uninstall service: {}", e);
                std::process::exit(1);
            }
        }
        Some("start") => {
            if let Err(e) = start_service() {
                eprintln!("Failed to start service: {}", e);
                std::process::exit(1);
            }
        }
        Some("stop") => {
            if let Err(e) = stop_service() {
                eprintln!("Failed to stop service: {}", e);
                std::process::exit(1);
            }
        }
        Some("status") => {
            if let Err(e) = query_status() {
                eprintln!("Failed to query status: {}", e);
                std::process::exit(1);
            }
        }
        Some("service") => {
            // Called by SCM when service starts
            if let Err(e) = run_as_service() {
                eprintln!("Service error: {}", e);
                std::process::exit(1);
            }
        }
        Some("test") => {
            // Test mode: run pipe server directly (for debugging)
            println!("Running in test mode (not as service)...");
            if let Err(e) = run_pipe_server() {
                eprintln!("Pipe server error: {}", e);
                std::process::exit(1);
            }
        }
        _ => {
            print_usage();
        }
    }
}

fn print_usage() {
    println!("GuruConnect SAS Service");
    println!();
    println!("Usage: guruconnect-sas-service <command>");
    println!();
    println!("Commands:");
    println!("  install    Install the service");
    println!("  uninstall  Remove the service");
    println!("  start      Start the service");
    println!("  stop       Stop the service");
    println!("  status     Query service status");
    println!("  test       Run in test mode (not as service)");
}

// Generate the Windows service boilerplate
define_windows_service!(ffi_service_main, service_main);

/// Entry point called by the Windows Service Control Manager
fn run_as_service() -> Result<()> {
    service_dispatcher::start(SERVICE_NAME, ffi_service_main)
        .context("Failed to start service dispatcher")?;
    Ok(())
}

/// Main service function called by the SCM
fn service_main(_arguments: Vec<OsString>) {
    if let Err(e) = run_service() {
        tracing::error!("Service error: {}", e);
    }
}

/// The actual service implementation
fn run_service() -> Result<()> {
    // Create a channel to receive stop events
    let (shutdown_tx, shutdown_rx) = mpsc::channel();

    // Create the service control handler
    let event_handler = move |control_event| -> ServiceControlHandlerResult {
        match control_event {
            ServiceControl::Stop | ServiceControl::Shutdown => {
                tracing::info!("Received stop/shutdown command");
                let _ = shutdown_tx.send(());
                ServiceControlHandlerResult::NoError
            }
            ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
            _ => ServiceControlHandlerResult::NotImplemented,
        }
    };

    // Register the service control handler
    let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)
        .context("Failed to register service control handler")?;

    // Report that we're starting
    status_handle
        .set_service_status(ServiceStatus {
            service_type: ServiceType::OWN_PROCESS,
            current_state: ServiceState::StartPending,
            controls_accepted: ServiceControlAccept::empty(),
            exit_code: ServiceExitCode::Win32(0),
            checkpoint: 0,
            wait_hint: Duration::from_secs(5),
            process_id: None,
        })
        .ok();

    // Report that we're running
    status_handle
        .set_service_status(ServiceStatus {
            service_type: ServiceType::OWN_PROCESS,
            current_state: ServiceState::Running,
            controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,
            exit_code: ServiceExitCode::Win32(0),
            checkpoint: 0,
            wait_hint: Duration::default(),
            process_id: None,
        })
        .ok();

    tracing::info!("GuruConnect SAS Service started");

    // Run the pipe server in a separate thread
    let pipe_handle = std::thread::spawn(|| {
        if let Err(e) = run_pipe_server() {
            tracing::error!("Pipe server error: {}", e);
        }
    });

    // Wait for shutdown signal
    let _ = shutdown_rx.recv();

    tracing::info!("Shutting down...");

    // Report that we're stopping
    status_handle
        .set_service_status(ServiceStatus {
            service_type: ServiceType::OWN_PROCESS,
            current_state: ServiceState::StopPending,
            controls_accepted: ServiceControlAccept::empty(),
            exit_code: ServiceExitCode::Win32(0),
            checkpoint: 0,
            wait_hint: Duration::from_secs(3),
            process_id: None,
        })
        .ok();

    // The pipe thread will exit when the service stops
    drop(pipe_handle);

    // Report stopped
    status_handle
        .set_service_status(ServiceStatus {
            service_type: ServiceType::OWN_PROCESS,
            current_state: ServiceState::Stopped,
            controls_accepted: ServiceControlAccept::empty(),
            exit_code: ServiceExitCode::Win32(0),
            checkpoint: 0,
            wait_hint: Duration::default(),
            process_id: None,
        })
        .ok();

    Ok(())
}

/// Run the named pipe server
fn run_pipe_server() -> Result<()> {
    tracing::info!("Starting pipe server on {}", PIPE_NAME);

    loop {
        // Build a restrictive security descriptor from SDDL: grant access only to
        // Authenticated Users (excludes anonymous / null-session callers). See PIPE_SDDL.
        let sddl: Vec<u16> = PIPE_SDDL.encode_utf16().chain(std::iter::once(0)).collect();
        let mut sd_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
        let converted = unsafe {
            ConvertStringSecurityDescriptorToSecurityDescriptorW(
                sddl.as_ptr(),
                SDDL_REVISION_1,
                &mut sd_ptr,
                std::ptr::null_mut(),
            )
        };
        if converted == 0 || sd_ptr.is_null() {
            let err = std::io::Error::last_os_error();
            tracing::error!(
                "Failed to build pipe security descriptor from SDDL: {}",
                err
            );
            std::thread::sleep(Duration::from_secs(1));
            continue;
        }

        let mut sa = SECURITY_ATTRIBUTES {
            nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
            lpSecurityDescriptor: sd_ptr as *mut u8,
            bInheritHandle: 0,
        };

        // Create the pipe name as wide string
        let pipe_name: Vec<u16> = PIPE_NAME.encode_utf16().chain(std::iter::once(0)).collect();

        // Create the named pipe
        let pipe = unsafe {
            CreateNamedPipeW(
                pipe_name.as_ptr(),
                PIPE_ACCESS_DUPLEX,
                PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                PIPE_UNLIMITED_INSTANCES,
                512,
                512,
                0,
                &mut sa,
            )
        };

        // CreateNamedPipeW copies the descriptor into the kernel object, so the SDDL-built
        // copy can be freed now regardless of success.
        unsafe {
            LocalFree(sd_ptr);
        }

        if pipe == INVALID_HANDLE_VALUE {
            tracing::error!("Failed to create named pipe");
            std::thread::sleep(Duration::from_secs(1));
            continue;
        }

        tracing::info!("Waiting for client connection...");

        // Wait for a client to connect
        let connected = unsafe { ConnectNamedPipe(pipe, std::ptr::null_mut()) };
        if connected == 0 {
            let err = std::io::Error::last_os_error();
            // ERROR_PIPE_CONNECTED (535) means client connected between Create and Connect
            if err.raw_os_error() != Some(535) {
                tracing::warn!("ConnectNamedPipe error: {}", err);
            }
        }

        tracing::info!("Client connected");

        // Read command from pipe
        let mut buffer = [0u8; 512];
        let mut bytes_read = 0u32;

        let read_result = unsafe {
            ReadFile(
                pipe,
                buffer.as_mut_ptr(),
                buffer.len() as u32,
                &mut bytes_read,
                std::ptr::null_mut(),
            )
        };

        if read_result != 0 && bytes_read > 0 {
            let command = String::from_utf8_lossy(&buffer[..bytes_read as usize]);
            let command = command.trim();

            tracing::info!("Received command: {}", command);

            let response = match command {
                "sas" => match send_sas() {
                    Ok(()) => {
                        tracing::info!("SendSAS executed successfully");
                        "ok\n"
                    }
                    Err(e) => {
                        tracing::error!("SendSAS failed: {}", e);
                        "error\n"
                    }
                },
                "ping" => {
                    tracing::info!("Ping received");
                    "pong\n"
                }
                _ => {
                    tracing::warn!("Unknown command: {}", command);
                    "unknown\n"
                }
            };

            // Write response
            let mut bytes_written = 0u32;
            unsafe {
                WriteFile(
                    pipe,
                    response.as_ptr(),
                    response.len() as u32,
                    &mut bytes_written,
                    std::ptr::null_mut(),
                );
                FlushFileBuffers(pipe);
            }
        }

        // Disconnect and close the pipe
        unsafe {
            DisconnectNamedPipe(pipe);
            CloseHandle(pipe);
        }
    }
}

/// Enable the `SoftwareSASGeneration` Winlogon policy so `SendSAS` is permitted.
///
/// Without this policy, `sas.dll!SendSAS` is a silent no-op even when called from
/// SYSTEM. The value lives at
/// `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\SoftwareSASGeneration`
/// and is a DWORD bitmask:
///   0 = none, 1 = services, 2 = ease-of-access apps, 3 = both.
///
/// We set `1` (services) because the GuruConnect SAS helper runs as a SYSTEM service.
/// This is invoked from the SAS service installer; the broader agent installer should
/// ensure this runs (see `// TODO(installer)` below).
fn set_software_sas_policy() -> Result<()> {
    use windows::core::PCWSTR;
    use windows::Win32::System::Registry::{
        RegCloseKey, RegCreateKeyExW, RegSetValueExW, HKEY, HKEY_LOCAL_MACHINE, KEY_SET_VALUE,
        REG_DWORD, REG_OPTION_NON_VOLATILE,
    };

    let subkey: Vec<u16> = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
        .encode_utf16()
        .chain(std::iter::once(0))
        .collect();
    let value_name: Vec<u16> = "SoftwareSASGeneration"
        .encode_utf16()
        .chain(std::iter::once(0))
        .collect();
    // DWORD 1 = allow services to generate a software SAS.
    let data: u32 = 1;

    unsafe {
        let mut hkey = HKEY::default();
        let status = RegCreateKeyExW(
            HKEY_LOCAL_MACHINE,
            PCWSTR(subkey.as_ptr()),
            0,
            PCWSTR::null(),
            REG_OPTION_NON_VOLATILE,
            KEY_SET_VALUE,
            None,
            &mut hkey,
            None,
        );
        if status.is_err() {
            anyhow::bail!("RegCreateKeyExW(Policies\\System) failed: {:?}", status);
        }

        let set = RegSetValueExW(
            hkey,
            PCWSTR(value_name.as_ptr()),
            0,
            REG_DWORD,
            Some(&data.to_ne_bytes()),
        );
        let _ = RegCloseKey(hkey);

        if set.is_err() {
            anyhow::bail!("RegSetValueExW(SoftwareSASGeneration) failed: {:?}", set);
        }
    }

    Ok(())
}

/// Call SendSAS via sas.dll
fn send_sas() -> Result<()> {
    unsafe {
        let lib = LoadLibraryW(w!("sas.dll")).context("Failed to load sas.dll")?;

        let proc = GetProcAddress(lib, s!("SendSAS"));
        if proc.is_none() {
            anyhow::bail!("SendSAS function not found in sas.dll");
        }

        // SendSAS takes a BOOL parameter: FALSE (0) = Ctrl+Alt+Del
        type SendSASFn = unsafe extern "system" fn(i32);
        let send_sas_fn: SendSASFn = std::mem::transmute(proc.unwrap());

        tracing::info!("Calling SendSAS(0)...");
        send_sas_fn(0);

        Ok(())
    }
}

/// Install the service
fn install_service() -> Result<()> {
    println!("Installing GuruConnect SAS Service...");

    // Get current executable path
    let current_exe = std::env::current_exe().context("Failed to get current executable")?;

    let binary_dest =
        std::path::PathBuf::from(format!(r"{}\\guruconnect-sas-service.exe", INSTALL_DIR));

    // Create install directory
    std::fs::create_dir_all(INSTALL_DIR).context("Failed to create install directory")?;

    // Copy binary
    println!("Copying binary to: {:?}", binary_dest);
    std::fs::copy(&current_exe, &binary_dest).context("Failed to copy binary")?;

    // Open service manager
    let manager = ServiceManager::local_computer(
        None::<&str>,
        ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE,
    )
    .context("Failed to connect to Service Control Manager. Run as Administrator.")?;

    // Check if service exists and remove it
    if let Ok(service) = manager.open_service(
        SERVICE_NAME,
        ServiceAccess::QUERY_STATUS | ServiceAccess::DELETE | ServiceAccess::STOP,
    ) {
        println!("Removing existing service...");

        if let Ok(status) = service.query_status() {
            if status.current_state != ServiceState::Stopped {
                let _ = service.stop();
                std::thread::sleep(Duration::from_secs(2));
            }
        }

        service
            .delete()
            .context("Failed to delete existing service")?;
        drop(service);
        std::thread::sleep(Duration::from_secs(2));
    }

    // Create the service
    let service_info = ServiceInfo {
        name: OsString::from(SERVICE_NAME),
        display_name: OsString::from(SERVICE_DISPLAY_NAME),
        service_type: ServiceType::OWN_PROCESS,
        start_type: ServiceStartType::AutoStart,
        error_control: ServiceErrorControl::Normal,
        executable_path: binary_dest.clone(),
        launch_arguments: vec![OsString::from("service")],
        dependencies: vec![],
        account_name: None, // LocalSystem
        account_password: None,
    };

    let service = manager
        .create_service(
            &service_info,
            ServiceAccess::CHANGE_CONFIG | ServiceAccess::START,
        )
        .context("Failed to create service")?;

    // Set description
    service
        .set_description(SERVICE_DESCRIPTION)
        .context("Failed to set service description")?;

    // Configure recovery
    let _ = std::process::Command::new("sc")
        .args([
            "failure",
            SERVICE_NAME,
            "reset=86400",
            "actions=restart/5000/restart/5000/restart/5000",
        ])
        .output();

    // Enable the SoftwareSASGeneration policy so SendSAS actually works from the
    // SYSTEM service. TODO(installer): the top-level managed agent installer should
    // also ensure this policy is set (and that this SAS service is installed) as part
    // of unattended deployment, rather than relying on a manual SAS-service install.
    match set_software_sas_policy() {
        Ok(()) => println!("Enabled SoftwareSASGeneration policy (services)"),
        Err(e) => println!(
            "Warning: failed to set SoftwareSASGeneration policy: {}. \
             Ctrl+Alt+Del may not reach the secure desktop until this is set.",
            e
        ),
    }

    println!("\n** GuruConnect SAS Service installed successfully!");
    println!("\nBinary: {:?}", binary_dest);
    println!("\nStarting service...");

    // Start the service
    start_service()?;

    Ok(())
}

/// Uninstall the service
fn uninstall_service() -> Result<()> {
    println!("Uninstalling GuruConnect SAS Service...");

    let binary_path =
        std::path::PathBuf::from(format!(r"{}\\guruconnect-sas-service.exe", INSTALL_DIR));

    let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)
        .context("Failed to connect to Service Control Manager. Run as Administrator.")?;

    match manager.open_service(
        SERVICE_NAME,
        ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE,
    ) {
        Ok(service) => {
            if let Ok(status) = service.query_status() {
                if status.current_state != ServiceState::Stopped {
                    println!("Stopping service...");
                    let _ = service.stop();
                    std::thread::sleep(Duration::from_secs(3));
                }
            }

            println!("Deleting service...");
            service.delete().context("Failed to delete service")?;
        }
        Err(_) => {
            println!("Service was not installed");
        }
    }

    // Remove binary
    if binary_path.exists() {
        std::thread::sleep(Duration::from_secs(1));
        if let Err(e) = std::fs::remove_file(&binary_path) {
            println!("Warning: Failed to remove binary: {}", e);
        }
    }

    println!("\n** GuruConnect SAS Service uninstalled successfully!");

    Ok(())
}

/// Start the service
fn start_service() -> Result<()> {
    let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)
        .context("Failed to connect to Service Control Manager")?;

    let service = manager
        .open_service(
            SERVICE_NAME,
            ServiceAccess::START | ServiceAccess::QUERY_STATUS,
        )
        .context("Failed to open service. Is it installed?")?;

    service
        .start::<String>(&[])
        .context("Failed to start service")?;

    std::thread::sleep(Duration::from_secs(1));

    let status = service.query_status()?;
    match status.current_state {
        ServiceState::Running => println!("** Service started successfully"),
        ServiceState::StartPending => println!("** Service is starting..."),
        other => println!("Service state: {:?}", other),
    }

    Ok(())
}

/// Stop the service
fn stop_service() -> Result<()> {
    let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)
        .context("Failed to connect to Service Control Manager")?;

    let service = manager
        .open_service(
            SERVICE_NAME,
            ServiceAccess::STOP | ServiceAccess::QUERY_STATUS,
        )
        .context("Failed to open service")?;

    service.stop().context("Failed to stop service")?;

    std::thread::sleep(Duration::from_secs(1));

    let status = service.query_status()?;
    match status.current_state {
        ServiceState::Stopped => println!("** Service stopped"),
        ServiceState::StopPending => println!("** Service is stopping..."),
        other => println!("Service state: {:?}", other),
    }

    Ok(())
}

/// Query service status
fn query_status() -> Result<()> {
    let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)
        .context("Failed to connect to Service Control Manager")?;

    match manager.open_service(SERVICE_NAME, ServiceAccess::QUERY_STATUS) {
        Ok(service) => {
            let status = service.query_status()?;
            println!("GuruConnect SAS Service");
            println!("=======================");
            println!("Name:   {}", SERVICE_NAME);
            println!("State:  {:?}", status.current_state);
            println!("Binary: {}\\guruconnect-sas-service.exe", INSTALL_DIR);
            println!("Pipe:   {}", PIPE_NAME);
        }
        Err(_) => {
            println!("GuruConnect SAS Service");
            println!("=======================");
            println!("Status: NOT INSTALLED");
            println!("\nTo install: guruconnect-sas-service install");
        }
    }

    Ok(())
}
