pub mod db;
pub mod error;
pub mod ipc;
pub mod job;
pub mod paths;
pub mod project;

pub use db::Database;
pub use error::UserError;
pub use job::{Job, Status};
pub use paths::Paths;
pub use project::detect_project;

/// Kill an entire process group.
/// The PID is the process group leader (child was spawned with `process_group(0)`).
#[cfg(unix)]
pub fn kill_process_group(pid: u32, force: bool) {
    use nix::sys::signal::{Signal, killpg};
    use nix::unistd::Pid;

    // SAFETY: Never signal pid 0 - that would kill our own process group!
    if pid == 0 {
        return;
    }

    let signal = if force {
        Signal::SIGKILL
    } else {
        Signal::SIGTERM
    };

    #[allow(clippy::cast_possible_wrap)]
    let _ = killpg(Pid::from_raw(pid as i32), signal);
}

#[cfg(not(unix))]
pub fn kill_process_group(_pid: u32, _force: bool) {
    // No-op on non-Unix platforms
}

/// Parse a duration string like "30s", "5m", "1h", "7d" into seconds
pub fn parse_duration(s: &str) -> anyhow::Result<u64> {
    let s = s.trim();
    let (num, unit) = if let Some(n) = s.strip_suffix('s') {
        (n, 1u64)
    } else if let Some(n) = s.strip_suffix('m') {
        (n, 60u64)
    } else if let Some(n) = s.strip_suffix('h') {
        (n, 3600u64)
    } else if let Some(n) = s.strip_suffix('d') {
        (n, 86400u64)
    } else {
        anyhow::bail!("Invalid duration format. Use: 30s, 5m, 1h, 7d");
    };

    let n: u64 = num.parse()?;
    Ok(n * unit)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_duration_seconds() {
        assert_eq!(parse_duration("30s").unwrap(), 30);
        assert_eq!(parse_duration("1s").unwrap(), 1);
        assert_eq!(parse_duration("0s").unwrap(), 0);
    }

    #[test]
    fn test_parse_duration_minutes() {
        assert_eq!(parse_duration("5m").unwrap(), 300);
        assert_eq!(parse_duration("1m").unwrap(), 60);
    }

    #[test]
    fn test_parse_duration_hours() {
        assert_eq!(parse_duration("1h").unwrap(), 3600);
        assert_eq!(parse_duration("2h").unwrap(), 7200);
    }

    #[test]
    fn test_parse_duration_days() {
        assert_eq!(parse_duration("1d").unwrap(), 86400);
        assert_eq!(parse_duration("7d").unwrap(), 604_800);
    }

    #[test]
    fn test_parse_duration_with_whitespace() {
        assert_eq!(parse_duration("  30s  ").unwrap(), 30);
    }

    #[test]
    fn test_parse_duration_invalid_format() {
        assert!(parse_duration("30").is_err());
        assert!(parse_duration("30x").is_err());
        assert!(parse_duration("abc").is_err());
    }

    #[test]
    fn test_parse_duration_invalid_number() {
        assert!(parse_duration("abcs").is_err());
    }
}
