//! IndexReader: open committed index, provide search snapshots.
//!
//! Reads all committed segments from storage and creates a `Searcher`
//! snapshot for querying.
//!
//! See [[architecture-query-execution]] and [[architecture-overview#Step 10]].

use luci_core::Result;
use luci_storage::Storage;

use crate::search::searcher::Searcher;
use crate::segment::reader::SegmentReader;

/// Opens an index for reading by loading all committed segments.
pub struct IndexReader {
    pub(crate) segments: Vec<SegmentReader>,
}

impl IndexReader {
    /// Open an index from storage, loading all committed segments.
    pub fn open(storage: &dyn Storage) -> Result<Self> {
        let mut segments = Vec::new();
        for entry in storage.segments() {
            let data = storage.read_segment(entry.segment_id)?;
            let reader = SegmentReader::open(data)?;
            segments.push(reader);
        }
        Ok(Self { segments })
    }

    /// Convert into a Searcher, consuming the reader.
    pub fn into_searcher(self) -> Searcher {
        Searcher::new(self.segments)
    }

    /// Number of loaded segments.
    pub fn num_segments(&self) -> usize {
        self.segments.len()
    }

    /// Total documents across all segments.
    pub fn total_docs(&self) -> u32 {
        self.segments.iter().map(|s| s.doc_count()).sum()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use luci_analysis::AnalyzerRegistry;
    use luci_core::FieldId;
    use luci_mapping::{FieldType, Mapping};
    use luci_storage::SingleFileDirectory;
    use crate::writer::IndexWriter;

    fn test_dir(name: &str) -> std::path::PathBuf {
        let dir = std::env::temp_dir().join(format!(
            "luci_reader_test_{}_{name}",
            std::process::id()
        ));
        let _ = std::fs::remove_dir_all(&dir);
        dir
    }

    fn cleanup(path: &std::path::Path) {
        let _ = std::fs::remove_dir_all(path);
    }

    #[test]
    fn open_and_search() {
        let path = test_dir("open_search");
        let storage = SingleFileDirectory::create(&path).unwrap();
        let schema = Mapping::builder()
            .field("title", FieldType::Text)
            .build();
        let mut writer = IndexWriter::new(storage, schema, AnalyzerRegistry::new());

        writer.add(serde_json::json!({"title": "hello world"})).unwrap();
        writer.add(serde_json::json!({"title": "hello luci"})).unwrap();
        writer.add(serde_json::json!({"title": "goodbye world"})).unwrap();
        writer.commit().unwrap();

        let storage = SingleFileDirectory::open(&path).unwrap();
        let reader = IndexReader::open(&storage).unwrap();
        assert_eq!(reader.total_docs(), 3);

        let searcher = reader.into_searcher();
        let results = searcher.search_term(FieldId::new(0), "hello", 10);
        assert_eq!(results.total_hits.value, 2);

        cleanup(&path);
    }

    #[test]
    fn multi_segment_reader() {
        let path = test_dir("multi_seg");
        let storage = SingleFileDirectory::create(&path).unwrap();
        let schema = Mapping::builder()
            .field("body", FieldType::Text)
            .build();
        let mut writer = IndexWriter::new(storage, schema, AnalyzerRegistry::new());

        writer.add(serde_json::json!({"body": "first segment doc"})).unwrap();
        writer.commit().unwrap();

        writer.add(serde_json::json!({"body": "second segment doc"})).unwrap();
        writer.commit().unwrap();

        let storage = SingleFileDirectory::open(&path).unwrap();
        let reader = IndexReader::open(&storage).unwrap();
        assert_eq!(reader.num_segments(), 2);
        assert_eq!(reader.total_docs(), 2);

        let searcher = reader.into_searcher();
        let results = searcher.search_term(FieldId::new(0), "segment", 10);
        assert_eq!(results.total_hits.value, 2);
        let results = searcher.search_term(FieldId::new(0), "first", 10);
        assert_eq!(results.total_hits.value, 1);

        cleanup(&path);
    }

    #[test]
    fn empty_index() {
        let path = test_dir("empty");
        let storage = SingleFileDirectory::create(&path).unwrap();
        let schema = Mapping::builder().field("x", FieldType::Text).build();
        let mut writer = IndexWriter::new(storage, schema, AnalyzerRegistry::new());
        writer.commit().unwrap();

        let storage = SingleFileDirectory::open(&path).unwrap();
        let reader = IndexReader::open(&storage).unwrap();
        assert_eq!(reader.num_segments(), 0);
        assert_eq!(reader.total_docs(), 0);

        let searcher = reader.into_searcher();
        let results = searcher.search_term(FieldId::new(0), "anything", 10);
        assert_eq!(results.total_hits.value, 0);

        cleanup(&path);
    }
}
