zc2 0.0.25

P2P compute broker with credit-based billing, WAL, and broker mesh support
use crate::common;
use crate::envs;
use crate::exec;
use std::env;

use colored::Colorize;

const MAINTAINER_FILTER: &str = "label=maintainer=dev@zakuro-ai.com";

pub fn rmi() {
    kill();
    // Was: `docker rmi -f $(docker images --filter ... -a -q)` (shell command
    // substitution). Resolve the IDs in Rust, then remove them as explicit
    // argv — no shell (audit M4b).
    if let Ok(ids) = exec::run(&[
        "docker",
        "images",
        "--filter",
        MAINTAINER_FILTER,
        "-a",
        "-q",
    ]) {
        let id_list: Vec<&str> = ids.split_whitespace().collect();
        if !id_list.is_empty() {
            let mut argv: Vec<&str> = vec!["docker", "rmi", "-f"];
            argv.extend(id_list);
            exec::run_tty(&argv);
        }
    }
}

pub fn push(image: Option<&str>) {
    if let (Some(image_value), Ok(dist_str)) = (image, common::dist()) {
        // Was: `docker tag … && docker push … && docker rmi …` (shell &&).
        // Three explicit argv commands instead.
        let latest = format!("zakuroai/{}:latest", image_value);
        let tagged = format!("zakuroai/{}:{}", image_value, dist_str);
        let _ = exec::run(&["docker", "tag", latest.as_str(), tagged.as_str()]);
        let _ = exec::run(&["docker", "push", tagged.as_str()]);
        let _ = exec::run(&["docker", "rmi", tagged.as_str()]);
    }
}

pub fn images() {
    exec::run_tty(&["docker", "images", "--filter", MAINTAINER_FILTER, "-a"]);
}

pub fn pull() {
    if let Ok(dist_str) = common::dist() {
        for image in ["network", "storage", "compute"] {
            let tagged = format!("zakuroai/{}:{}", image, dist_str);
            let latest = format!("zakuroai/{}:latest", image);
            exec::run_tty(&["docker", "pull", tagged.as_str()]);
            exec::run_tty(&["docker", "tag", tagged.as_str(), latest.as_str()]);
            exec::run_tty(&["docker", "rmi", tagged.as_str()]);
        }
    }
}

pub fn kill() {
    // Was: `ids=$(docker ps --filter 'name=zk0*' -a -q);echo $ids`. List the
    // container IDs as argv and stop/remove each — no shell.
    if let Ok(ids) = exec::run(&["docker", "ps", "--filter", "name=zk0*", "-a", "-q"]) {
        for id in ids.split_whitespace() {
            exec::run_tty(&["docker", "stop", id]);
            exec::run_tty(&["docker", "rm", id]);
        }
    }
}

pub fn remove_container() {
    // Was: `docker stop zk0 && docker rm zk0` (shell &&).
    let _ = exec::run(&["docker", "stop", "zk0"]);
    let _ = exec::run(&["docker", "rm", "zk0"]);
}

pub fn restart() {
    envs::update();
    if let Ok(zakuro_context) = env::var("ZAKURO_CONTEXT") {
        // Was: `cd {context} && docker compose down && docker compose up -d`.
        // Run compose in the context dir via current_dir — no `cd`, no shell
        // interpolation of the context path (audit M4b).
        exec::run_tty_in(Some(&zakuro_context), &["docker", "compose", "down"]);
        exec::run_tty_in(Some(&zakuro_context), &["docker", "compose", "up", "-d"]);
    }
}

pub fn add_worker() {
    // The inner `bash -c /spark` is the CONTAINER's shell (fixed command); the
    // outer call is now explicit argv (no host shell).
    let _ = exec::run(&["docker", "exec", "-d", "zk0", "bash", "-c", "/spark"]);
}

pub fn connect() {
    // Interactive shell into the container — inherited stdio gives it the TTY.
    exec::run_tty(&[
        "docker",
        "exec",
        "-it",
        "-e",
        "TERM=xterm-256color",
        "zk0",
        "/bin/bash",
    ]);
}

pub fn nodes() {
    // Was: `nmap -sP 10.13.13.0/24 -oG - | awk '/Up$/{print $2}'`. Run nmap and
    // parse the greppable output in Rust (no shell pipe). Each "Up" host line is
    // `Host: <ip> (...)  Status: Up`; field index 1 is the IP.
    if let Ok(out) = exec::run(&["nmap", "-sP", "10.13.13.0/24", "-oG", "-"]) {
        let ips: Vec<String> = out
            .lines()
            .filter(|l| l.trim_end().ends_with("Up"))
            .filter_map(|l| l.split_whitespace().nth(1).map(|s| s.to_string()))
            .collect();
        println!("{}\t\t\t{}", "[Nodes]".bold().blue(), ips.join("|"));
    }
}

pub fn server_list() {
    let _ = exec::zk0("jupyter-server list");
}

pub fn ps() {
    exec::run_tty(&["docker", "ps", "--filter", MAINTAINER_FILTER, "-a"]);
}