//! Handler for creating votes.

use axum::{extract::State, http::StatusCode, Json};
use tracing::instrument;

use crate::{
    dto::{CreateResponse, CreateVoteRequest, ErrorResponse},
    error::{ApiError, Result},
    hex,
    state::AppState,
};

use stemedb_core::types::Vote;
use stemedb_ingest::worker::serialize_vote;

/// Create a new vote on an assertion.
///
/// This endpoint accepts a vote DTO, validates weight bounds (0.0-1.0),
/// converts hex-encoded fields to binary, serializes the vote to WAL format,
/// and appends it to the journal. Returns the content-addressed BLAKE3 hash.
///
/// # Validation
/// - Weight must be between 0.0 and 1.0
/// - All hex fields must have correct lengths (32 bytes for agent_id/assertion_hash, 64 bytes for signature)
#[utoipa::path(
    post,
    path = "/v1/vote",
    request_body = CreateVoteRequest,
    responses(
        (status = 201, description = "Vote created successfully", body = CreateResponse),
        (status = 400, description = "Invalid request", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "votes"
)]
#[instrument(skip(state), fields(assertion_hash = %req.assertion_hash, weight = %req.weight))]
pub async fn create_vote(
    State(state): State<AppState>,
    Json(req): Json<CreateVoteRequest>,
) -> Result<(StatusCode, Json<CreateResponse>)> {
    // Convert DTO to internal Vote type
    let vote = dto_to_vote(req)?;

    // Serialize to WAL format (includes record type header)
    let payload = serialize_vote(&vote)
        .map_err(|e| ApiError::Serialization(format!("Failed to serialize vote: {}", e)))?;

    // Compute the content-addressed hash
    let serialized_vote = stemedb_core::serde::serialize(&vote)
        .map_err(|e| ApiError::Serialization(format!("Failed to serialize for hash: {}", e)))?;
    let hash = blake3::hash(&serialized_vote);

    // Append to WAL via group commit buffer
    state.commit_buffer.append(payload).await?;

    let response =
        CreateResponse { hash: hash.to_hex().to_string(), status: "created".to_string() };

    Ok((StatusCode::CREATED, Json(response)))
}

/// Convert CreateVoteRequest DTO to internal Vote type.
fn dto_to_vote(req: CreateVoteRequest) -> Result<Vote> {
    // Validate weight bounds (0.0 to 1.0)
    if req.weight < 0.0 || req.weight > 1.0 {
        return Err(ApiError::InvalidRequest(format!(
            "Weight must be between 0.0 and 1.0, got {}",
            req.weight
        )));
    }

    // Validate source_url if provided
    if let Some(ref url) = req.source_url {
        // Check if empty
        if url.is_empty() {
            return Err(ApiError::InvalidRequest(
                "source_url must not be empty if provided".to_string(),
            ));
        }
        // Check max length (2048 characters)
        if url.len() > 2048 {
            return Err(ApiError::InvalidRequest(format!(
                "source_url exceeds maximum length of 2048 characters, got {}",
                url.len()
            )));
        }
    }

    // Validate observed_context size if provided
    let observed_context = if let Some(ref context_str) = req.observed_context {
        let context_bytes = context_str.as_bytes();
        // Check max size (64KB)
        if context_bytes.len() > 65536 {
            return Err(ApiError::InvalidRequest(format!(
                "observed_context exceeds maximum size of 64KB, got {} bytes",
                context_bytes.len()
            )));
        }
        Some(context_bytes.to_vec())
    } else {
        None
    };

    let assertion_hash = hex::decode_hash_32(&req.assertion_hash)?;
    let agent_id = hex::decode_hash_32(&req.agent_id)?;
    let signature = hex::decode_signature(&req.signature)?;

    // Get current timestamp
    let timestamp = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map_err(|e| ApiError::Serialization(format!("Failed to get timestamp: {}", e)))?
        .as_secs();

    Ok(Vote {
        assertion_hash,
        agent_id,
        weight: req.weight,
        signature,
        timestamp,
        source_url: req.source_url,
        observed_context,
    })
}
