//! Handler for layered consensus (per-tier resolution) queries.

use axum::{
    extract::{Query as AxumQuery, State},
    Json,
};
use tracing::instrument;

use crate::{
    dto::{
        AssertionResponse, ErrorResponse, LayeredQueryResponse, SkepticQueryParams, SourceClassDto,
        TierResolutionDto,
    },
    error::{ApiError, Result},
    state::AppState,
};

use stemedb_core::types::SourceClass;
use stemedb_lens::{LayeredConsensusLens, LayeredLens};
use stemedb_query::Query;

/// Query for per-tier consensus using LayeredConsensusLens.
///
/// This endpoint provides visibility into what each source class tier says,
/// enabling "What does Tier 0 (FDA) say? What does Tier 5 (Reddit) say?" queries.
///
/// # Response
///
/// Returns a `LayeredQueryResponse` with:
/// - `tiers`: Per-tier consensus results (only tiers with candidates)
/// - `overall_winner`: Winner from the highest-authority tier present
/// - `overall_conflict_score`: Cross-tier disagreement (0.0 = tiers agree, 1.0 = tiers disagree)
///
/// # Example
///
/// ```ignore
/// GET /v1/layered?subject=Semaglutide&predicate=muscle_effect
///
/// {
///   "subject": "Semaglutide",
///   "predicate": "muscle_effect",
///   "tiers": [
///     {
///       "tier": 1,
///       "source_class": "Clinical",
///       "winner": { ... },
///       "candidates_count": 12,
///       "conflict_score": 0.15,
///       "resolution_confidence": 0.85
///     },
///     {
///       "tier": 5,
///       "source_class": "Anecdotal",
///       "winner": { ... },
///       "candidates_count": 200,
///       "conflict_score": 0.45,
///       "resolution_confidence": 0.55
///     }
///   ],
///   "overall_winner": { ... },
///   "overall_conflict_score": 0.78,
///   "total_candidates": 212,
///   "lens_name": "LayeredConsensus"
/// }
/// ```
#[utoipa::path(
    get,
    path = "/v1/layered",
    params(SkepticQueryParams),
    responses(
        (status = 200, description = "Layered consensus successful", body = LayeredQueryResponse),
        (status = 404, description = "No assertions found for subject+predicate", body = ErrorResponse),
        (status = 400, description = "Invalid request", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "query"
)]
#[instrument(skip(state), fields(subject = %params.subject, predicate = %params.predicate))]
pub async fn layered_query(
    State(state): State<AppState>,
    AxumQuery(params): AxumQuery<SkepticQueryParams>,
) -> Result<Json<LayeredQueryResponse>> {
    let computed_at = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);

    // Build query for subject+predicate
    let query = Query::builder()
        .subject(params.subject.clone())
        .predicate(params.predicate.clone())
        .build();

    // Execute the query to get candidates
    let query_engine = state.query_engine();
    let result = query_engine.execute(&query).await?;

    // Return 404 if no assertions found
    if result.assertions.is_empty() {
        return Err(ApiError::NotFound(format!(
            "No assertions found for subject '{}' and predicate '{}'",
            params.subject, params.predicate
        )));
    }

    // Apply LayeredConsensusLens
    let lens = LayeredConsensusLens::new();
    let layered = lens.resolve_layered(&result.assertions);

    // Convert to DTOs
    let tiers: Vec<TierResolutionDto> = layered
        .tiers
        .into_iter()
        .map(|tr| {
            let winner = tr.winner.map(assertion_to_dto).transpose()?;
            Ok(TierResolutionDto {
                tier: tr.tier,
                source_class: source_class_to_dto(tr.source_class),
                winner,
                candidates_count: tr.candidates_count,
                conflict_score: tr.conflict_score,
                resolution_confidence: tr.resolution_confidence,
            })
        })
        .collect::<Result<Vec<_>>>()?;

    let overall_winner = layered.overall_winner.map(assertion_to_dto).transpose()?;

    Ok(Json(LayeredQueryResponse {
        subject: params.subject,
        predicate: params.predicate,
        tiers,
        overall_winner,
        overall_conflict_score: layered.overall_conflict_score,
        total_candidates: layered.total_candidates,
        computed_at,
        lens_name: "LayeredConsensus".to_string(),
    }))
}

/// Convert SourceClass to SourceClassDto.
fn source_class_to_dto(sc: SourceClass) -> SourceClassDto {
    match sc {
        SourceClass::Regulatory => SourceClassDto::Regulatory,
        SourceClass::Clinical => SourceClassDto::Clinical,
        SourceClass::Observational => SourceClassDto::Observational,
        SourceClass::Expert => SourceClassDto::Expert,
        SourceClass::Community => SourceClassDto::Community,
        SourceClass::Anecdotal => SourceClassDto::Anecdotal,
    }
}

/// Convert an internal Assertion to an AssertionResponse DTO.
fn assertion_to_dto(assertion: stemedb_core::types::Assertion) -> Result<AssertionResponse> {
    let serialized = stemedb_core::serde::serialize(&assertion)
        .map_err(|e| ApiError::Serialization(format!("Failed to serialize assertion: {}", e)))?;
    let hash = blake3::hash(&serialized);

    Ok(AssertionResponse {
        hash: hash.to_hex().to_string(),
        subject: assertion.subject,
        predicate: assertion.predicate,
        object: assertion.object.into(),
        parent_hash: assertion.parent_hash.map(hex::encode),
        source_hash: hex::encode(assertion.source_hash),
        source_class: assertion.source_class.into(),
        visual_hash: assertion.visual_hash.map(hex::encode),
        epoch: assertion.epoch.map(hex::encode),
        lifecycle: assertion.lifecycle.into(),
        signatures: assertion.signatures.into_iter().map(Into::into).collect(),
        confidence: assertion.confidence,
        timestamp: assertion.timestamp,
        vector: assertion.vector,
        source_metadata: assertion.source_metadata.and_then(|bytes| String::from_utf8(bytes).ok()),
        source_warning: None, // LayeredConsensus doesn't do source status enrichment
    })
}

#[cfg(test)]
mod tests {
    // Integration tests would go here
    // For now, the unit tests in stemedb-lens cover the core functionality
}
