//! `pelagos stop` — send SIGTERM to a running container, wait for exit, SIGKILL if needed.

use super::{check_liveness, read_state, write_state, ContainerStatus};

pub fn cmd_stop(name: &str, time: u64) -> Result<(), Box<dyn std::error::Error>> {
    let mut state = read_state(name).map_err(|_| format!("no container named '{}'", name))?;

    if state.status != ContainerStatus::Running {
        // Already stopped — idempotent like `docker stop`. Fixes #232.
        return Ok(());
    }

    // In detached mode there is a brief window where pid==0: the watcher has
    // written state.json but hasn't yet spawned the container process.  Poll
    // until the real PID appears so we send SIGTERM to the right process.
    if state.pid == 0 && check_liveness(state.watcher_pid) {
        let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
        while state.pid == 0
            && state.status == ContainerStatus::Running
            && check_liveness(state.watcher_pid)
            && std::time::Instant::now() < deadline
        {
            std::thread::sleep(std::time::Duration::from_millis(50));
            if let Ok(s) = read_state(name) {
                state = s;
            }
        }
    }

    if !check_liveness(state.pid) {
        // Already dead — update state and return.
        state.status = ContainerStatus::Exited;
        write_state(&state)?;
        return Ok(());
    }

    let pid = state.pid;

    let r = unsafe { libc::kill(pid, libc::SIGTERM) };
    if r != 0 {
        let err = std::io::Error::last_os_error();
        if err.raw_os_error() == Some(libc::ESRCH) {
            // Process already gone.
        } else {
            return Err(format!("kill({}): {}", pid, err).into());
        }
    }

    // Wait up to `time` seconds for the process to exit cleanly.
    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(time.max(1));
    while check_liveness(pid) && std::time::Instant::now() < deadline {
        std::thread::sleep(std::time::Duration::from_millis(100));
    }

    // If still alive after the grace period, escalate to SIGKILL.
    if check_liveness(pid) {
        unsafe { libc::kill(pid, libc::SIGKILL) };
        let kill_deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
        while check_liveness(pid) && std::time::Instant::now() < kill_deadline {
            std::thread::sleep(std::time::Duration::from_millis(100));
        }
    }

    // Update state to exited.
    state.status = ContainerStatus::Exited;
    write_state(&state)?;

    Ok(())
}
