#![cfg(windows)]

use crate::traits::{ProcessGroupManager, ProcessLifecycle};
use std::io;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use tracing::{debug, info, warn};
use windows_sys::Win32::Foundation::{CloseHandle, FALSE, HANDLE};
use windows_sys::Win32::System::JobObjects::{
    AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation,
    SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
};
use windows_sys::Win32::System::Threading::{
    OpenProcess, TerminateProcess, PROCESS_ALL_ACCESS,
};
use windows_sys::Win32::System::Console::{
    GenerateConsoleCtrlEvent, CTRL_BREAK_EVENT,
};

/// Windows process group manager using Job Objects.
///
/// Creates a Job Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` — when
/// this manager is dropped (or the process crashes), the OS automatically
/// kills all assigned child processes including grandchildren.
pub struct WindowsProcessGroupManager {
    /// Wrapped in a Mutex so we can mutate through a shared reference after init.
    job_handle: Mutex<HANDLE>,
    initialized: AtomicBool,
}

// Safety: HANDLE (*mut c_void) is not Send/Sync by default, but we only
// mutate it during init() (guarded by AtomicBool + Mutex) and read it
// (via copy) in create_lifecycle() and drop. No concurrent mutation occurs.
unsafe impl Send for WindowsProcessGroupManager {}
unsafe impl Sync for WindowsProcessGroupManager {}

impl WindowsProcessGroupManager {
    pub fn new() -> Self {
        Self {
            job_handle: Mutex::new(std::ptr::null_mut()),
            initialized: AtomicBool::new(false),
        }
    }

    fn handle(&self) -> HANDLE {
        *self.job_handle.lock().unwrap()
    }
}

impl Default for WindowsProcessGroupManager {
    fn default() -> Self {
        Self::new()
    }
}

impl ProcessGroupManager for WindowsProcessGroupManager {
    fn init(&self) -> Result<(), io::Error> {
        if self.initialized.swap(true, Ordering::SeqCst) {
            return Ok(());
        }

        unsafe {
            let handle = CreateJobObjectW(std::ptr::null(), std::ptr::null());
            if handle.is_null() {
                self.initialized.store(false, Ordering::SeqCst);
                return Err(io::Error::last_os_error());
            }

            let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
            info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

            let result = SetInformationJobObject(
                handle,
                JobObjectExtendedLimitInformation,
                &info as *const _ as *const _,
                std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
            );

            if result == FALSE {
                CloseHandle(handle);
                self.initialized.store(false, Ordering::SeqCst);
                return Err(io::Error::last_os_error());
            }

            *self.job_handle.lock().unwrap() = handle;

            info!("Windows Job Object created with KILL_ON_JOB_CLOSE");
            Ok(())
        }
    }

    fn create_lifecycle(&self) -> Box<dyn ProcessLifecycle> {
        Box::new(WindowsProcessLifecycle {
            job_handle: self.handle(),
        })
    }
}

impl Drop for WindowsProcessGroupManager {
    fn drop(&mut self) {
        let handle = self.handle();
        if !handle.is_null() {
            unsafe { CloseHandle(handle); }
            debug!("Windows Job Object closed");
        }
    }
}

/// Per-child lifecycle manager for Windows.
///
/// Assigns children to the parent's Job Object and uses
/// `CTRL_BREAK_EVENT` / `TerminateProcess` for shutdown.
pub struct WindowsProcessLifecycle {
    job_handle: HANDLE,
}

// Safety: same reasoning as WindowsProcessGroupManager — HANDLE is used
// read-only after construction (only passed to OS APIs).
unsafe impl Send for WindowsProcessLifecycle {}
unsafe impl Sync for WindowsProcessLifecycle {}

impl ProcessLifecycle for WindowsProcessLifecycle {
    fn configure_command(&self, cmd: &mut std::process::Command) {
        use std::os::windows::process::CommandExt;
        const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
        const CREATE_NO_WINDOW: u32 = 0x0800_0000;
        cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW);
    }

    #[cfg(feature = "tokio")]
    fn configure_async_command(&self, cmd: &mut tokio::process::Command) {
        use std::os::windows::process::CommandExt;
        const CREATE_NEW_PROCESS_GROUP: u32 = 0x0000_0200;
        const CREATE_NO_WINDOW: u32 = 0x0800_0000;
        cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW);
    }

    fn register_child(&self, pid: u32) -> Result<(), io::Error> {
        if self.job_handle.is_null() {
            warn!(pid, "Job Object not initialized, skipping child registration");
            return Ok(());
        }

        unsafe {
            let process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
            if process_handle.is_null() {
                return Err(io::Error::last_os_error());
            }

            let result = AssignProcessToJobObject(self.job_handle, process_handle);
            CloseHandle(process_handle);

            if result == FALSE {
                let err = io::Error::last_os_error();
                warn!(pid, error = %err, "Failed to assign process to Job Object (may already be in a job)");
                return Err(err);
            }

            debug!(pid, "Process assigned to Job Object");
            Ok(())
        }
    }

    fn send_shutdown_signal(&self, pid: u32) -> Result<(), io::Error> {
        unsafe {
            if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid) == FALSE {
                return Err(io::Error::last_os_error());
            }
        }
        debug!(pid, "Sent CTRL_BREAK_EVENT");
        Ok(())
    }

    fn send_kill_signal(&self, pid: u32) -> Result<(), io::Error> {
        unsafe {
            let handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
            if handle.is_null() {
                let err = io::Error::last_os_error();
                // ERROR_INVALID_PARAMETER (87) means process already exited
                if err.raw_os_error() == Some(87) {
                    return Ok(());
                }
                return Err(err);
            }
            let result = TerminateProcess(handle, 1);
            CloseHandle(handle);
            if result == FALSE {
                let err = io::Error::last_os_error();
                // ERROR_ACCESS_DENIED (5) — process may have already exited
                if err.raw_os_error() == Some(5) {
                    return Ok(());
                }
                return Err(err);
            }
        }
        debug!(pid, "Sent TerminateProcess");
        Ok(())
    }
}
