//! Full-text indexing with tantivy for keyword search
//!
//! Provides inverted index-based full-text search capabilities
//! complementing semantic search with keyword matching.

use crate::error::{ErrorWrapper, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Full-text search result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
    /// Document identifier
    pub doc_id: String,
    /// Document content
    pub content: String,
    /// Relevance score
    pub score: f32,
    /// Metadata
    pub metadata: HashMap<String, String>,
}

/// Document for indexing
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Document {
    /// Unique document identifier
    pub id: String,
    /// Main content to index
    pub content: String,
    /// Additional fields
    pub fields: HashMap<String, String>,
}

impl Document {
    /// Create a new document
    pub fn new(id: String, content: String) -> Self {
        Document {
            id,
            content,
            fields: HashMap::new(),
        }
    }

    /// Add a field to the document
    pub fn with_field(mut self, key: String, value: String) -> Self {
        self.fields.insert(key, value);
        self
    }
}

/// Full-text indexer
#[cfg(feature = "ai_backend")]
#[derive(Serialize, Deserialize, Default)]
pub struct FullTextIndexer {
    documents: HashMap<String, Document>,
}

#[cfg(feature = "ai_backend")]
impl FullTextIndexer {
    /// Create a new indexer
    pub fn new() -> Self {
        Self::default()
    }

    /// Index a document
    pub fn index(&mut self, doc: Document) -> Result<()> {
        if doc.id.is_empty() {
            return Err(ErrorWrapper::validation_failed(
                "Document ID cannot be empty",
            ));
        }
        if doc.content.is_empty() {
            return Err(ErrorWrapper::validation_failed(
                "Document content cannot be empty",
            ));
        }

        self.documents.insert(doc.id.clone(), doc);
        Ok(())
    }

    /// Search documents by keyword
    pub fn search(&self, query: &str, limit: usize) -> Result<Vec<SearchResult>> {
        if query.is_empty() {
            return Err(ErrorWrapper::validation_failed("Query cannot be empty"));
        }

        let query_lower = query.to_lowercase();
        let query_terms: Vec<_> = query_lower
            .split_whitespace()
            .filter(|t| !t.is_empty())
            .collect();

        if query_terms.is_empty() {
            return Ok(Vec::new());
        }

        // Simple word matching and scoring
        let mut results: Vec<SearchResult> = self
            .documents
            .values()
            .filter_map(|doc| {
                let content_lower = doc.content.to_lowercase();
                let mut score = 0.0;
                let mut matches = 0;

                // Count term matches
                for term in &query_terms {
                    let count = content_lower.matches(term).count();
                    if count > 0 {
                        matches += 1;
                        score += count as f32 * 10.0; // Boost matching terms
                    }
                }

                // Only return results with matches
                if matches > 0 {
                    Some(SearchResult {
                        doc_id: doc.id.clone(),
                        content: doc.content.clone(),
                        score,
                        metadata: doc.fields.clone(),
                    })
                } else {
                    None
                }
            })
            .collect();

        // Sort by relevance (descending)
        results.sort_by(|a, b| {
            b.score
                .partial_cmp(&a.score)
                .unwrap_or(std::cmp::Ordering::Equal)
        });

        // Return limited results
        Ok(results.into_iter().take(limit).collect())
    }

    /// Remove a document
    pub fn remove(&mut self, id: &str) -> Option<Document> {
        self.documents.remove(id)
    }

    /// Get document count
    pub fn doc_count(&self) -> usize {
        self.documents.len()
    }

    /// Clear all documents
    pub fn clear(&mut self) {
        self.documents.clear();
    }

    /// Get a document by ID
    pub fn get(&self, id: &str) -> Option<Document> {
        self.documents.get(id).cloned()
    }

    /// Serialize indexer for persistence
    pub fn serialize(&self) -> Result<Vec<u8>> {
        bincode::serialize(self).map_err(|e| {
            ErrorWrapper::validation_failed(format!("Failed to serialize indexer: {}", e))
        })
    }

    /// Deserialize indexer from persistence
    pub fn deserialize(data: &[u8]) -> Result<Self> {
        bincode::deserialize(data).map_err(|e| {
            ErrorWrapper::validation_failed(format!("Failed to deserialize indexer: {}", e))
        })
    }
}

#[cfg(not(feature = "ai_backend"))]
pub struct FullTextIndexer;

#[cfg(all(test, feature = "ai_backend"))]
mod tests {
    use super::*;

    #[test]
    fn test_indexer_index() {
        let mut indexer = FullTextIndexer::new();
        let doc = Document::new("doc1".to_string(), "hello world".to_string());
        assert!(indexer.index(doc).is_ok());
        assert_eq!(indexer.doc_count(), 1);
    }

    #[test]
    fn test_indexer_empty_id() {
        let mut indexer = FullTextIndexer::new();
        let doc = Document::new(String::new(), "content".to_string());
        assert!(indexer.index(doc).is_err());
    }

    #[test]
    fn test_indexer_empty_content() {
        let mut indexer = FullTextIndexer::new();
        let doc = Document::new("doc1".to_string(), String::new());
        assert!(indexer.index(doc).is_err());
    }

    #[test]
    fn test_indexer_search() {
        let mut indexer = FullTextIndexer::new();
        indexer
            .index(Document::new("doc1".to_string(), "hello world".to_string()))
            .unwrap();
        indexer
            .index(Document::new(
                "doc2".to_string(),
                "goodbye world".to_string(),
            ))
            .unwrap();

        let results = indexer.search("hello", 10).unwrap();
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].doc_id, "doc1");
    }

    #[test]
    fn test_indexer_search_multiple_terms() {
        let mut indexer = FullTextIndexer::new();
        indexer
            .index(Document::new(
                "doc1".to_string(),
                "hello world example".to_string(),
            ))
            .unwrap();
        indexer
            .index(Document::new(
                "doc2".to_string(),
                "goodbye world".to_string(),
            ))
            .unwrap();

        let results = indexer.search("hello world", 10).unwrap();
        assert!(!results.is_empty());
    }

    #[test]
    fn test_indexer_remove() {
        let mut indexer = FullTextIndexer::new();
        let doc = Document::new("doc1".to_string(), "hello world".to_string());
        indexer.index(doc).unwrap();
        assert_eq!(indexer.doc_count(), 1);

        let removed = indexer.remove("doc1");
        assert!(removed.is_some());
        assert_eq!(indexer.doc_count(), 0);
    }

    #[test]
    fn test_indexer_get() {
        let mut indexer = FullTextIndexer::new();
        let doc = Document::new("doc1".to_string(), "hello world".to_string());
        indexer.index(doc).unwrap();

        let retrieved = indexer.get("doc1");
        assert!(retrieved.is_some());
    }
}
