use compliance_core::error::CoreError;
use compliance_core::models::graph::CodeNode;
use tantivy::collector::TopDocs;
use tantivy::query::QueryParser;
use tantivy::schema::{Schema, Value, STORED, TEXT};
use tantivy::{doc, Index, IndexWriter, ReloadPolicy};
use tracing::info;

/// BM25 text search index over code symbols
pub struct SymbolIndex {
    index: Index,
    #[allow(dead_code)]
    schema: Schema,
    qualified_name_field: tantivy::schema::Field,
    name_field: tantivy::schema::Field,
    kind_field: tantivy::schema::Field,
    file_path_field: tantivy::schema::Field,
    language_field: tantivy::schema::Field,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct SearchResult {
    pub qualified_name: String,
    pub name: String,
    pub kind: String,
    pub file_path: String,
    pub language: String,
    pub score: f32,
}

impl SymbolIndex {
    /// Create a new in-memory symbol index
    pub fn new() -> Result<Self, CoreError> {
        let mut schema_builder = Schema::builder();
        let qualified_name_field = schema_builder.add_text_field("qualified_name", TEXT | STORED);
        let name_field = schema_builder.add_text_field("name", TEXT | STORED);
        let kind_field = schema_builder.add_text_field("kind", TEXT | STORED);
        let file_path_field = schema_builder.add_text_field("file_path", TEXT | STORED);
        let language_field = schema_builder.add_text_field("language", TEXT | STORED);
        let schema = schema_builder.build();

        let index = Index::create_in_ram(schema.clone());

        Ok(Self {
            index,
            schema,
            qualified_name_field,
            name_field,
            kind_field,
            file_path_field,
            language_field,
        })
    }

    /// Index a set of code nodes
    pub fn index_nodes(&self, nodes: &[CodeNode]) -> Result<(), CoreError> {
        let mut writer: IndexWriter = self
            .index
            .writer(50_000_000)
            .map_err(|e| CoreError::Graph(format!("Failed to create index writer: {e}")))?;

        for node in nodes {
            writer
                .add_document(doc!(
                    self.qualified_name_field => node.qualified_name.as_str(),
                    self.name_field => node.name.as_str(),
                    self.kind_field => node.kind.to_string(),
                    self.file_path_field => node.file_path.as_str(),
                    self.language_field => node.language.as_str(),
                ))
                .map_err(|e| CoreError::Graph(format!("Failed to add document: {e}")))?;
        }

        writer
            .commit()
            .map_err(|e| CoreError::Graph(format!("Failed to commit index: {e}")))?;

        info!(nodes = nodes.len(), "Symbol index built");
        Ok(())
    }

    /// Search for symbols matching a query
    pub fn search(&self, query_str: &str, limit: usize) -> Result<Vec<SearchResult>, CoreError> {
        let reader = self
            .index
            .reader_builder()
            .reload_policy(ReloadPolicy::Manual)
            .try_into()
            .map_err(|e| CoreError::Graph(format!("Failed to create reader: {e}")))?;

        let searcher = reader.searcher();
        let query_parser = QueryParser::for_index(
            &self.index,
            vec![self.name_field, self.qualified_name_field],
        );

        let query = query_parser
            .parse_query(query_str)
            .map_err(|e| CoreError::Graph(format!("Failed to parse query: {e}")))?;

        let top_docs = searcher
            .search(&query, &TopDocs::with_limit(limit))
            .map_err(|e| CoreError::Graph(format!("Search failed: {e}")))?;

        let mut results = Vec::new();
        for (score, doc_address) in top_docs {
            let doc: tantivy::TantivyDocument = searcher
                .doc(doc_address)
                .map_err(|e| CoreError::Graph(format!("Failed to retrieve doc: {e}")))?;

            let get_field = |field: tantivy::schema::Field| -> String {
                doc.get_first(field)
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string()
            };

            results.push(SearchResult {
                qualified_name: get_field(self.qualified_name_field),
                name: get_field(self.name_field),
                kind: get_field(self.kind_field),
                file_path: get_field(self.file_path_field),
                language: get_field(self.language_field),
                score,
            });
        }

        Ok(results)
    }
}
