//! `zg` — ZeroGravity daemon manager.
//!
//! All commands exit immediately (safe for agent use via fast-bash MCP).

use std::process::{Command, Stdio};

const SERVICE: &str = "zerogravity";
const PORT: u16 = 8741;

// ANSI colors
const RED: &str = "\x1b[0;31m";
const GREEN: &str = "\x1b[0;32m";
const YELLOW: &str = "\x1b[1;33m";
const CYAN: &str = "\x1b[0;36m";
const BOLD: &str = "\x1b[1m";
const DIM: &str = "\x1b[2m";
const NC: &str = "\x1b[0m";

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let cmd = args.get(1).map(|s| s.as_str()).unwrap_or("");
    let arg = args.get(2).map(|s| s.as_str());

    match cmd {
        "start" => do_start(),
        "stop" => do_stop(),
        "restart" => do_restart(),
        "rebuild" => do_build(),
        "status" => do_status(),
        "logs" => do_logs(arg.unwrap_or("30"), false),
        "logs-follow" => do_logs(arg.unwrap_or("30"), true),
        "logs-all" => do_logs_all(),
        "test" => do_test(arg.unwrap_or("Say hello in exactly 3 words")),
        "health" => do_health(),
        _ => usage(),
    }
}

fn usage() {
    println!("{BOLD}zg{NC} — ZeroGravity daemon manager\n");
    println!("  {CYAN}start{NC}           Start the proxy daemon");
    println!("  {CYAN}stop{NC}            Stop the proxy daemon");
    println!("  {CYAN}restart{NC}         Rebuild + restart");
    println!("  {CYAN}rebuild{NC}         Build release binary only");
    println!("  {CYAN}status{NC}          Service status + quota + usage");
    println!("  {CYAN}logs{NC} [N]        Show last N lines (default 30)");
    println!("  {CYAN}logs-follow{NC} [N] Tail last N lines + follow");
    println!("  {CYAN}logs-all{NC}        Full log dump");
    println!("  {CYAN}test{NC} [msg]      Quick test request (gemini-3-flash)");
    println!("  {CYAN}health{NC}          Health check");
}

fn project_dir() -> String {
    // Resolve project dir from binary location:
    // binary is at <project>/target/release/zg or <project>/target/debug/zg
    let exe = std::env::current_exe().expect("cannot resolve exe path");
    let exe_dir = exe.parent().expect("no parent dir");

    // Walk up from target/release/ or target/debug/
    if let Some(target_dir) = exe_dir.parent() {
        if let Some(project) = target_dir.parent() {
            return project.to_string_lossy().to_string();
        }
    }

    // Fallback: use current dir
    std::env::current_dir()
        .expect("no cwd")
        .to_string_lossy()
        .to_string()
}

fn base_url() -> String {
    let port = std::env::var("PROXY_PORT")
        .ok()
        .and_then(|p| p.parse::<u16>().ok())
        .unwrap_or(PORT);
    format!("http://localhost:{port}")
}

fn systemctl(args: &[&str]) -> bool {
    Command::new("systemctl")
        .arg("--user")
        .args(args)
        .status()
        .map(|s| s.success())
        .unwrap_or(false)
}

fn curl_get(path: &str) -> Option<String> {
    let url = format!("{}{}", base_url(), path);
    Command::new("curl")
        .args(["-sf", &url])
        .output()
        .ok()
        .filter(|o| o.status.success())
        .map(|o| String::from_utf8_lossy(&o.stdout).to_string())
}

fn curl_post(path: &str, body: &str) -> Option<String> {
    let url = format!("{}{}", base_url(), path);
    Command::new("curl")
        .args([
            "-sf",
            &url,
            "-H",
            "Content-Type: application/json",
            "-d",
            body,
        ])
        .output()
        .ok()
        .filter(|o| o.status.success())
        .map(|o| String::from_utf8_lossy(&o.stdout).to_string())
}

fn health_ok() -> bool {
    curl_get("/health").is_some()
}

fn jq_print(json: &str) {
    let mut child = Command::new("jq")
        .arg(".")
        .stdin(Stdio::piped())
        .spawn()
        .expect("jq not found");
    if let Some(stdin) = child.stdin.as_mut() {
        use std::io::Write;
        let _ = stdin.write_all(json.as_bytes());
    }
    let _ = child.wait();
}

// ── Commands ──

fn do_build() {
    let dir = project_dir();
    println!("{YELLOW}Building release binary...{NC}");
    let status = Command::new("cargo")
        .args(["build", "--release"])
        .current_dir(&dir)
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status()
        .expect("failed to run cargo");

    if status.success() {
        println!("{GREEN}Build complete.{NC}");
    } else {
        eprintln!("{RED}Build failed.{NC}");
        std::process::exit(1);
    }
}

fn do_start() {
    systemctl(&["daemon-reload"]);
    systemctl(&["start", SERVICE]);
    println!("{GREEN}Started.{NC} Waiting for ready...");

    for _ in 0..20 {
        if health_ok() {
            println!("{GREEN}ZeroGravity is up on port {PORT}.{NC}");
            return;
        }
        std::thread::sleep(std::time::Duration::from_millis(500));
    }

    eprintln!("{RED}Proxy didn't become healthy in 10s. Check logs:{NC}");
    let _ = Command::new("journalctl")
        .args(["--user", "-u", SERVICE, "--no-pager", "-n", "20"])
        .status();
    std::process::exit(1);
}

fn do_stop() {
    let _ = systemctl(&["stop", SERVICE]);
    println!("{YELLOW}Stopped.{NC}");
}

fn do_restart() {
    println!("{YELLOW}Stopping...{NC}");
    do_stop();
    do_build();
    do_start();
}

fn do_status() {
    println!("{BOLD}── Service ──{NC}");
    let output = Command::new("systemctl")
        .args(["--user", "status", SERVICE, "--no-pager"])
        .output();
    match output {
        Ok(o) => {
            let text = String::from_utf8_lossy(&o.stdout);
            // Print first 6 lines
            for (i, line) in text.lines().enumerate() {
                if i >= 6 {
                    break;
                }
                println!("{line}");
            }
        }
        Err(_) => println!("{RED}Not running{NC}"),
    }
    println!();

    if !health_ok() {
        println!("{RED}Proxy is not responding on port {PORT}.{NC}");
        std::process::exit(1);
    }

    println!("{BOLD}── Quota ──{NC}");
    match curl_get("/v1/quota") {
        Some(json) => jq_print(&json),
        None => println!("{DIM}(no quota data){NC}"),
    }
    println!();

    println!("{BOLD}── Usage ──{NC}");
    match curl_get("/v1/usage") {
        Some(json) => jq_print(&json),
        None => println!("{DIM}(no usage data){NC}"),
    }
    println!();

    println!("{BOLD}── Sessions ──{NC}");
    match curl_get("/v1/sessions") {
        Some(json) => jq_print(&json),
        None => println!("{DIM}(no sessions){NC}"),
    }
}

fn do_logs(n: &str, follow: bool) {
    let mut args = vec!["--user", "-u", SERVICE, "--no-pager", "-n", n];
    if follow {
        args.push("-f");
    }
    let _ = Command::new("journalctl")
        .args(&args)
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status();
}

fn do_logs_all() {
    let _ = Command::new("journalctl")
        .args(["--user", "-u", SERVICE, "--no-pager"])
        .stdin(Stdio::inherit())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit())
        .status();
}

fn do_test(msg: &str) {
    println!("{CYAN}Testing:{NC} {msg}");
    let body = format!(
        r#"{{"model":"gemini-3-flash","input":"{}","stream":false,"timeout":30}}"#,
        msg.replace('"', r#"\""#)
    );
    match curl_post("/v1/responses", &body) {
        Some(json) => jq_print(&json),
        None => {
            eprintln!("{RED}Request failed. Is the proxy running?{NC}");
            std::process::exit(1);
        }
    }
}

fn do_health() {
    match curl_get("/health") {
        Some(json) => {
            jq_print(&json);
            println!("{GREEN}Healthy{NC}");
        }
        None => {
            println!("{RED}Not responding{NC}");
            std::process::exit(1);
        }
    }
}
