//! VM stop command implementation.

use std::path::Path;
use color_eyre::{Result, eyre};
use clap::ArgMatches;

use super::session::{discover_vm_session, is_process_alive, cleanup_vm_session_files, vm_session_file_path};

/// Entry point for `epkg vm stop` command.
pub fn cmd_vm_stop(_args: &ArgMatches) -> Result<()> {
    let cfg = crate::models::config();
    let env_name = cfg.common.env_name.clone();
    let _env_root = if cfg.common.env_root.is_empty() {
        crate::dirs::get_env_root(env_name.clone())?
    } else {
        std::path::PathBuf::from(&cfg.common.env_root)
    };

    // Check if VM is running
    let session = discover_vm_session(&env_name)?
        .ok_or_else(|| eyre::eyre!("No VM running for {}", env_name))?;

    log::info!("Stopping VM for {} (PID {}, backend={})", env_name, session.daemon_pid, session.backend);

    // Send shutdown signal to guest vm_daemon via vsock or Unix socket
    // The guest will close connections and VM will shut down
    if let Err(e) = send_shutdown_to_guest(&session.socket_path, &session.backend) {
        log::warn!("Failed to send shutdown to guest: {}", e);
    }

    // Send SIGTERM to daemon process to trigger graceful shutdown
    // For libkrun, this calls krun_signal_shutdown() which speeds up VM termination
    // For QEMU, the guest is already powering off from the shutdown command
    #[cfg(unix)]
    {
        use nix::sys::signal::{kill, Signal};
        let pid = nix::unistd::Pid::from_raw(session.daemon_pid as i32);
        let _ = kill(pid, Signal::SIGTERM);
    }

    #[cfg(windows)]
    {
        use windows::Win32::Foundation::CloseHandle;
        use windows::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};

        unsafe {
            let handle = OpenProcess(PROCESS_TERMINATE, false, session.daemon_pid);
            if let Ok(h) = handle {
                let _ = TerminateProcess(h, 1);
                let _ = CloseHandle(h);
            }
        }
    }

    // Wait for daemon process to exit (up to 500ms)
    // We need to ensure the daemon exits before cleaning up session files
    // to avoid leaving orphan processes or race conditions with new VM starts
    let start = std::time::Instant::now();
    while start.elapsed() < std::time::Duration::from_millis(500) {
        if !is_process_alive(session.daemon_pid) {
            break;
        }
        std::thread::sleep(std::time::Duration::from_millis(10));
    }

    // Force kill if still alive
    #[cfg(unix)]
    if is_process_alive(session.daemon_pid) {
        use nix::sys::signal::{kill, Signal};
        let pid = nix::unistd::Pid::from_raw(session.daemon_pid as i32);
        let _ = kill(pid, Signal::SIGKILL);
        // Brief wait for SIGKILL to take effect
        std::thread::sleep(std::time::Duration::from_millis(50));
    }

    #[cfg(windows)]
    if is_process_alive(session.daemon_pid) {
        use windows::Win32::Foundation::CloseHandle;
        use windows::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};

        unsafe {
            let handle = OpenProcess(PROCESS_TERMINATE, false, session.daemon_pid);
            if let Ok(h) = handle {
                let _ = TerminateProcess(h, 1);
                let _ = CloseHandle(h);
            }
        }
        // Brief wait for termination to take effect
        std::thread::sleep(std::time::Duration::from_millis(50));
    }

    // Clean up session files only after daemon has exited
    let session_file = vm_session_file_path(&env_name);
    cleanup_vm_session_files(&session_file, &session.socket_path);

    println!("VM stopped for {}", env_name);
    Ok(())
}

/// Send shutdown signal to guest vm_daemon.
/// For QEMU: uses vsock (socket_path format: "vsock:3") - Linux only
/// For libkrun: uses Unix socket
fn send_shutdown_to_guest(socket_path: &Path, _backend: &str) -> Result<()> {
    // vsock path (Linux only)
    #[cfg(target_os = "linux")]
    {
        let socket_str = socket_path.to_string_lossy();
        if socket_str.starts_with("vsock:") {
            send_shutdown_via_vsock(&socket_str)?;
            return Ok(());
        }
    }

    // libkrun uses Unix socket (Linux only - client module is Linux-specific)
    #[cfg(all(target_os = "linux", feature = "libkrun"))]
    {
        use std::io::Write;

        let mut stream = std::os::unix::net::UnixStream::connect(socket_path)?;

        // Use proper command request format that guest daemon expects
        // vm_keep_timeout_secs=None triggers immediate shutdown
        let request = super::client::build_command_request(
            &[crate::run::VM_SESSION_DONE_CMD.to_string()],
            crate::models::IoMode::Stream,
            None,  // vm_keep_timeout - triggers shutdown
            None,  // user
        );
        let request_json = serde_json::Value::Object(request);
        writeln!(stream, "{}", request_json)?;

        log::debug!("Sent {} to guest vm_daemon via Unix socket", crate::run::VM_SESSION_DONE_CMD);
        Ok(())
    }

    #[cfg(all(windows, feature = "libkrun"))]
    {
        use std::io::Write;

        let mut stream = crate::libkrun::bridge::connect_vsock_bridge(socket_path, crate::libkrun::bridge::VSOCK_BRIDGE_MAX_RETRIES)?;

        // Build simple JSON request inline (client module is Linux-only)
        let request = serde_json::json!({
            "command": [crate::run::VM_SESSION_DONE_CMD],
            "cwd": null,
            "env": {},
            "stdin": "",
            "pty": false,
        });
        writeln!(stream, "{}", request)?;

        log::debug!("Sent {} to guest vm_daemon via named pipe", crate::run::VM_SESSION_DONE_CMD);
        Ok(())
    }

    // macOS libkrun: use Unix socket with inline JSON request
    #[cfg(all(target_os = "macos", feature = "libkrun"))]
    {
        use std::io::Write;

        let mut stream = std::os::unix::net::UnixStream::connect(socket_path)?;

        // Build simple JSON request inline (client module is Linux-only)
        // Note: vm_keep_timeout_secs is omitted -> guest daemon will shutdown
        let request = serde_json::json!({
            "command": [crate::run::VM_SESSION_DONE_CMD],
            "cwd": null,
            "env": {},
            "stdin": "",
            "pty": false,
        });
        writeln!(stream, "{}", request)?;

        log::debug!("Sent {} to guest vm_daemon via Unix socket", crate::run::VM_SESSION_DONE_CMD);
        Ok(())
    }

    #[cfg(not(feature = "libkrun"))]
    {
        let socket_str = socket_path.to_string_lossy();
        // Non-libkrun backend but not vsock - should not happen
        log::warn!("Unknown socket type for shutdown: {}", socket_str);
        Ok(())
    }
}

/// Send shutdown signal to guest via vsock.
/// Format: "vsock:3" -> connects to CID 3, port 10000 (vm_daemon control port)
#[cfg(target_os = "linux")]
fn send_shutdown_via_vsock(socket_str: &str) -> Result<()> {
    use nix::sys::socket::{socket, connect, AddressFamily, SockType, SockFlag, VsockAddr};
    use std::os::fd::{IntoRawFd, FromRawFd};

    // Parse "vsock:3" -> CID 3
    let cid: u32 = socket_str
        .strip_prefix("vsock:")
        .and_then(|s| s.parse().ok())
        .ok_or_else(|| eyre::eyre!("Invalid vsock address: {}", socket_str))?;

    // vm_daemon control port is 10000
    const VM_DAEMON_PORT: u32 = 10000;

    log::debug!("Sending shutdown via vsock to CID {} port {}", cid, VM_DAEMON_PORT);

    let fd = socket(
        AddressFamily::Vsock,
        SockType::Stream,
        SockFlag::SOCK_CLOEXEC,
        None,
    ).map_err(|e| eyre::eyre!("Failed to create vsock socket: {}", e))?;

    let raw_fd = fd.into_raw_fd();
    // Connect from host to guest CID (guest-cid=3 in QEMU config)
    let addr = VsockAddr::new(cid, VM_DAEMON_PORT);

    // vsock connect should be instant if VM is running
    connect(raw_fd, &addr)
        .map_err(|e| eyre::eyre!("Failed to connect to vsock CID {} port {}: {}", cid, VM_DAEMON_PORT, e))?;

    // Send session_done command using proper JSON request format
    // The guest daemon expects: {"command": ["__epkg_vm_session_done__"], ...}
    use std::io::Write;
    let mut stream = unsafe { std::os::unix::net::UnixStream::from_raw_fd(raw_fd) };

    // vm_keep_timeout_secs=None triggers immediate shutdown
    let request = super::client::build_command_request(
        &[crate::run::VM_SESSION_DONE_CMD.to_string()],
        crate::models::IoMode::Stream,
        None,  // vm_keep_timeout - triggers shutdown
        None,  // user
    );
    let request_json = serde_json::Value::Object(request);
    writeln!(stream, "{}", request_json)?;

    log::debug!("Sent {} to guest vm_daemon via vsock", crate::run::VM_SESSION_DONE_CMD);

    // Close the stream (this will also close the fd)
    let _ = stream.shutdown(std::net::Shutdown::Both);

    Ok(())
}