//! Handlers for write operations (assert, vote).

use axum::extract::State;
use axum::http::HeaderMap;
use axum::Json;
use std::sync::Arc;
use tracing::instrument;

use crate::gateway::service::GatewayState;

use super::types::{ApiError, CreateAssertionRequest, VoteRequest, VoteResponse};

/// POST /v1/assert - Create a new assertion.
///
/// Routes by subject hash to the shard leader and forwards the request via
/// HTTP to that node's stemedb-api. Returns the response from the leader.
#[instrument(skip(state, headers, req), fields(subject = %req.subject))]
pub async fn handle_assert(
    State(state): State<Arc<GatewayState>>,
    headers: HeaderMap,
    Json(req): Json<CreateAssertionRequest>,
) -> Result<Json<serde_json::Value>, ApiError> {
    state.inc_requests();

    // 1. Route by subject hash to determine shard
    let shard_id = state.router.route_subject(&req.subject).map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("Routing failed: {e}"),
    })?;

    // 2. Get leader for this shard
    let leader_id = state.router.get_leader(shard_id).map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("No leader for shard {shard_id}: {e}"),
    })?;

    // 3. Look up leader's HTTP API address via membership
    let leader_info = state.membership.get_member(leader_id).ok_or_else(|| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("Leader {} not found in membership", leader_id.short_hex()),
    })?;

    // 4. Get or create a pooled HTTP client for this node
    let http_client = {
        let entry = state.http_forwarders.entry(leader_id).or_insert_with(reqwest::Client::new);
        entry.clone()
    };

    // 5. Forward to the leader's stemedb-api
    let url = format!("http://{}/v1/assert", leader_info.api_addr);
    tracing::info!(
        shard_id,
        leader = %leader_id.short_hex(),
        url = %url,
        "Forwarding assertion to shard leader"
    );

    let mut builder = http_client.post(&url).json(&req);
    if let Some(key) = headers.get("x-api-key") {
        builder = builder.header("x-api-key", key.to_str().unwrap_or_default());
    }
    let response = builder.send().await.map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("Forward to leader failed: {e}"),
    })?;

    if !response.status().is_success() {
        let status_code = response.status().as_u16();
        let body = response.text().await.unwrap_or_default();
        return Err(ApiError {
            code: "UNAVAILABLE".to_string(),
            message: format!("Leader returned {status_code}: {body}"),
        });
    }

    let result: serde_json::Value = response.json().await.map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("Failed to parse leader response: {e}"),
    })?;

    Ok(Json(result))
}

/// POST /v1/vote - Submit a vote.
///
/// Routes to the shard leader for the assertion's subject and forwards via HTTP.
#[instrument(skip(state, headers, req), fields(subject = %req.subject))]
pub async fn handle_vote(
    State(state): State<Arc<GatewayState>>,
    headers: HeaderMap,
    Json(req): Json<VoteRequest>,
) -> Result<Json<VoteResponse>, ApiError> {
    state.inc_requests();

    // Route by subject hash
    let shard_id = state.router.route_subject(&req.subject).map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("Routing failed: {e}"),
    })?;

    // Get leader
    let leader_id = state.router.get_leader(shard_id).map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("No leader for shard {shard_id}: {e}"),
    })?;

    // Look up leader's API address
    let leader_info = state.membership.get_member(leader_id).ok_or_else(|| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("Leader {} not found in membership", leader_id.short_hex()),
    })?;

    // Get or create a pooled HTTP client
    let http_client = {
        let entry = state.http_forwarders.entry(leader_id).or_insert_with(reqwest::Client::new);
        entry.clone()
    };

    // Forward to leader's stemedb-api
    let url = format!("http://{}/v1/vote", leader_info.api_addr);
    tracing::info!(
        shard_id,
        leader = %leader_id.short_hex(),
        assertion_id = %req.assertion_id,
        "Forwarding vote to shard leader"
    );

    let mut builder = http_client.post(&url).json(&req);
    if let Some(key) = headers.get("x-api-key") {
        builder = builder.header("x-api-key", key.to_str().unwrap_or_default());
    }
    let response = builder.send().await.map_err(|e| ApiError {
        code: "UNAVAILABLE".to_string(),
        message: format!("Forward to leader failed: {e}"),
    })?;

    if !response.status().is_success() {
        let status_code = response.status().as_u16();
        let body = response.text().await.unwrap_or_default();
        return Err(ApiError {
            code: "UNAVAILABLE".to_string(),
            message: format!("Leader returned {status_code}: {body}"),
        });
    }

    Ok(Json(VoteResponse { success: true, shard_id }))
}
