#![cfg(target_os = "linux")]

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, warn};

/// Linux process group manager with kernel-level orphan prevention.
///
/// Uses `PR_SET_CHILD_SUBREAPER` so orphaned grandchildren are reparented
/// to this process, and `PR_SET_PDEATHSIG` so children auto-die when
/// the parent crashes.
pub struct LinuxProcessGroupManager;

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

impl ProcessGroupManager for LinuxProcessGroupManager {
    fn init(&self) -> Result<(), io::Error> {
        unsafe {
            if libc::prctl(libc::PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0 {
                let err = io::Error::last_os_error();
                warn!(error = %err, "Failed to set PR_SET_CHILD_SUBREAPER");
                return Err(err);
            }
        }
        info!("Linux process group manager initialized (child subreaper enabled)");
        Ok(())
    }

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

pub struct LinuxProcessLifecycle;

impl ProcessLifecycle for LinuxProcessLifecycle {
    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());
                }
                if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL, 0, 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());
                }
                if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL, 0, 0, 0) != 0 {
                    return Err(io::Error::last_os_error());
                }
                Ok(())
            });
        }
    }

    fn register_child(&self, pid: u32) -> Result<(), io::Error> {
        debug!(pid, pgid = pid, "Linux child registered (process group + PR_SET_PDEATHSIG)");
        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(())
    }
}
