Skip to content
use std::process;

struct Args {
    url: String,
    custom_message: Option<String>,
    severity: String,
    service: String,
    api_key: Option<String>,
    run_lifecycle: bool,
}

fn print_help() {
    println!("Void CLI - A helper tool to send test or custom logs to Void ingestion server.");
    println!();
    println!("Usage:");
    println!("  void-cli [options]");
    println!("  void-cli test-lifecycle [options]");
    println!();
    println!("Commands:");
    println!("  test-lifecycle     Starts the server background process, runs ingestion tests,");
    println!("                     and then shuts the server down automatically.");
    println!();
    println!("Options:");
    println!("  --url <URL>        Ingestion server URL (default: http://127.0.0.1:9180/ingest)");
    println!("  --message <MSG>    Send a single log line with custom message");
    println!("  --severity <SEV>   Severity for custom log line (default: INFO)");
    println!("  --service <SRV>    Service name for custom log line (default: void-cli)");
    println!("  --api-key <KEY>    API key for authentication (fallback: API_KEY env var)");
    println!("  --test-lifecycle   Alias command to run the lifecycle test");
    println!("  -h, --help         Show this help message");
    println!();
    println!("If no custom --message is provided and not running test-lifecycle,");
    println!("3 default test log lines will be sent to the configured URL using the log crate.");
}

fn parse_args() -> Result<Args, pico_args::Error> {
    let mut pargs = pico_args::Arguments::from_env();

    if pargs.contains(["-h", "--help"]) {
        print_help();
        process::exit(0);
    }

    let run_lifecycle = pargs.contains("--test-lifecycle")
        || match pargs.subcommand() {
            Ok(Some(sub)) => sub == "test-lifecycle",
            _ => false,
        };

    let args = Args {
        url: pargs
            .opt_value_from_str("--url")?
            .unwrap_or_else(|| "http://127.0.0.1:9180/ingest".to_string()),
        custom_message: pargs.opt_value_from_str("--message")?,
        severity: pargs
            .opt_value_from_str("--severity")?
            .unwrap_or_else(|| "INFO".to_string()),
        service: pargs
            .opt_value_from_str("--service")?
            .unwrap_or_else(|| "void-cli".to_string()),
        api_key: pargs
            .opt_value_from_str("--api-key")?
            .or_else(|| std::env::var("API_KEY").ok()),
        run_lifecycle,
    };

    let remaining = pargs.finish();
    if !remaining.is_empty() {
        eprintln!("Warning: Unused or unrecognized arguments: {:?}", remaining);
    }

    Ok(args)
}

fn main() {
    let args = match parse_args() {
        Ok(a) => a,
        Err(e) => {
            eprintln!("Error parsing arguments: {}", e);
            print_help();
            process::exit(1);
        }
    };

    if args.run_lifecycle {
        run_server_test_lifecycle(args.api_key);
    } else {
        send_logs_to_server(
            args.url,
            args.custom_message,
            args.severity,
            args.service,
            args.api_key,
        );
    }
}

fn send_logs_to_server(
    url: String,
    custom_message: Option<String>,
    severity: String,
    service: String,
    api_key: Option<String>,
) {
    let severity_upper = severity.to_uppercase();
    let max_level = match severity_upper.as_str() {
        "TRACE" => log::LevelFilter::Trace,
        "DEBUG" => log::LevelFilter::Debug,
        "INFO" => log::LevelFilter::Info,
        "WARN" => log::LevelFilter::Warn,
        "ERROR" => log::LevelFilter::Error,
        _ => log::LevelFilter::Info,
    };

    let mut builder = void_logger::VoidLoggerBuilder::new(&url)
        .service_name(&service)
        .max_level(max_level)
        .batch_size(1)
        .console_output(void_logger::ConsoleOutput::Split);

    if let Some(ref key) = api_key {
        builder = builder.api_key(key);
    }

    if let Err(e) = builder.init() {
        eprintln!("Error: Failed to initialize VoidLogger: {}", e);
        process::exit(1);
    }

    if let Some(msg) = custom_message {
        let level = match severity_upper.as_str() {
            "TRACE" => log::Level::Trace,
            "DEBUG" => log::Level::Debug,
            "INFO" => log::Level::Info,
            "WARN" => log::Level::Warn,
            "ERROR" => log::Level::Error,
            _ => log::Level::Info,
        };
        log::log!(level, "{}", msg);
    } else {
        log::info!(
            "FICSIT Pioneer landed. Remember: efficiency is key, and sleep is a non-essential activity."
        );
        log::warn!("Screws bottleneck detected. Solution: Construct 12 more Constructors.");
        log::error!("Lizard Doggo ate nuclear waste. Sector 4 is now highly radioactive!");
    }

    log::logger().flush();
}

fn run_server_test_lifecycle(api_key: Option<String>) {
    let api_key_value = api_key.unwrap_or_else(|| "test-api-key-12345".to_string());
    println!("=== Starting Lifecycle Test ===");
    println!("API Key for test: {}", api_key_value);

    println!("Spawning server background process via cargo run --bin void...");
    let mut child = match std::process::Command::new("cargo")
        .args(["run", "--bin", "void"])
        .env("API_KEY", &api_key_value)
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .spawn()
    {
        Ok(c) => c,
        Err(e) => {
            eprintln!("Error: Failed to spawn server process: {}", e);
            process::exit(1);
        }
    };

    println!("Waiting for server to become ready at http://127.0.0.1:9180/up...");
    let start_time = std::time::Instant::now();
    let mut is_ready = false;

    while start_time.elapsed() < std::time::Duration::from_secs(15) {
        match child.try_wait() {
            Ok(Some(status)) => {
                eprintln!(
                    "Error: Server process exited prematurely with status: {}",
                    status
                );
                process::exit(1);
            }
            Ok(None) => {}
            Err(e) => {
                eprintln!("Error checking server status: {}", e);
            }
        }

        if let Ok(resp) = minreq::get("http://127.0.0.1:9180/up")
            .with_timeout(1)
            .send()
        {
            if resp.status_code == 200 && resp.as_str().map_or(false, |s| s == "up") {
                is_ready = true;
                break;
            }
        }
        std::thread::sleep(std::time::Duration::from_millis(200));
    }

    if !is_ready {
        eprintln!("Error: Server did not become ready within 15 seconds.");
        let _ = child.kill();
        process::exit(1);
    }

    println!("Server is up! Initializing void-logger...");

    let builder = void_logger::VoidLoggerBuilder::new("http://127.0.0.1:9180/ingest")
        .service_name("test-service")
        .max_level(log::LevelFilter::Info)
        .batch_size(1)
        .console_output(void_logger::ConsoleOutput::Split)
        .api_key(&api_key_value);

    if let Err(e) = builder.init() {
        eprintln!("Error: Failed to initialize VoidLogger: {}", e);
        let _ = child.kill();
        process::exit(1);
    }

    println!("Sending test logs through log facade...");
    log::info!(
        "FICSIT Pioneer landed. Remember: efficiency is key, and sleep is a non-essential activity."
    );
    log::warn!("Screws bottleneck detected. Solution: Construct 12 more Constructors.");
    log::error!("Lizard Doggo ate nuclear waste. Sector 4 is now highly radioactive!");

    println!("Flushing void-logger...");
    log::logger().flush();
    println!("Logs sent and flushed successfully.");

    let mut test_success = true;

    println!("Verifying authentication is enforced (sending without API key)...");
    let unauth_response = minreq::post("http://127.0.0.1:9180/ingest")
        .with_header("Content-Type", "application/json")
        .with_timeout(5)
        .with_body("[]")
        .send();

    match unauth_response {
        Ok(res) => {
            if res.status_code == 401 {
                println!(
                    "Enforcement Success! Server correctly rejected request without API key (status 401)."
                );
            } else {
                eprintln!(
                    "Enforcement Failure! Server returned status {} instead of 401.",
                    res.status_code
                );
                test_success = false;
            }
        }
        Err(e) => {
            eprintln!("Error during enforcement check: {}", e);
            test_success = false;
        }
    }

    println!("Killing server process...");
    if let Err(e) = child.kill() {
        eprintln!("Warning: Failed to kill server process: {}", e);
    } else {
        let _ = child.wait();
        println!("Server process killed successfully.");
    }

    if test_success {
        println!("=== Lifecycle Test PASSED ===");
    } else {
        println!("=== Lifecycle Test FAILED ===");
        process::exit(1);
    }
}