use std::{ffi::OsStr, os::fd::RawFd};

use nix::sys::{resource::rlim_t, signal::Signal};

use crate::{
    sandbox::RawIoctlMap,
    unshare::{ffi_util::ToCString, Command},
};

impl Command {
    /// Allow child process to daemonize. By default we run equivalent of
    /// `set_parent_death_signal(SIGKILL)`. See the `set_parent_death_signal`
    /// for better explanation.
    pub fn allow_daemonize(&mut self) -> &mut Command {
        self.config.death_sig = None;
        self
    }

    /// Set a signal that is sent to a process when it's parent is dead.
    /// This is by default set to `SIGKILL`. And you should keep it that way
    /// unless you know what you are doing.
    ///
    /// Particularly you should consider the following choices:
    ///
    /// 1. Instead of setting ``PDEATHSIG`` to some other signal, send signal
    ///    yourself and wait until child gracefully finishes.
    ///
    /// 2. Instead of daemonizing use ``systemd``/``upstart``/whatever system
    ///    init script to run your service
    ///
    /// Another issue with this option is that it works only with immediate
    /// child. To better control all descendant processes you may need the
    /// following:
    ///
    /// 1. The `prctl(PR_SET_CHILD_SUBREAPER..)` in parent which allows to
    ///    "catch" descendant processes.
    ///
    /// 2. The pid namespaces
    ///
    /// The former is out of scope of this library. The latter works by
    /// ``cmd.unshare(Namespace::Pid)``, but you may need to setup mount points
    /// and other important things (which are out of scope too).
    ///
    /// To reset this behavior use ``allow_daemonize()``.
    pub fn set_parent_death_signal(&mut self, sig: Signal) -> &mut Command {
        self.config.death_sig = Some(sig);
        self
    }

    /// Keep signal mask intact after executing child, keeps also ignored
    /// signals
    ///
    /// By default signal mask is empty and all signals are reset to the
    /// `SIG_DFL` value right before `execve()` syscall.
    ///
    /// This is only useful if started process is aware of the issue and sets
    /// sigmasks to some reasonable value. When used wisely it may avoid some
    /// race conditions when signal is sent after child is cloned but before
    /// child have been able to establish it's state.
    pub fn keep_sigmask(&mut self) -> &mut Command {
        self.config.restore_sigmask = false;
        self
    }

    /// Set the argument zero for the process
    ///
    /// By default argument zero is same as path to the program to run. You
    /// may set it to a short name of the command or to something else to
    /// pretend there is a symlink to a program (for example to run `gzip` as
    /// `gunzip`).
    pub fn arg0<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Command {
        if let Some(ref mut exe_args) = self.exe_args {
            exe_args[0] = arg.to_cstring();
        } else {
            self.exe_args = Some(vec![arg.to_cstring()]);
        }
        self
    }

    /// Makes child process a group leader
    ///
    /// If child process is being launched as a foreground job,
    /// the child process group needs to be put into the foreground on
    /// the controlling terminal using `tcsetpgrp`. To request status
    /// information from stopped child process you should call `waitpid` with
    /// `WUNTRACED` flag. And then check status with `WIFSTOPPED` macro.
    /// After giving child process group access to the controlling terminal
    /// you should send the SIGCONT signal to the child process group.
    pub fn make_group_leader(&mut self, make_group_leader: bool) -> &mut Command {
        self.config.make_group_leader = make_group_leader;
        self
    }

    /// Deny reading the timestamp counter (x86 only)
    pub fn deny_tsc(&mut self, deny: bool) -> &mut Command {
        self.config.deny_tsc = deny;
        self
    }

    /// Makes process keeps capabilities (only CAP_SYS_PTRACE is dropped atm).
    pub fn keep(&mut self, keep: bool) -> &mut Command {
        self.config.keep = keep;
        self
    }

    /// STOPs process before exec, so e.g. a ptracer can attach.
    pub fn stop(&mut self, stop: bool) -> &mut Command {
        self.config.stop = stop;
        self
    }

    /// Disable Speculative Store Bypass mitigations for seccomp(2) filters.
    pub fn ssb(&mut self, ssb: bool) -> &mut Command {
        self.config.ssb = ssb;
        self
    }

    /// Enable append-only mitigations.
    ///
    /// Currently only disables pwritev2(2) with the RWF_NOAPPEND flag.
    pub fn append_only(&mut self, append_only: bool) -> &mut Command {
        self.config.append_only = append_only;
        self
    }

    /// Enable kernel pointer in syscall arguments mitigations.
    pub fn restrict_kptr(&mut self, restrict_kptr: bool) -> &mut Command {
        self.config.restrict_kptr = restrict_kptr;
        self
    }

    /// Hint whether prlimit(2) is enabled by seccomp(2) so resource limits can be applied late.
    pub fn restrict_prlimit(&mut self, restrict_prlimit: bool) -> &mut Command {
        self.config.restrict_prlimit = restrict_prlimit;
        self
    }

    /// Specify pseudoterminal file descriptor.
    pub fn pty(&mut self, fd: Option<RawFd>) -> &mut Command {
        self.pty_fd = fd;
        self
    }

    /// Specify ioctl(2) denylist.
    pub fn ioctl_denylist(&mut self, denylist: Option<RawIoctlMap>) -> &mut Command {
        self.ioctl_denylist = denylist;
        self
    }

    /// Specify RLIMIT_AS rlimit(2).
    pub fn rlimit_as(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_as = lim;
        self
    }

    /// Specify RLIMIT_CORE rlimit(2).
    pub fn rlimit_core(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_core = lim;
        self
    }

    /// Specify RLIMIT_CPU rlimit(2).
    pub fn rlimit_cpu(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_cpu = lim;
        self
    }

    /// Specify RLIMIT_DATA rlimit(2).
    pub fn rlimit_data(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_data = lim;
        self
    }

    /// Specify RLIMIT_FSIZE rlimit(2).
    pub fn rlimit_fsize(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_fsize = lim;
        self
    }

    /// Specify RLIMIT_MEMLOCK rlimit(2).
    pub fn rlimit_memlock(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_memlock = lim;
        self
    }

    /// Specify RLIMIT_MSGQUEUE rlimit(2).
    pub fn rlimit_msgqueue(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_msgqueue = lim;
        self
    }

    /// Specify RLIMIT_NICE rlimit(2).
    pub fn rlimit_nice(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_nice = lim;
        self
    }

    /// Specify RLIMIT_NOFILE rlimit(2).
    pub fn rlimit_nofile(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_nofile = lim;
        self
    }

    /// Specify RLIMIT_NPROC rlimit(2).
    pub fn rlimit_nproc(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_nproc = lim;
        self
    }

    /// Specify RLIMIT_RTPRIO rlimit(2).
    pub fn rlimit_rtprio(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_rtprio = lim;
        self
    }

    /// Specify RLIMIT_RTTIME rlimit(2).
    pub fn rlimit_rttime(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_rttime = lim;
        self
    }

    /// Specify RLIMIT_SIGPENDING rlimit(2).
    pub fn rlimit_sigpending(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_sigpending = lim;
        self
    }

    /// Specify RLIMIT_STACK rlimit(2).
    pub fn rlimit_stack(&mut self, lim: Option<rlim_t>) -> &mut Command {
        self.config.rlimit_stack = lim;
        self
    }
}
