#![cfg(unix)]

use crate::traits::{ProcessGroupManager, ProcessLifecycle};
use nix::sys::signal::{killpg, Signal};
use nix::unistd::Pid;
use std::io;
use std::os::unix::process::CommandExt;
use tracing::{debug, info};

/// macOS / generic Unix process group manager.
///
/// Uses process groups for tree management. No kernel-level orphan
/// prevention (macOS lacks `PR_SET_PDEATHSIG`). Relies on launchd
/// supervision for crash recovery.
pub struct UnixProcessGroupManager;

impl UnixProcessGroupManager {
    pub fn new() -> Self { Self }
}

impl ProcessGroupManager for UnixProcessGroupManager {
    fn init(&self) -> Result<(), io::Error> {
        info!("Unix process group manager initialized");
        Ok(())
    }

    fn create_lifecycle(&self) -> Box<dyn ProcessLifecycle> {
        Box::new(UnixProcessLifecycle)
    }
}

pub struct UnixProcessLifecycle;

impl ProcessLifecycle for UnixProcessLifecycle {
    fn configure_command(&self, cmd: &mut std::process::Command) {
        unsafe {
            cmd.pre_exec(|| {
                if libc::setpgid(0, 0) != 0 {
                    return Err(io::Error::last_os_error());
                }
                Ok(())
            });
        }
    }

    #[cfg(feature = "tokio")]
    fn configure_async_command(&self, cmd: &mut tokio::process::Command) {
        unsafe {
            cmd.pre_exec(|| {
                if libc::setpgid(0, 0) != 0 {
                    return Err(io::Error::last_os_error());
                }
                Ok(())
            });
        }
    }

    fn register_child(&self, pid: u32) -> Result<(), io::Error> {
        debug!(pid, pgid = pid, "Child registered in its own process group");
        Ok(())
    }

    fn send_shutdown_signal(&self, pid: u32) -> Result<(), io::Error> {
        let pgid = Pid::from_raw(pid as i32);
        killpg(pgid, Signal::SIGTERM)
            .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
        debug!(pid, "Sent SIGTERM to process group");
        Ok(())
    }

    fn send_kill_signal(&self, pid: u32) -> Result<(), io::Error> {
        let pgid = Pid::from_raw(pid as i32);
        killpg(pgid, Signal::SIGKILL)
            .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
        debug!(pid, "Sent SIGKILL to process group");
        Ok(())
    }
}
