//! Metadata indexing with Roaring bitmaps for O(1) filter evaluation
//!
//! Qdrant-style payload indexes: each indexed field has an inverted index
//! mapping values to document IDs via Roaring bitmaps.

use ordered_float::OrderedFloat;
use roaring::RoaringBitmap;
use std::collections::{BTreeMap, HashMap};
use std::io::{self, Read, Write};

// Deserialization limits to prevent DoS from malformed files
const MAX_STRING_LEN: usize = 64 * 1024; // 64KB for field names/values
const MAX_BITMAP_LEN: usize = 16 * 1024 * 1024; // 16MB for bitmaps
const MAX_ENTRIES: usize = 100_000_000; // 100M entries
const MAX_FIELD_COUNT: usize = 10_000; // 10K fields

fn check_len(len: usize, max: usize, what: &str) -> io::Result<()> {
    if len > max {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("{what} too large: {len} > {max}"),
        ));
    }
    Ok(())
}

fn to_u32(len: usize, what: &str) -> io::Result<u32> {
    u32::try_from(len).map_err(|_| {
        io::Error::new(
            io::ErrorKind::InvalidData,
            format!("{what} exceeds u32::MAX: {len}"),
        )
    })
}

/// Keyword index - inverted index for string equality
#[derive(Debug, Clone, Default)]
pub struct KeywordIndex {
    /// term -> bitmap of doc IDs
    terms: HashMap<String, RoaringBitmap>,
}

impl KeywordIndex {
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a document with the given term
    pub fn insert(&mut self, doc_id: u32, term: &str) {
        self.terms
            .entry(term.to_string())
            .or_default()
            .insert(doc_id);
    }

    /// Remove a document from all terms
    pub fn remove(&mut self, doc_id: u32) {
        for bitmap in self.terms.values_mut() {
            bitmap.remove(doc_id);
        }
        // Clean up empty bitmaps to prevent memory leak
        self.terms.retain(|_, bitmap| !bitmap.is_empty());
    }

    /// Get documents matching a term
    pub fn get(&self, term: &str) -> Option<&RoaringBitmap> {
        self.terms.get(term)
    }

    /// Check if a document has a specific term
    #[inline]
    pub fn contains(&self, doc_id: u32, term: &str) -> bool {
        self.terms
            .get(term)
            .is_some_and(|bitmap| bitmap.contains(doc_id))
    }

    /// Get all terms
    pub fn terms(&self) -> impl Iterator<Item = &str> {
        self.terms.keys().map(std::string::String::as_str)
    }

    /// Serialize to bytes
    pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        // Write term count
        writer.write_all(&to_u32(self.terms.len(), "term count")?.to_le_bytes())?;

        for (term, bitmap) in &self.terms {
            // Write term (length-prefixed)
            writer.write_all(&to_u32(term.len(), "term length")?.to_le_bytes())?;
            writer.write_all(term.as_bytes())?;

            // Write bitmap (use native serialization)
            let mut bitmap_bytes = Vec::new();
            bitmap.serialize_into(&mut bitmap_bytes)?;
            writer.write_all(&to_u32(bitmap_bytes.len(), "bitmap length")?.to_le_bytes())?;
            writer.write_all(&bitmap_bytes)?;
        }

        Ok(())
    }

    /// Deserialize from bytes
    pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
        let mut len_buf = [0u8; 4];
        reader.read_exact(&mut len_buf)?;
        let term_count = u32::from_le_bytes(len_buf) as usize;
        check_len(term_count, MAX_ENTRIES, "term count")?;

        let mut terms = HashMap::with_capacity(term_count.min(1024));

        for _ in 0..term_count {
            // Read term
            reader.read_exact(&mut len_buf)?;
            let term_len = u32::from_le_bytes(len_buf) as usize;
            check_len(term_len, MAX_STRING_LEN, "term length")?;
            let mut term_buf = vec![0u8; term_len];
            reader.read_exact(&mut term_buf)?;
            let term = String::from_utf8(term_buf)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

            // Read bitmap
            reader.read_exact(&mut len_buf)?;
            let bitmap_len = u32::from_le_bytes(len_buf) as usize;
            check_len(bitmap_len, MAX_BITMAP_LEN, "bitmap length")?;
            let mut bitmap_buf = vec![0u8; bitmap_len];
            reader.read_exact(&mut bitmap_buf)?;
            let bitmap = RoaringBitmap::deserialize_from(&bitmap_buf[..])
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

            terms.insert(term, bitmap);
        }

        Ok(Self { terms })
    }
}

/// Boolean index - two bitmaps for true/false
#[derive(Debug, Clone, Default)]
pub struct BooleanIndex {
    true_docs: RoaringBitmap,
    false_docs: RoaringBitmap,
}

impl BooleanIndex {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert(&mut self, doc_id: u32, value: bool) {
        if value {
            self.true_docs.insert(doc_id);
            self.false_docs.remove(doc_id);
        } else {
            self.false_docs.insert(doc_id);
            self.true_docs.remove(doc_id);
        }
    }

    pub fn remove(&mut self, doc_id: u32) {
        self.true_docs.remove(doc_id);
        self.false_docs.remove(doc_id);
    }

    #[inline]
    pub fn matches(&self, doc_id: u32, value: bool) -> bool {
        if value {
            self.true_docs.contains(doc_id)
        } else {
            self.false_docs.contains(doc_id)
        }
    }

    pub fn get_true(&self) -> &RoaringBitmap {
        &self.true_docs
    }

    pub fn get_false(&self) -> &RoaringBitmap {
        &self.false_docs
    }

    /// Serialize to bytes
    pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        // Write true_docs bitmap
        let mut true_bytes = Vec::new();
        self.true_docs.serialize_into(&mut true_bytes)?;
        writer.write_all(&to_u32(true_bytes.len(), "true bitmap")?.to_le_bytes())?;
        writer.write_all(&true_bytes)?;

        // Write false_docs bitmap
        let mut false_bytes = Vec::new();
        self.false_docs.serialize_into(&mut false_bytes)?;
        writer.write_all(&to_u32(false_bytes.len(), "false bitmap")?.to_le_bytes())?;
        writer.write_all(&false_bytes)?;

        Ok(())
    }

    /// Deserialize from bytes
    pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
        let mut len_buf = [0u8; 4];

        // Read true_docs bitmap
        reader.read_exact(&mut len_buf)?;
        let true_len = u32::from_le_bytes(len_buf) as usize;
        check_len(true_len, MAX_BITMAP_LEN, "true bitmap")?;
        let mut true_buf = vec![0u8; true_len];
        reader.read_exact(&mut true_buf)?;
        let true_docs = RoaringBitmap::deserialize_from(&true_buf[..])
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

        // Read false_docs bitmap
        reader.read_exact(&mut len_buf)?;
        let false_len = u32::from_le_bytes(len_buf) as usize;
        check_len(false_len, MAX_BITMAP_LEN, "false bitmap")?;
        let mut false_buf = vec![0u8; false_len];
        reader.read_exact(&mut false_buf)?;
        let false_docs = RoaringBitmap::deserialize_from(&false_buf[..])
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

        Ok(Self {
            true_docs,
            false_docs,
        })
    }
}

/// Numeric index for integer/float range queries using BTreeMap
///
/// Each distinct value maps to a RoaringBitmap of document IDs.
/// BTreeMap provides O(log n) equality lookups and O(log n + m) range queries.
#[derive(Debug, Clone, Default)]
pub struct NumericIndex {
    entries: BTreeMap<OrderedFloat<f64>, RoaringBitmap>,
}

impl NumericIndex {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert(&mut self, doc_id: u32, value: f64) {
        if value.is_nan() {
            return;
        }
        self.entries
            .entry(OrderedFloat(value))
            .or_default()
            .insert(doc_id);
    }

    pub fn remove(&mut self, doc_id: u32) {
        self.entries.retain(|_, bitmap| {
            bitmap.remove(doc_id);
            !bitmap.is_empty()
        });
    }

    /// Get documents where value == target
    pub fn get_eq(&self, value: f64) -> Option<&RoaringBitmap> {
        self.entries.get(&OrderedFloat(value))
    }

    /// Get documents where value is in range [min, max]
    pub fn get_range(&self, min: f64, max: f64) -> RoaringBitmap {
        let mut result = RoaringBitmap::new();
        for (_, bitmap) in self.entries.range(OrderedFloat(min)..=OrderedFloat(max)) {
            result |= bitmap;
        }
        result
    }

    /// Iterate over entries in the given range (for bitmap evaluation)
    pub fn entries_range<R>(
        &self,
        range: R,
    ) -> impl Iterator<Item = (&OrderedFloat<f64>, &RoaringBitmap)>
    where
        R: std::ops::RangeBounds<OrderedFloat<f64>>,
    {
        self.entries.range(range)
    }

    /// Check if document matches value
    #[inline]
    pub fn matches_eq(&self, doc_id: u32, value: f64) -> bool {
        self.entries
            .get(&OrderedFloat(value))
            .is_some_and(|bitmap| bitmap.contains(doc_id))
    }

    /// Check if document is in range [min, max] (inclusive)
    #[inline]
    pub fn matches_range(&self, doc_id: u32, min: f64, max: f64) -> bool {
        self.entries
            .range(OrderedFloat(min)..=OrderedFloat(max))
            .any(|(_, bitmap)| bitmap.contains(doc_id))
    }

    /// Check if document value > threshold (strict greater than)
    #[inline]
    pub fn matches_gt(&self, doc_id: u32, threshold: f64) -> bool {
        use std::ops::Bound;
        self.entries
            .range((Bound::Excluded(OrderedFloat(threshold)), Bound::Unbounded))
            .any(|(_, bitmap)| bitmap.contains(doc_id))
    }

    /// Check if document value < threshold (strict less than)
    #[inline]
    pub fn matches_lt(&self, doc_id: u32, threshold: f64) -> bool {
        self.entries
            .range(..OrderedFloat(threshold))
            .any(|(_, bitmap)| bitmap.contains(doc_id))
    }

    /// Serialize to bytes
    pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        writer.write_all(&to_u32(self.entries.len(), "entries count")?.to_le_bytes())?;
        for (value, bitmap) in &self.entries {
            writer.write_all(&value.into_inner().to_le_bytes())?;
            let mut bitmap_bytes = Vec::new();
            bitmap.serialize_into(&mut bitmap_bytes)?;
            writer.write_all(&to_u32(bitmap_bytes.len(), "bitmap length")?.to_le_bytes())?;
            writer.write_all(&bitmap_bytes)?;
        }
        Ok(())
    }

    /// Deserialize from bytes
    pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
        let mut buf4 = [0u8; 4];
        let mut buf8 = [0u8; 8];

        reader.read_exact(&mut buf4)?;
        let entries_len = u32::from_le_bytes(buf4) as usize;
        check_len(entries_len, MAX_ENTRIES, "entries count")?;
        let mut entries = BTreeMap::new();
        for _ in 0..entries_len {
            reader.read_exact(&mut buf8)?;
            let value = f64::from_le_bytes(buf8);
            reader.read_exact(&mut buf4)?;
            let bitmap_len = u32::from_le_bytes(buf4) as usize;
            check_len(bitmap_len, MAX_BITMAP_LEN, "bitmap length")?;
            let mut bitmap_buf = vec![0u8; bitmap_len];
            reader.read_exact(&mut bitmap_buf)?;
            let bitmap = RoaringBitmap::deserialize_from(&bitmap_buf[..])
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
            entries.insert(OrderedFloat(value), bitmap);
        }

        Ok(Self { entries })
    }
}

/// Field index - wraps different index types
#[derive(Debug, Clone)]
pub enum FieldIndex {
    Keyword(KeywordIndex),
    Boolean(BooleanIndex),
    Numeric(NumericIndex),
}

impl FieldIndex {
    #[must_use]
    pub fn keyword() -> Self {
        Self::Keyword(KeywordIndex::new())
    }

    #[must_use]
    pub fn boolean() -> Self {
        Self::Boolean(BooleanIndex::new())
    }

    #[must_use]
    pub fn numeric() -> Self {
        Self::Numeric(NumericIndex::new())
    }

    /// Serialize to bytes (type tag + index data)
    pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        match self {
            Self::Keyword(idx) => {
                writer.write_all(&[0u8])?;
                idx.serialize(writer)
            }
            Self::Boolean(idx) => {
                writer.write_all(&[1u8])?;
                idx.serialize(writer)
            }
            Self::Numeric(idx) => {
                writer.write_all(&[2u8])?;
                idx.serialize(writer)
            }
        }
    }

    /// Deserialize from bytes
    pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
        let mut type_tag = [0u8; 1];
        reader.read_exact(&mut type_tag)?;

        match type_tag[0] {
            0 => Ok(Self::Keyword(KeywordIndex::deserialize(reader)?)),
            1 => Ok(Self::Boolean(BooleanIndex::deserialize(reader)?)),
            2 => Ok(Self::Numeric(NumericIndex::deserialize(reader)?)),
            _ => Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("Unknown field index type: {}", type_tag[0]),
            )),
        }
    }
}

/// Metadata index - collection of field indexes
#[derive(Debug, Clone, Default)]
pub struct MetadataIndex {
    fields: HashMap<String, FieldIndex>,
}

impl MetadataIndex {
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Create or get a keyword index for a field.
    /// Returns None if the field exists with a different type.
    pub fn keyword_index(&mut self, field: &str) -> Option<&mut KeywordIndex> {
        let index = self
            .fields
            .entry(field.to_string())
            .or_insert_with(FieldIndex::keyword);
        match index {
            FieldIndex::Keyword(idx) => Some(idx),
            _ => None, // Field exists with different type
        }
    }

    /// Create or get a boolean index for a field.
    /// Returns None if the field exists with a different type.
    pub fn boolean_index(&mut self, field: &str) -> Option<&mut BooleanIndex> {
        let index = self
            .fields
            .entry(field.to_string())
            .or_insert_with(FieldIndex::boolean);
        match index {
            FieldIndex::Boolean(idx) => Some(idx),
            _ => None, // Field exists with different type
        }
    }

    /// Create or get a numeric index for a field.
    /// Returns None if the field exists with a different type.
    pub fn numeric_index(&mut self, field: &str) -> Option<&mut NumericIndex> {
        let index = self
            .fields
            .entry(field.to_string())
            .or_insert_with(FieldIndex::numeric);
        match index {
            FieldIndex::Numeric(idx) => Some(idx),
            _ => None, // Field exists with different type
        }
    }

    /// Get a field index
    #[must_use]
    pub fn get(&self, field: &str) -> Option<&FieldIndex> {
        self.fields.get(field)
    }

    /// Index a JSON metadata object.
    /// Silently skips fields with inconsistent types across documents.
    pub fn index_json(&mut self, doc_id: u32, metadata: &serde_json::Value) {
        if let serde_json::Value::Object(map) = metadata {
            for (key, value) in map {
                match value {
                    serde_json::Value::String(s) => {
                        if let Some(idx) = self.keyword_index(key) {
                            idx.insert(doc_id, s);
                        }
                    }
                    serde_json::Value::Bool(b) => {
                        if let Some(idx) = self.boolean_index(key) {
                            idx.insert(doc_id, *b);
                        }
                    }
                    serde_json::Value::Number(n) => {
                        if let Some(f) = n.as_f64()
                            && let Some(idx) = self.numeric_index(key)
                        {
                            idx.insert(doc_id, f);
                        }
                    }
                    _ => {} // Skip arrays and nested objects
                }
            }
        }
    }

    /// Remove a document from all indexes
    pub fn remove(&mut self, doc_id: u32) {
        for index in self.fields.values_mut() {
            match index {
                FieldIndex::Keyword(idx) => idx.remove(doc_id),
                FieldIndex::Boolean(idx) => idx.remove(doc_id),
                FieldIndex::Numeric(idx) => idx.remove(doc_id),
            }
        }
    }

    /// Evaluate a filter expression (returns true if matches)
    #[inline]
    #[must_use]
    pub fn matches(&self, doc_id: u32, filter: &Filter) -> bool {
        match filter {
            Filter::Eq(field, value) => self.matches_eq(doc_id, field, value),
            Filter::Ne(field, value) => !self.matches_eq(doc_id, field, value),
            Filter::Gt(field, value) => self.matches_gt(doc_id, field, *value),
            Filter::Gte(field, value) => self.matches_gte(doc_id, field, *value),
            Filter::Lt(field, value) => self.matches_lt(doc_id, field, *value),
            Filter::Lte(field, value) => self.matches_lte(doc_id, field, *value),
            Filter::In(field, values) => values.iter().any(|v| self.matches_eq(doc_id, field, v)),
            Filter::And(filters) => filters.iter().all(|f| self.matches(doc_id, f)),
            Filter::Or(filters) => filters.iter().any(|f| self.matches(doc_id, f)),
            Filter::Not(inner) => !self.matches(doc_id, inner),
        }
    }

    fn matches_eq(&self, doc_id: u32, field: &str, value: &FilterValue) -> bool {
        match (self.get(field), value) {
            (Some(FieldIndex::Keyword(idx)), FilterValue::String(s)) => idx.contains(doc_id, s),
            (Some(FieldIndex::Boolean(idx)), FilterValue::Bool(b)) => idx.matches(doc_id, *b),
            (Some(FieldIndex::Numeric(idx)), FilterValue::Number(n)) => idx.matches_eq(doc_id, *n),
            _ => false,
        }
    }

    fn matches_gt(&self, doc_id: u32, field: &str, value: f64) -> bool {
        match self.get(field) {
            Some(FieldIndex::Numeric(idx)) => idx.matches_gt(doc_id, value),
            _ => false,
        }
    }

    fn matches_gte(&self, doc_id: u32, field: &str, value: f64) -> bool {
        match self.get(field) {
            Some(FieldIndex::Numeric(idx)) => idx.matches_range(doc_id, value, f64::INFINITY),
            _ => false,
        }
    }

    fn matches_lt(&self, doc_id: u32, field: &str, value: f64) -> bool {
        match self.get(field) {
            Some(FieldIndex::Numeric(idx)) => idx.matches_lt(doc_id, value),
            _ => false,
        }
    }

    fn matches_lte(&self, doc_id: u32, field: &str, value: f64) -> bool {
        match self.get(field) {
            Some(FieldIndex::Numeric(idx)) => idx.matches_range(doc_id, f64::NEG_INFINITY, value),
            _ => false,
        }
    }

    /// Serialize to bytes
    pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        // Write field count
        writer.write_all(&to_u32(self.fields.len(), "field count")?.to_le_bytes())?;

        // Write each field
        for (name, index) in &self.fields {
            // Write field name (length-prefixed)
            writer.write_all(&to_u32(name.len(), "field name")?.to_le_bytes())?;
            writer.write_all(name.as_bytes())?;

            // Write field index
            index.serialize(writer)?;
        }

        Ok(())
    }

    /// Serialize to bytes (convenience)
    pub fn to_bytes(&self) -> io::Result<Vec<u8>> {
        let mut buf = Vec::new();
        self.serialize(&mut buf)?;
        Ok(buf)
    }

    /// Deserialize from bytes
    pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
        let mut len_buf = [0u8; 4];
        reader.read_exact(&mut len_buf)?;
        let field_count = u32::from_le_bytes(len_buf) as usize;
        check_len(field_count, MAX_FIELD_COUNT, "field count")?;

        let mut fields = HashMap::with_capacity(field_count);

        for _ in 0..field_count {
            // Read field name
            reader.read_exact(&mut len_buf)?;
            let name_len = u32::from_le_bytes(len_buf) as usize;
            check_len(name_len, MAX_STRING_LEN, "field name length")?;
            let mut name_buf = vec![0u8; name_len];
            reader.read_exact(&mut name_buf)?;
            let name = String::from_utf8(name_buf)
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

            // Read field index
            let index = FieldIndex::deserialize(reader)?;

            fields.insert(name, index);
        }

        Ok(Self { fields })
    }

    /// Deserialize from bytes (convenience)
    pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
        let mut reader = std::io::Cursor::new(bytes);
        Self::deserialize(&mut reader)
    }
}

/// Filter value types
#[derive(Debug, Clone)]
pub enum FilterValue {
    String(String),
    Number(f64),
    Bool(bool),
}

/// Filter expressions
#[derive(Debug, Clone)]
pub enum Filter {
    Eq(String, FilterValue),
    Ne(String, FilterValue),
    Gt(String, f64),
    Gte(String, f64),
    Lt(String, f64),
    Lte(String, f64),
    In(String, Vec<FilterValue>),
    And(Vec<Filter>),
    Or(Vec<Filter>),
    Not(Box<Filter>),
}

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

    #[test]
    fn test_keyword_index() {
        let mut idx = KeywordIndex::new();
        idx.insert(1, "apple");
        idx.insert(2, "apple");
        idx.insert(3, "banana");

        assert!(idx.contains(1, "apple"));
        assert!(idx.contains(2, "apple"));
        assert!(!idx.contains(3, "apple"));
        assert!(idx.contains(3, "banana"));

        let bitmap = idx.get("apple").unwrap();
        assert_eq!(bitmap.len(), 2);
    }

    #[test]
    fn test_boolean_index() {
        let mut idx = BooleanIndex::new();
        idx.insert(1, true);
        idx.insert(2, false);
        idx.insert(3, true);

        assert!(idx.matches(1, true));
        assert!(!idx.matches(1, false));
        assert!(idx.matches(2, false));
        assert!(idx.matches(3, true));
    }

    #[test]
    fn test_numeric_index() {
        let mut idx = NumericIndex::new();
        idx.insert(1, 10.0);
        idx.insert(2, 20.0);
        idx.insert(3, 30.0);

        // Equality
        assert!(idx.matches_eq(1, 10.0));
        assert!(!idx.matches_eq(1, 20.0));

        // Range
        let range = idx.get_range(15.0, 25.0);
        assert!(range.contains(2));
        assert!(!range.contains(1));
        assert!(!range.contains(3));
    }

    #[test]
    fn test_metadata_index_json() {
        let mut idx = MetadataIndex::new();

        idx.index_json(1, &json!({"category": "tech", "score": 85, "active": true}));
        idx.index_json(
            2,
            &json!({"category": "tech", "score": 92, "active": false}),
        );
        idx.index_json(
            3,
            &json!({"category": "science", "score": 78, "active": true}),
        );

        // Keyword filter
        let filter = Filter::Eq("category".into(), FilterValue::String("tech".into()));
        assert!(idx.matches(1, &filter));
        assert!(idx.matches(2, &filter));
        assert!(!idx.matches(3, &filter));

        // Numeric filter
        let filter = Filter::Gte("score".into(), 80.0);
        assert!(idx.matches(1, &filter));
        assert!(idx.matches(2, &filter));
        assert!(!idx.matches(3, &filter));

        // Boolean filter
        let filter = Filter::Eq("active".into(), FilterValue::Bool(true));
        assert!(idx.matches(1, &filter));
        assert!(!idx.matches(2, &filter));
        assert!(idx.matches(3, &filter));

        // Combined filter
        let filter = Filter::And(vec![
            Filter::Eq("category".into(), FilterValue::String("tech".into())),
            Filter::Eq("active".into(), FilterValue::Bool(true)),
        ]);
        assert!(idx.matches(1, &filter));
        assert!(!idx.matches(2, &filter));
        assert!(!idx.matches(3, &filter));
    }

    #[test]
    fn test_numeric_index_range_scale() {
        let mut idx = NumericIndex::new();
        for i in 0..10_000u32 {
            idx.insert(i, i as f64);
        }

        // Equality
        assert!(idx.matches_eq(5000, 5000.0));
        assert!(!idx.matches_eq(5000, 5001.0));

        // Range query [100, 200] should contain exactly 101 docs
        let range = idx.get_range(100.0, 200.0);
        assert_eq!(range.len(), 101);
        assert!(range.contains(100));
        assert!(range.contains(200));
        assert!(!range.contains(99));
        assert!(!range.contains(201));

        // Strict gt/lt
        assert!(idx.matches_gt(5000, 4999.0));
        assert!(!idx.matches_gt(5000, 5000.0));
        assert!(idx.matches_lt(5000, 5001.0));
        assert!(!idx.matches_lt(5000, 5000.0));

        // Remove and verify
        idx.remove(150);
        let range2 = idx.get_range(100.0, 200.0);
        assert_eq!(range2.len(), 100);
        assert!(!range2.contains(150));
    }

    #[test]
    fn test_numeric_index_float_values() {
        let mut idx = NumericIndex::new();
        idx.insert(1, 1.5);
        idx.insert(2, 2.7);
        idx.insert(3, 2.7); // duplicate value
        idx.insert(4, 3.25);

        assert!(idx.matches_eq(1, 1.5));
        assert!(idx.matches_eq(2, 2.7));
        assert!(idx.matches_eq(3, 2.7));
        assert!(!idx.matches_eq(1, 2.7));

        let range = idx.get_range(2.0, 3.0);
        assert_eq!(range.len(), 2); // doc 2 and 3
        assert!(range.contains(2));
        assert!(range.contains(3));
        assert!(!range.contains(1));
        assert!(!range.contains(4));
    }

    #[test]
    fn test_numeric_serialize_roundtrip() {
        let mut idx = NumericIndex::new();
        idx.insert(1, 10.0);
        idx.insert(2, 20.5);
        idx.insert(3, 30.0);

        let mut buf = Vec::new();
        idx.serialize(&mut buf).unwrap();

        let mut cursor = std::io::Cursor::new(&buf);
        let idx2 = NumericIndex::deserialize(&mut cursor).unwrap();

        assert!(idx2.matches_eq(1, 10.0));
        assert!(idx2.matches_eq(2, 20.5));
        assert!(idx2.matches_eq(3, 30.0));
        let range = idx2.get_range(15.0, 25.0);
        assert!(range.contains(2));
        assert!(!range.contains(1));
    }

    #[test]
    fn test_numeric_nan_ignored() {
        let mut idx = NumericIndex::new();
        idx.insert(1, f64::NAN);
        idx.insert(2, 10.0);

        assert!(!idx.matches_eq(1, f64::NAN));
        assert!(idx.matches_eq(2, 10.0));
    }

    #[test]
    fn test_keyword_serialize_roundtrip() {
        let mut idx = KeywordIndex::new();
        idx.insert(1, "apple");
        idx.insert(2, "apple");
        idx.insert(3, "banana");

        let mut buf = Vec::new();
        idx.serialize(&mut buf).unwrap();

        let mut cursor = std::io::Cursor::new(&buf);
        let idx2 = KeywordIndex::deserialize(&mut cursor).unwrap();

        assert!(idx2.contains(1, "apple"));
        assert!(idx2.contains(2, "apple"));
        assert!(idx2.contains(3, "banana"));
    }
}
