use std::sync::Arc;
use anyhow::Result;
use colored::Colorize;
use serde::Serialize;
use tokio::time::Duration;
use tracing::info;
use crate::api::{search_library, create_http_client, run_with_retry};
use crate::i18n::{t, Message};
use crate::output::{emit_ndjson, print_health_line, health_symbol};
use crate::storage::load_api_keys;
#[derive(Debug, Serialize)]
pub struct HealthReport {
pub config_ok: bool,
pub keys_count: usize,
pub api_reachable: bool,
pub api_details: Option<String>,
}
#[must_use]
pub async fn run_health(json: bool) -> Result<i32> {
info!("Running health check");
if !json {
print_health_line(&t(Message::HealthRunning).cyan().to_string());
}
let client = match create_http_client() {
Ok(c) => {
if !json {
print_health_line(&format!(
"{} {}",
health_symbol(true),
t(Message::HealthConfigOk).green()
));
}
c
}
Err(err) => {
let detail = err.to_string();
if json {
let report = HealthReport {
config_ok: false,
keys_count: 0,
api_reachable: false,
api_details: Some(detail.clone()),
};
emit_ndjson("health", &report);
} else {
print_health_line(&format!(
"{} {} — {}",
health_symbol(false),
t(Message::HealthConfigFailed).red(),
detail
));
}
return Ok(74);
}
};
let keys = match load_api_keys() {
Ok(c) if !c.is_empty() => {
if !json {
print_health_line(&format!(
"{} {} {}",
health_symbol(true),
t(Message::HealthKeysOk).green(),
c.len().to_string().bold()
));
}
c
}
_ => {
if json {
let report = HealthReport {
config_ok: true,
keys_count: 0,
api_reachable: false,
api_details: None,
};
emit_ndjson("health", &report);
} else {
print_health_line(&format!(
"{} {}",
health_symbol(false),
t(Message::HealthKeysMissing).yellow()
));
}
return Ok(66);
}
};
let client_arc = Arc::new(client);
let probe_result = tokio::time::timeout(
Duration::from_secs(10),
run_with_retry(&keys, move |key| {
let c = Arc::clone(&client_arc);
async move { search_library(&c, &key, "react", "health probe").await }
}),
)
.await;
let api_reachable = matches!(probe_result, Ok(Ok(_)));
let api_details = match &probe_result {
Err(_) => Some("timeout after 10s".to_string()),
Ok(Err(e)) => Some(e.to_string()),
Ok(Ok(_)) => None,
};
if json {
let report = HealthReport {
config_ok: true,
keys_count: keys.len(),
api_reachable,
api_details: api_details.clone(),
};
emit_ndjson("health", &report);
} else if api_reachable {
print_health_line(&format!(
"{} {}",
health_symbol(true),
t(Message::HealthApiOk).green()
));
} else {
let detail = api_details.as_deref().unwrap_or("");
print_health_line(&format!(
"{} {} — {}",
health_symbol(false),
t(Message::HealthApiOffline).red(),
detail
));
}
if api_reachable {
Ok(0)
} else {
Ok(69)
}
}