//! Handlers for read operations (query, health, cluster status, routing).

use axum::extract::{Query, State};
use axum::Json;
use std::sync::Arc;
use tracing::instrument;

use crate::gateway::service::GatewayState;
use crate::sharding::ShardId;

use super::types::{
    ApiError, ClusterStatusResponse, HealthResponse, NodeStatusInfo, QueryParams, QueryResponse,
};

/// GET /v1/query - Query assertions.
#[instrument(skip(state), fields(subject = %params.subject))]
pub async fn handle_query(
    State(state): State<Arc<GatewayState>>,
    Query(params): Query<QueryParams>,
) -> Result<Json<QueryResponse>, ApiError> {
    // 1. Route by subject hash
    let shard_id = state.router.route_subject(&params.subject).map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("Routing failed: {e}"),
    })?;

    // 2. Get replicas, preferring local
    let replicas = state.router.get_replicas_prefer_local(shard_id).map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("No replicas for shard {shard_id}: {e}"),
    })?;

    let replica = replicas.first().ok_or_else(|| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("No replicas available for shard {shard_id}"),
    })?;

    // 3. Forward to replica via RPC (not yet wired)
    tracing::info!(
        shard_id = shard_id,
        replica = %replica.short_hex(),
        "Routed query to replica"
    );

    Ok(Json(QueryResponse { assertions: vec![], shard_id, served_by: replica.short_hex() }))
}

/// GET /v1/health - Health check.
#[instrument(skip(state))]
pub async fn handle_health(State(state): State<Arc<GatewayState>>) -> Json<HealthResponse> {
    let members = state.membership.members();
    let joined = state.membership.is_joined();

    Json(HealthResponse { healthy: joined, reachable_nodes: members.len(), joined })
}

/// GET /v1/cluster/status - Cluster status.
#[instrument(skip(state))]
pub async fn handle_cluster_status(
    State(state): State<Arc<GatewayState>>,
) -> Json<ClusterStatusResponse> {
    let all_members = state.membership.all_members();
    let meta = state.router.get_meta_range();

    let nodes: Vec<NodeStatusInfo> = all_members
        .iter()
        .map(|(info, node_state)| {
            let shards = meta.shards_for_node(info.id);
            NodeStatusInfo { id: info.id.short_hex(), state: format!("{node_state}"), shards }
        })
        .collect();

    Json(ClusterStatusResponse {
        node_count: all_members.len(),
        shard_count: meta.num_shards() as u32,
        meta_version: meta.version,
        nodes,
    })
}

/// GET /v1/shards/:shard_id - Get shard info.
#[instrument(skip(state))]
pub async fn handle_shard_info(
    State(state): State<Arc<GatewayState>>,
    axum::extract::Path(shard_id): axum::extract::Path<ShardId>,
) -> Result<Json<serde_json::Value>, ApiError> {
    let descriptor = state.router.get_descriptor(shard_id).map_err(|_| ApiError {
        code: "NOT_FOUND".to_string(),
        message: format!("Shard {shard_id} not found"),
    })?;

    Ok(Json(serde_json::json!({
        "shard_id": descriptor.shard_id,
        "start_key": descriptor.start_key.as_ref().map(hex::encode),
        "end_key": descriptor.end_key.as_ref().map(hex::encode),
        "replicas": descriptor.replicas.iter().map(|n| n.short_hex()).collect::<Vec<_>>(),
        "size_bytes": descriptor.size_bytes,
        "assertion_count": descriptor.assertion_count,
        "generation": descriptor.generation,
    })))
}

/// GET /v1/route - Test subject routing.
#[instrument(skip(state))]
pub async fn handle_route_test(
    State(state): State<Arc<GatewayState>>,
    Query(params): Query<std::collections::HashMap<String, String>>,
) -> Result<Json<serde_json::Value>, ApiError> {
    let subject = params.get("subject").ok_or_else(|| ApiError {
        code: "BAD_REQUEST".to_string(),
        message: "subject parameter required".to_string(),
    })?;

    let shard_id = state.router.route_subject(subject).map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("Routing failed: {e}"),
    })?;
    let replicas = state
        .router
        .get_replicas(shard_id)
        .map_err(|e| ApiError { code: "UNAVAILABLE".to_string(), message: e.to_string() })?;

    Ok(Json(serde_json::json!({
        "subject": subject,
        "shard_id": shard_id,
        "replicas": replicas.iter().map(|n| n.short_hex()).collect::<Vec<_>>(),
    })))
}
