//! Handler for constraints (agent pre-flight check) queries.

use axum::{
    extract::{Query as AxumQuery, State},
    Json,
};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::instrument;

use crate::{
    dto::{ConstraintEntryDto, ConstraintsQueryParams, ConstraintsResponse, ErrorResponse},
    error::Result,
    state::AppState,
};

use stemedb_core::types::Assertion;
use stemedb_lens::ConstraintsLens;
use stemedb_query::Query;

/// Query for constraint analysis using ConstraintsLens.
///
/// This endpoint provides pre-flight constraint checking for AI agents,
/// categorizing assertions by predicate pattern into must_use, forbidden, and prefer.
///
/// # Predicate Patterns
///
/// - `must_use:*` → Required, non-negotiable constraints
/// - `forbidden:*` → Explicitly banned items
/// - `prefer:*` → Recommended but optional
///
/// # Response
///
/// Returns a `ConstraintsResponse` with:
/// - `must_use`: Required constraints sorted by confidence
/// - `forbidden`: Banned items sorted by confidence
/// - `prefer`: Recommendations sorted by confidence
///
/// # Example
///
/// ```ignore
/// GET /v1/constraints?subject=project_alpha
///
/// {
///   "subject": "project_alpha",
///   "must_use": [
///     { "category": "http_client", "value": {"type": "Text", "value": "axios"}, "confidence": 0.95 }
///   ],
///   "forbidden": [
///     { "category": "http_client", "value": {"type": "Text", "value": "requests"}, "confidence": 0.9 }
///   ],
///   "prefer": [
///     { "category": "language", "value": {"type": "Text", "value": "typescript"}, "confidence": 0.8 }
///   ]
/// }
/// ```
#[utoipa::path(
    get,
    path = "/v1/constraints",
    params(ConstraintsQueryParams),
    responses(
        (status = 200, description = "Constraint analysis successful", body = ConstraintsResponse),
        (status = 400, description = "Invalid request", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "query"
)]
#[instrument(skip(state), fields(subject = %params.subject))]
pub async fn constraints_query(
    State(state): State<AppState>,
    AxumQuery(params): AxumQuery<ConstraintsQueryParams>,
) -> Result<Json<ConstraintsResponse>> {
    // Build query for all assertions with this subject
    // We need ALL predicates, not just one specific one
    let query = Query::builder().subject(&params.subject).build();

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

    // Apply ConstraintsLens to categorize
    let lens = ConstraintsLens;
    let constraint_set = lens.resolve_constraints(&result.assertions);

    // Get current timestamp
    let computed_at =
        SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);

    // Convert to DTOs
    let must_use = constraint_set
        .must_use
        .into_iter()
        .map(|a| assertion_to_constraint_entry(a, "must_use:"))
        .collect();

    let forbidden = constraint_set
        .forbidden
        .into_iter()
        .map(|a| assertion_to_constraint_entry(a, "forbidden:"))
        .collect();

    let prefer = constraint_set
        .prefer
        .into_iter()
        .map(|a| assertion_to_constraint_entry(a, "prefer:"))
        .collect();

    Ok(Json(ConstraintsResponse {
        subject: params.subject,
        must_use,
        forbidden,
        prefer,
        candidates_count: constraint_set.candidates_count,
        conflict_score: constraint_set.conflict_score,
        computed_at,
    }))
}

/// Convert an assertion to a ConstraintEntryDto.
///
/// Extracts the category from the predicate by removing the prefix.
fn assertion_to_constraint_entry(assertion: Assertion, prefix: &str) -> ConstraintEntryDto {
    // Extract category from predicate (e.g., "must_use:http_client" -> "http_client")
    let category =
        assertion.predicate.strip_prefix(prefix).unwrap_or(&assertion.predicate).to_string();

    // Compute assertion hash
    let hash = stemedb_core::serde::serialize(&assertion)
        .map(|bytes| hex::encode(blake3::hash(&bytes).as_bytes()))
        .unwrap_or_default();

    ConstraintEntryDto {
        category,
        value: assertion.object.into(),
        confidence: assertion.confidence,
        assertion_hash: hash,
        timestamp: assertion.timestamp,
        source_class: assertion.source_class.into(),
    }
}

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