//! HTTP observability server for partition nodes.
//!
//! Exposes three endpoints:
//! - `GET /metrics` — Prometheus text-format metrics
//! - `GET /health`  — Always returns 200 OK if the process is alive
//! - `GET /ready`   — Returns 200 "ready" or 503 "not ready" based on startup state

pub mod node_metrics;
pub use node_metrics::PartitionNodeMetrics;

#[cfg(feature = "distributed")]
use axum::{
    extract::State,
    http::StatusCode,
    response::IntoResponse,
    routing::get,
    Router,
};
#[cfg(feature = "distributed")]
use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc,
};

/// Shared state for the observability HTTP server.
#[cfg(feature = "distributed")]
#[derive(Clone)]
pub struct ObsState {
    pub metrics: Arc<PartitionNodeMetrics>,
    pub ready: Arc<AtomicBool>,
}

/// Bind and serve the observability HTTP server.
#[cfg(feature = "distributed")]
pub async fn serve(
    addr: &str,
    state: ObsState,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let app = Router::new()
        .route("/metrics", get(metrics_handler))
        .route("/health", get(health_handler))
        .route("/ready", get(ready_handler))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind(addr).await?;
    tracing::info!(addr, "HTTP observability server listening");
    axum::serve(listener, app).await?;
    Ok(())
}

/// Return Prometheus text-format metrics.
#[cfg(feature = "distributed")]
async fn metrics_handler(State(state): State<ObsState>) -> impl IntoResponse {
    let body = state.metrics.encode();
    (
        StatusCode::OK,
        [(
            axum::http::header::CONTENT_TYPE,
            "text/plain; version=0.0.4; charset=utf-8",
        )],
        body,
    )
}

/// Liveness probe — always OK if the process is running.
#[cfg(feature = "distributed")]
async fn health_handler() -> impl IntoResponse {
    (StatusCode::OK, "OK")
}

/// Readiness probe — 200 when the node has finished startup, 503 otherwise.
#[cfg(feature = "distributed")]
async fn ready_handler(State(state): State<ObsState>) -> impl IntoResponse {
    if state.ready.load(Ordering::Relaxed) {
        (StatusCode::OK, "ready")
    } else {
        (StatusCode::SERVICE_UNAVAILABLE, "not ready")
    }
}
