//! Handler for creating assertions.

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

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

use stemedb_core::types::{
    Assertion, HlcTimestamp, LifecycleStage, ObjectValue, SignatureEntry, SourceClass,
};
use stemedb_ingest::worker::serialize_assertion;

/// Create a new assertion in the knowledge graph.
///
/// This endpoint accepts an assertion DTO, validates confidence bounds (0.0-1.0),
/// converts hex-encoded fields to binary, serializes the assertion to WAL format,
/// and appends it to the journal. Returns the content-addressed BLAKE3 hash.
///
/// # Validation
/// - Confidence must be between 0.0 and 1.0
/// - At least one signature is required
/// - All hex fields must have correct lengths (32 bytes for hashes, 64 bytes for signatures)
#[utoipa::path(
    post,
    path = "/v1/assert",
    request_body = CreateAssertionRequest,
    responses(
        (status = 201, description = "Assertion created successfully", body = CreateResponse),
        (status = 400, description = "Invalid request", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "assertions"
)]
#[instrument(skip(state), fields(subject = %req.subject, predicate = %req.predicate))]
pub async fn create_assertion(
    State(state): State<AppState>,
    Json(req): Json<CreateAssertionRequest>,
) -> Result<(StatusCode, Json<CreateResponse>)> {
    // Convert DTO to internal Assertion type
    let assertion = dto_to_assertion(req)?;

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

    // Compute the content-addressed hash
    // This must match the hash computation in the ingest worker
    let serialized_assertion = stemedb_core::serde::serialize(&assertion)
        .map_err(|e| ApiError::Serialization(format!("Failed to serialize for hash: {}", e)))?;
    let hash = blake3::hash(&serialized_assertion);

    // 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 CreateAssertionRequest DTO to internal Assertion type.
fn dto_to_assertion(req: CreateAssertionRequest) -> Result<Assertion> {
    // Validate confidence bounds (0.0 to 1.0)
    if req.confidence < 0.0 || req.confidence > 1.0 {
        return Err(ApiError::InvalidRequest(format!(
            "Confidence must be between 0.0 and 1.0, got {}",
            req.confidence
        )));
    }

    // Decode hex fields using shared hex module
    let parent_hash = req.parent_hash.map(|h| hex::decode_hash_32(&h)).transpose()?;

    let source_hash = hex::decode_hash_32(&req.source_hash)?;

    let visual_hash = req.visual_hash.map(|h| hex::decode_hash_8(&h)).transpose()?;

    let epoch = req.epoch.map(|e| hex::decode_hash_32(&e)).transpose()?;

    // Convert signatures
    let signatures =
        req.signatures.into_iter().map(decode_signature).collect::<Result<Vec<_>>>()?;

    // Validate signatures are not empty
    if signatures.is_empty() {
        return Err(ApiError::InvalidRequest("At least one signature is required".to_string()));
    }

    // Use provided timestamp or generate a new one
    // IMPORTANT: For v2 signatures, the timestamp must match what was signed
    let timestamp = req.timestamp.unwrap_or_else(|| {
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_secs())
            .unwrap_or(0)
    });

    // Use provided HLC timestamp or default
    // IMPORTANT: For v2 signatures, the HLC timestamp must match what was signed
    let hlc_timestamp = match req.hlc_timestamp {
        Some(dto) => {
            let node_id = hex::decode_node_id(&dto.node_id)?;
            HlcTimestamp { time_ntp64: dto.time_ntp64, node_id }
        }
        None => HlcTimestamp::default(),
    };

    Ok(Assertion {
        subject: req.subject,
        predicate: req.predicate,
        object: ObjectValue::from(req.object),
        parent_hash,
        source_hash,
        source_class: req.source_class.map(Into::into).unwrap_or(SourceClass::Expert),
        visual_hash,
        epoch,
        source_metadata: req.source_metadata.map(|s| s.into_bytes()),
        lifecycle: req.lifecycle.map(Into::into).unwrap_or(LifecycleStage::Proposed),
        signatures,
        confidence: req.confidence,
        timestamp,
        hlc_timestamp,
        vector: req.vector,
    })
}

/// Decode a signature DTO.
fn decode_signature(dto: SignatureDto) -> Result<SignatureEntry> {
    let agent_id = hex::decode_hash_32(&dto.agent_id)?;
    let signature = hex::decode_signature(&dto.signature)?;

    Ok(SignatureEntry {
        agent_id,
        signature,
        timestamp: dto.timestamp,
        version: dto.version.unwrap_or(1),
    })
}
