use axum::Router;
use axum::extract::State;
use axum::response::Json;
use axum::routing::get;
use serde::Serialize;

use crate::state::AppState;

#[derive(Debug, Serialize)]
pub struct HealthResponse {
    pub status: String,
    pub version: String,
    pub modules: Vec<String>,
}

/// GET /health — 轻量存活检查
pub async fn health_check(State(state): State<AppState>) -> Json<HealthResponse> {
    let modules = state
        .module_registry
        .modules()
        .iter()
        .map(|m| m.name().to_string())
        .collect();

    Json(HealthResponse {
        status: "ok".to_string(),
        version: env!("CARGO_PKG_VERSION").to_string(),
        modules,
    })
}

#[derive(Debug, Serialize)]
pub struct ReadyResponse {
    pub status: String,
    pub version: String,
    pub database: ComponentStatus,
    pub redis: ComponentStatus,
    pub modules: Vec<String>,
}

#[derive(Debug, Serialize)]
pub struct ComponentStatus {
    pub status: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub latency_ms: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

/// GET /health/ready — 就绪检查（含 DB + Redis 连通性）
pub async fn readiness_check(State(state): State<AppState>) -> Json<ReadyResponse> {
    let modules = state
        .module_registry
        .modules()
        .iter()
        .map(|m| m.name().to_string())
        .collect();

    let (db_status, redis_status) =
        tokio::join!(check_database(&state.db), check_redis(&state.redis),);

    let overall = if db_status.status == "ok" && redis_status.status == "ok" {
        "ok"
    } else if db_status.status == "ok" {
        "degraded"
    } else {
        "unavailable"
    };

    Json(ReadyResponse {
        status: overall.to_string(),
        version: env!("CARGO_PKG_VERSION").to_string(),
        database: db_status,
        redis: redis_status,
        modules,
    })
}

async fn check_database(db: &sea_orm::DatabaseConnection) -> ComponentStatus {
    use sea_orm::ConnectionTrait;
    let start = std::time::Instant::now();
    let stmt =
        sea_orm::Statement::from_string(sea_orm::DatabaseBackend::Postgres, "SELECT 1".to_string());
    match db.query_one(stmt).await {
        Ok(_) => ComponentStatus {
            status: "ok".to_string(),
            latency_ms: Some(start.elapsed().as_millis() as u64),
            error: None,
        },
        Err(e) => {
            tracing::error!(error = %e, "Database health check failed");
            ComponentStatus {
                status: "error".to_string(),
                latency_ms: Some(start.elapsed().as_millis() as u64),
                error: Some("connection failed".to_string()),
            }
        }
    }
}

async fn check_redis(client: &redis::Client) -> ComponentStatus {
    let start = std::time::Instant::now();
    match client.get_multiplexed_async_connection().await {
        Ok(mut conn) => match redis::cmd("PING").query_async::<String>(&mut conn).await {
            Ok(_) => ComponentStatus {
                status: "ok".to_string(),
                latency_ms: Some(start.elapsed().as_millis() as u64),
                error: None,
            },
            Err(e) => {
                tracing::error!(error = %e, "Redis PING failed");
                ComponentStatus {
                    status: "error".to_string(),
                    latency_ms: Some(start.elapsed().as_millis() as u64),
                    error: Some("connection failed".to_string()),
                }
            }
        },
        Err(e) => {
            tracing::error!(error = %e, "Redis connection failed");
            ComponentStatus {
                status: "error".to_string(),
                latency_ms: Some(start.elapsed().as_millis() as u64),
                error: Some("connection failed".to_string()),
            }
        }
    }
}

pub fn health_check_router() -> Router<AppState> {
    Router::new()
        .route("/health", get(health_check))
        .route("/health/live", get(health_check))
        .route("/health/ready", get(readiness_check))
}
