//! Semantic search and retrieval module

use serde::{Deserialize, Serialize};

use crate::config::RetrievalConfig;
use crate::db::DbConnection;
use crate::embeddings::EmbeddingEngine;
use crate::error::Result;

/// Search result with document and similarity score
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
    /// Document ID
    pub doc_id: String,

    /// Source path
    pub source_path: String,

    /// Document type
    pub doc_type: String,

    /// Content snippet
    pub content: String,

    /// Similarity score (0.0-1.0)
    pub similarity: f32,

    /// Metadata
    pub metadata: std::collections::HashMap<String, String>,
}

/// Retrieval engine for semantic search
pub struct RetrieverEngine {
    config: RetrievalConfig,
    db: DbConnection,
    embedding_engine: EmbeddingEngine,
}

impl RetrieverEngine {
    /// Create a new retriever engine with database connection
    pub async fn new(
        config: RetrievalConfig,
        db: DbConnection,
        embedding_engine: EmbeddingEngine,
    ) -> Result<Self> {
        Ok(Self {
            config,
            db,
            embedding_engine,
        })
    }

    /// Search for documents similar to query
    pub async fn search(&self, query: &str, top_k: Option<usize>) -> Result<Vec<SearchResult>> {
        // 1. Embed the query
        let query_chunks = vec![crate::embeddings::DocumentChunk {
            id: "query".to_string(),
            source_path: "query".to_string(),
            doc_type: "query".to_string(),
            content: query.to_string(),
            category: None,
            metadata: std::collections::HashMap::new(),
        }];

        let embedded_query = self.embedding_engine.embed_batch(&query_chunks).await?;
        let query_embedding = embedded_query.first().ok_or_else(|| {
            crate::error::RagError::InvalidInput("Failed to embed query".to_string())
        })?;

        let k = top_k.unwrap_or(self.config.top_k);

        // 2. Search database
        let db_results = self
            .db
            .search_similar(
                &query_embedding.embedding,
                k,
                self.config.similarity_threshold,
            )
            .await?;

        // 3. Convert to SearchResult
        let results: Vec<SearchResult> = db_results
            .into_iter()
            .map(|doc| SearchResult {
                doc_id: doc.id,
                source_path: doc.source_path,
                doc_type: doc.doc_type,
                content: doc.content,
                similarity: 0.8, // Placeholder - actual similarity would come from DB
                metadata: doc.metadata,
            })
            .collect();

        tracing::info!("Search found {} results for query", results.len());
        Ok(results)
    }

    /// Hybrid search (vector + keyword)
    pub async fn hybrid_search(
        &self,
        query: &str,
        top_k: Option<usize>,
    ) -> Result<Vec<SearchResult>> {
        // For now, just do vector search (hybrid would combine with FTS)
        // TODO: Add full-text search component for true hybrid
        self.search(query, top_k).await
    }

    /// Search with filters (document type, category, etc.)
    pub async fn filtered_search(
        &self,
        query: &str,
        filters: SearchFilters,
        top_k: Option<usize>,
    ) -> Result<Vec<SearchResult>> {
        // Do semantic search first
        let mut results = self.search(query, top_k).await?;

        // Apply filters to results
        if let Some(doc_types) = &filters.doc_types {
            if !doc_types.is_empty() {
                results.retain(|r| doc_types.contains(&r.doc_type));
            }
        }

        if let Some(categories) = &filters.categories {
            if !categories.is_empty() {
                results.retain(|r| {
                    let category = r.metadata.get("category").map(|s| s.as_str()).unwrap_or("");
                    categories.iter().any(|c| c.as_str() == category)
                });
            }
        }

        Ok(results)
    }
}

/// Search filters for advanced queries
#[derive(Debug, Clone, Default)]
pub struct SearchFilters {
    /// Filter by document type
    pub doc_types: Option<Vec<String>>,

    /// Filter by category
    pub categories: Option<Vec<String>>,

    /// Filter by source path pattern
    pub source_pattern: Option<String>,

    /// Minimum similarity threshold
    pub min_similarity: Option<f32>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_search_filters_default() {
        let filters = SearchFilters::default();
        assert!(filters.doc_types.is_none());
        assert!(filters.categories.is_none());
    }
}
