//! Corpus query handler for Aphoria.
//!
//! This endpoint returns authoritative assertions from RFC, OWASP, and Community
//! corpus sources - valuable best practices rather than statistical aggregates.

use axum::{extract::State, Json};
use stemedb_core::types::{ObjectValue, SourceClass};
use stemedb_storage::KVStore;
use tracing::instrument;

use crate::{
    dto::aphoria::{CorpusItemDto, GetCorpusRequest, GetCorpusResponse},
    error::{ApiError, Result},
    extractors::QsQuery,
    state::AppState,
};

/// Get corpus items from authoritative sources (RFC, OWASP, vendor, community patterns, and CLI-created items).
///
/// Unlike the `/patterns` endpoint (which returns statistical aggregates),
/// this endpoint returns valuable, curated best practices from trusted sources.
#[utoipa::path(
    get,
    path = "/v1/aphoria/corpus",
    params(
        ("sources" = Option<Vec<String>>, Query, description = "Filter by source schemes (rfc, owasp, community, vendor)"),
        ("category" = Option<String>, Query, description = "Filter by category (security, architecture, etc.)"),
        ("limit" = usize, Query, description = "Maximum items to return (default: 100)"),
        ("offset" = usize, Query, description = "Pagination offset (default: 0)"),
    ),
    responses(
        (status = 200, description = "Corpus items retrieved successfully", body = GetCorpusResponse),
        (status = 400, description = "Invalid request", body = crate::dto::ErrorResponse),
        (status = 500, description = "Internal server error", body = crate::dto::ErrorResponse),
    ),
    tag = "aphoria"
)]
#[instrument(skip_all, fields(sources = ?params.sources, limit = params.limit, offset = params.offset))]
pub async fn get_corpus(
    State(state): State<AppState>,
    QsQuery(params): QsQuery<GetCorpusRequest>,
) -> Result<Json<GetCorpusResponse>> {
    // Determine which source prefixes to query
    let source_prefixes = if let Some(sources) = &params.sources {
        sources
            .iter()
            .map(|s| match s.as_str() {
                "rfc" => "rfc://",
                "owasp" => "owasp://",
                "community" => "community://",
                "vendor" => "vendor://",
                _ => s.as_str(),
            })
            .collect::<Vec<_>>()
    } else {
        // Default: query all authoritative sources
        vec!["rfc://", "owasp://", "community://", "vendor://"]
    };

    let mut all_items = Vec::new();
    let mut sources_included = std::collections::HashSet::new();

    // Query each source prefix
    for prefix in source_prefixes {
        let prefix_key = format!("subject:{}", prefix);
        let pairs = state
            .corpus_store
            .scan_prefix(prefix_key.as_bytes())
            .await
            .map_err(|e| ApiError::Internal(format!("Failed to scan corpus: {}", e)))?;

        for (_key, value) in pairs {
            // Deserialize assertion
            let assertion: stemedb_core::types::Assertion =
                stemedb_core::serde::deserialize(&value).map_err(|e| {
                    ApiError::Internal(format!("Failed to deserialize assertion: {}", e))
                })?;

            // Extract metadata
            let metadata: Option<serde_json::Value> = assertion
                .source_metadata
                .as_ref()
                .and_then(|bytes| serde_json::from_slice(bytes).ok());

            let explanation = metadata
                .as_ref()
                .and_then(|m| m.get("description"))
                .and_then(|v| v.as_str())
                .unwrap_or("No description")
                .to_string();

            let category = metadata
                .as_ref()
                .and_then(|m| m.get("category"))
                .and_then(|v| v.as_str())
                .map(|s| s.to_string());

            let authority_source = metadata
                .as_ref()
                .and_then(|m| m.get("authority_source"))
                .and_then(|v| v.as_str())
                .or_else(|| {
                    // Fallback: extract from subject
                    if assertion.subject.starts_with("rfc://") {
                        Some("RFC")
                    } else if assertion.subject.starts_with("owasp://") {
                        Some("OWASP")
                    } else if assertion.subject.starts_with("community://") {
                        Some("Community")
                    } else if assertion.subject.starts_with("vendor://") {
                        Some("Vendor")
                    } else {
                        Some("Unknown")
                    }
                })
                .unwrap_or("Unknown")
                .to_string();

            // Filter by category if requested
            if let Some(ref filter_category) = params.category {
                if category.as_deref() != Some(filter_category.as_str()) {
                    continue;
                }
            }

            // Extract source scheme
            let source = if let Some(pos) = assertion.subject.find("://") {
                let scheme_end = assertion.subject[..pos].to_string();
                format!("{}://", scheme_end)
            } else {
                assertion.subject.clone()
            };

            sources_included.insert(source.clone());

            // Convert object to display value
            let value = match &assertion.object {
                ObjectValue::Boolean(b) => b.to_string(),
                ObjectValue::Number(n) => n.to_string(),
                ObjectValue::Text(s) => s.clone(),
                ObjectValue::Reference(r) => r.clone(),
            };

            // Map SourceClass to tier number
            let tier = match assertion.source_class {
                SourceClass::Regulatory => 0,
                SourceClass::Clinical => 1,
                SourceClass::Observational => 2,
                SourceClass::Expert => 3,
                SourceClass::Community => 4,
                SourceClass::Anecdotal => 5,
                SourceClass::TeamPolicy => 1, // Treat team policy similar to clinical
            };

            all_items.push(CorpusItemDto {
                subject: assertion.subject,
                predicate: assertion.predicate,
                value,
                source,
                tier,
                category,
                explanation,
                authority_source,
            });
        }
    }

    // Apply pagination
    let total_matching = all_items.len();
    let items: Vec<CorpusItemDto> =
        all_items.into_iter().skip(params.offset).take(params.limit).collect();

    let sources_included: Vec<String> = sources_included.into_iter().collect();

    tracing::info!(
        total_matching,
        returned = items.len(),
        sources = sources_included.len(),
        "Corpus query complete"
    );

    Ok(Json(GetCorpusResponse { items, total_matching, sources_included }))
}
