//! `pelagos rm` — remove a container.

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

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

    if state.status == ContainerStatus::Running && check_liveness(state.pid) {
        if force {
            // SIGTERM first so the watcher can run teardown (veth/netns/nftables cleanup).
            // The watcher's SIGTERM handler forwards the signal to the container; when the
            // container exits the watcher's wait() returns and teardown_resources() runs.
            let r = unsafe { libc::kill(state.pid, libc::SIGTERM) };
            if r != 0 {
                let err = std::io::Error::last_os_error();
                if err.raw_os_error() != Some(libc::ESRCH) {
                    return Err(format!("kill({}): {}", state.pid, err).into());
                }
            }
            // Give the container up to 5 s to exit cleanly before resorting to SIGKILL.
            let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
            while check_liveness(state.pid) && std::time::Instant::now() < deadline {
                std::thread::sleep(std::time::Duration::from_millis(100));
            }
            if check_liveness(state.pid) {
                unsafe { libc::kill(state.pid, libc::SIGKILL) };
                for _ in 0..10 {
                    std::thread::sleep(std::time::Duration::from_millis(100));
                    if !check_liveness(state.pid) {
                        break;
                    }
                }
            }
            // Sweep the whole container cgroup so forked/`setsid`'d descendants (which
            // the single-PID kill above misses) can't survive as orphans (#412).
            if let Some(ref cg) = state.cgroup_name {
                pelagos::cgroup::kill_cgroup(cg);
            }
            // Wait for the watcher to finish cleanup (nftables/veth/overlay teardown)
            // before removing state.  The watcher exits shortly after the container dies.
            if state.watcher_pid > 0 {
                let watcher_deadline =
                    std::time::Instant::now() + std::time::Duration::from_secs(5);
                while check_liveness(state.watcher_pid as i32)
                    && std::time::Instant::now() < watcher_deadline
                {
                    std::thread::sleep(std::time::Duration::from_millis(50));
                }
            }
        } else {
            return Err(format!(
                "container '{}' is running; use --force to remove it or `pelagos stop {}` first",
                name, name
            )
            .into());
        }
    }

    // Reject an empty/separator-laden name before building a removal path: an
    // empty name makes `container_dir("")` resolve to the containers parent dir
    // (wiping ALL containers), and a `/`-leading name escapes it entirely (#347).
    if name.is_empty() || name.contains('/') || name == "." || name == ".." {
        return Err(format!("invalid container name '{}'", name).into());
    }
    let dir = container_dir(name);
    pelagos::paths::guarded_remove_dir_all(&dir)
        .map_err(|e| format!("remove {}: {}", dir.display(), e))?;

    Ok(())
}
