use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RestartPolicy {
    Always,
    OnFailure,
    Never,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum StopSignal {
    #[default]
    Term,
    Int,
    Quit,
}

#[cfg(unix)]
impl StopSignal {
    pub fn as_libc_signal(&self) -> libc::c_int {
        match self {
            Self::Term => libc::SIGTERM,
            Self::Int => libc::SIGINT,
            Self::Quit => libc::SIGQUIT,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum OutputTarget {
    #[default]
    Inherit,
    Null,
    File(PathBuf),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
    #[serde(default)]
    pub stdout: OutputTarget,
    #[serde(default)]
    pub stderr: OutputTarget,
}

impl Default for LoggingConfig {
    fn default() -> Self {
        Self {
            stdout: OutputTarget::Inherit,
            stderr: OutputTarget::Inherit,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessDef {
    pub name: String,
    pub command: String,
    pub restart: RestartPolicy,
    pub max_restarts: u32,
    #[serde(with = "humantime_serde")]
    pub restart_delay: Duration,

    #[serde(default)]
    pub directory: Option<PathBuf>,

    #[serde(default = "default_stop_timeout", with = "humantime_serde")]
    pub stop_timeout: Duration,

    #[serde(default)]
    pub stop_signal: StopSignal,

    #[serde(default = "default_numprocs")]
    pub numprocs: u32,

    #[serde(default)]
    pub env: HashMap<String, String>,

    #[serde(default)]
    pub logging: LoggingConfig,
}

fn default_stop_timeout() -> Duration {
    Duration::from_secs(5)
}

fn default_numprocs() -> u32 {
    1
}

impl Default for ProcessDef {
    fn default() -> Self {
        Self {
            name: "unnamed".into(),
            command: "true".into(),
            restart: RestartPolicy::OnFailure,
            max_restarts: 5,
            restart_delay: Duration::from_secs(1),
            directory: None,
            stop_timeout: default_stop_timeout(),
            stop_signal: StopSignal::default(),
            numprocs: 1,
            env: HashMap::new(),
            logging: LoggingConfig::default(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct ProcessConfig {
    pub processes: Vec<ProcessDef>,
}
