use log::{debug, error, info};
use nix::sys::signal::kill;
use nix::sys::signal::Signal;
use nix::unistd::Pid;
use std::ffi::OsStr;
use std::fs::read;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::thread::sleep;
use std::time::{Duration, Instant};
use tempfile::NamedTempFile;
#[derive(Debug, thiserror::Error)]
pub enum DaemonError {
            #[error("daemon took longer than {0} ms to start: {1}\n{2}")]
    Timeout(u128, String, String),
        #[error(transparent)]
    TempFile(#[from] std::io::Error),
        #[error("failed to read daemon stdout: {0}")]
    Stdout(std::io::Error),
        #[error("failed to read daemon stderr: {0}")]
    Stderr(std::io::Error),
        #[error("failed to kill process {0}: {1}")]
    Kill(i32, nix::Error),
}
pub struct DaemonManager {
    timeout: Duration,
}
impl Default for DaemonManager {
    fn default() -> Self {
        Self {
            timeout: Duration::from_millis(1000),
        }
    }
}
impl DaemonManager {
        pub fn new() -> Self {
        Self::default()
    }
            pub fn timeout(mut self, millis: u64) -> Self {
        self.timeout = Duration::from_millis(millis);
        self
    }
                                    pub fn start(
        &self,
        argv: &[&OsStr],
        stdout: &Path,
        stderr: &Path,
    ) -> Result<Daemon, DaemonError> {
        info!("start daemon: {:?}", argv);
        let pid = NamedTempFile::new()?;
        let output = Command::new("daemonize")
            .args(&[
                "-c",
                "/",
                "-e",
                &stderr.display().to_string(),
                "-o",
                &stdout.display().to_string(),
                "-p",
                &pid.path().display().to_string(),
            ])
            .args(argv)
            .output()
            .unwrap();
        if output.status.code() != Some(0) {
            eprintln!("{}", String::from_utf8_lossy(&output.stdout));
            eprintln!("{}", String::from_utf8_lossy(&output.stderr));
            std::process::exit(1);
        }
        debug!("waiting for daemon to write PID file");
        let time = Instant::now();
        while time.elapsed() < self.timeout {
                        if let Ok(pid) = std::fs::read(pid.path()) {
                                let pid = String::from_utf8_lossy(&pid).into_owned();
                                if let Some(pid) = pid.strip_suffix('\n') {
                                        if let Ok(pid) = pid.parse() {
                                                info!("got pid for daemon: pid");
                        return Ok(Daemon::new(pid, stdout, stderr));
                    }
                }
                sleep_briefly();
            } else {
                sleep_briefly();
            }
        }
        error!(
            "no PID file within {} ms, giving up",
            self.timeout.as_millis()
        );
        let mut cmd = String::new();
        for arg in argv {
            if !cmd.is_empty() {
                cmd.push(' ');
            }
            cmd.push_str(
                &String::from_utf8_lossy(arg.as_bytes())
                    .to_owned()
                    .to_string(),
            );
        }
        let err = read(&stderr).map_err(DaemonError::Stderr)?;
        let err = String::from_utf8_lossy(&err).into_owned();
        Err(DaemonError::Timeout(self.timeout.as_millis(), cmd, err))
    }
}
#[derive(Debug)]
pub struct Daemon {
    pid: Option<i32>,
    stdout: PathBuf,
    stderr: PathBuf,
}
impl Daemon {
    fn new(pid: i32, stdout: &Path, stderr: &Path) -> Self {
        info!("started daemon with PID {}", pid);
        Self {
            pid: Some(pid),
            stdout: stdout.to_path_buf(),
            stderr: stderr.to_path_buf(),
        }
    }
                    pub fn stop(&mut self) -> Result<(), DaemonError> {
        if let Some(raw_pid) = self.pid.take() {
            info!("stopping daemon with PID {}", raw_pid);
            let pid = Pid::from_raw(raw_pid);
            kill(pid, Some(Signal::SIGKILL)).map_err(|e| DaemonError::Kill(raw_pid, e))?;
        }
        Ok(())
    }
        pub fn stdout(&self) -> Result<Vec<u8>, DaemonError> {
        std::fs::read(&self.stdout).map_err(DaemonError::Stdout)
    }
        pub fn stderr(&self) -> Result<Vec<u8>, DaemonError> {
        std::fs::read(&self.stderr).map_err(DaemonError::Stderr)
    }
}
impl Drop for Daemon {
    fn drop(&mut self) {
        if self.stop().is_err() {
                    }
    }
}
fn sleep_briefly() {
    sleep(Duration::from_millis(100));
}