//! Metadata Filter for Vector Search
//!
//! This module provides metadata filtering capabilities for vector search results,
//! allowing combination of vector similarity search with metadata-based filtering.
//!
//! # Example
//!
//! ```ignore
//! use aifastdb_core::vector::filter::{MetadataFilter, FilterOp};
//! use std::collections::HashMap;
//!
//! // Create a filter: category == "article" AND author == "John"
//! let filter = MetadataFilter::new()
//!     .and("category", FilterOp::Eq, serde_json::json!("article"))
//!     .and("author", FilterOp::Eq, serde_json::json!("John"));
//!
//! // Apply filter to search results
//! let filtered_results = filter.apply(&search_results, &metadata_store);
//! ```

use std::collections::HashMap;
use serde_json::Value;

use super::concurrent::VectorSearchResult;

/// Filter operation types
#[derive(Debug, Clone, PartialEq)]
pub enum FilterOp {
    /// Equality: field == value
    Eq,
    /// Not equal: field != value
    Ne,
    /// Greater than: field > value
    Gt,
    /// Greater than or equal: field >= value
    Gte,
    /// Less than: field < value
    Lt,
    /// Less than or equal: field <= value
    Lte,
    /// Contains (for arrays/strings): field contains value
    Contains,
    /// In (for arrays): field in [value1, value2, ...]
    In,
    /// Not in: field not in [value1, value2, ...]
    NotIn,
    /// Exists: field exists (not null/undefined)
    Exists,
    /// Not exists: field does not exist
    NotExists,
}

/// Metadata filter for vector search results
///
/// Supports combining multiple filter conditions with AND/OR logic.
/// Filters are applied after vector similarity search to refine results.
#[derive(Debug, Clone)]
pub struct MetadataFilter {
    /// Filter conditions: (field_name, operation, value)
    conditions: Vec<(String, FilterOp, Value)>,
    /// Whether conditions are combined with AND (true) or OR (false)
    combine_and: bool,
}

impl MetadataFilter {
    /// Create a new empty filter
    pub fn new() -> Self {
        Self {
            conditions: Vec::new(),
            combine_and: true, // Default to AND
        }
    }

    /// Create a filter that combines conditions with AND (default)
    pub fn and_combine(mut self) -> Self {
        self.combine_and = true;
        self
    }

    /// Create a filter that combines conditions with OR
    pub fn or_combine(mut self) -> Self {
        self.combine_and = false;
        self
    }

    /// Add an AND condition
    pub fn and(mut self, field: impl Into<String>, op: FilterOp, value: Value) -> Self {
        self.conditions.push((field.into(), op, value));
        self.combine_and = true;
        self
    }

    /// Add an OR condition
    pub fn or(mut self, field: impl Into<String>, op: FilterOp, value: Value) -> Self {
        self.conditions.push((field.into(), op, value));
        self.combine_and = false;
        self
    }

    /// Apply filter to search results
    ///
    /// # Arguments
    ///
    /// * `results` - Vector search results to filter
    /// * `metadata_store` - Map from vector ID to metadata
    ///
    /// # Returns
    ///
    /// Filtered results that match all (AND) or any (OR) conditions
    pub fn apply(
        &self,
        results: &[VectorSearchResult],
        metadata_store: &HashMap<String, HashMap<String, Value>>,
    ) -> Vec<VectorSearchResult> {
        if self.conditions.is_empty() {
            return results.to_vec();
        }

        results
            .iter()
            .filter(|result| self.matches(result, metadata_store))
            .cloned()
            .collect()
    }

    /// Check if a search result matches the filter
    fn matches(
        &self,
        result: &VectorSearchResult,
        metadata_store: &HashMap<String, HashMap<String, Value>>,
    ) -> bool {
        if self.conditions.is_empty() {
            return true;
        }

        let metadata = metadata_store
            .get(&result.id)
            .cloned()
            .unwrap_or_default();

        if self.combine_and {
            // All conditions must match (AND)
            self.conditions
                .iter()
                .all(|(field, op, expected_value)| {
                    Self::check_condition(&metadata, field, op, expected_value)
                })
        } else {
            // At least one condition must match (OR)
            self.conditions
                .iter()
                .any(|(field, op, expected_value)| {
                    Self::check_condition(&metadata, field, op, expected_value)
                })
        }
    }

    /// Check a single filter condition
    fn check_condition(
        metadata: &HashMap<String, Value>,
        field: &str,
        op: &FilterOp,
        expected_value: &Value,
    ) -> bool {
        let field_value = metadata.get(field);

        match op {
            FilterOp::Eq => {
                field_value.map_or(false, |v| Self::values_equal(v, expected_value))
            }
            FilterOp::Ne => {
                field_value.map_or(true, |v| !Self::values_equal(v, expected_value))
            }
            FilterOp::Gt => {
                field_value.and_then(|v| Self::compare_values(v, expected_value))
                    .map_or(false, |ord| ord == std::cmp::Ordering::Greater)
            }
            FilterOp::Gte => {
                field_value.and_then(|v| Self::compare_values(v, expected_value))
                    .map_or(false, |ord| ord == std::cmp::Ordering::Greater || ord == std::cmp::Ordering::Equal)
            }
            FilterOp::Lt => {
                field_value.and_then(|v| Self::compare_values(v, expected_value))
                    .map_or(false, |ord| ord == std::cmp::Ordering::Less)
            }
            FilterOp::Lte => {
                field_value.and_then(|v| Self::compare_values(v, expected_value))
                    .map_or(false, |ord| ord == std::cmp::Ordering::Less || ord == std::cmp::Ordering::Equal)
            }
            FilterOp::Contains => {
                field_value.map_or(false, |v| Self::value_contains(v, expected_value))
            }
            FilterOp::In => {
                if let Value::Array(arr) = expected_value {
                    field_value.map_or(false, |v| arr.iter().any(|item| Self::values_equal(v, item)))
                } else {
                    false
                }
            }
            FilterOp::NotIn => {
                if let Value::Array(arr) = expected_value {
                    field_value.map_or(true, |v| !arr.iter().any(|item| Self::values_equal(v, item)))
                } else {
                    true
                }
            }
            FilterOp::Exists => field_value.is_some(),
            FilterOp::NotExists => field_value.is_none(),
        }
    }

    /// Compare two JSON values for ordering
    fn compare_values(v1: &Value, v2: &Value) -> Option<std::cmp::Ordering> {
        match (v1, v2) {
            (Value::Number(n1), Value::Number(n2)) => {
                let f1 = n1.as_f64()?;
                let f2 = n2.as_f64()?;
                f1.partial_cmp(&f2)
            }
            (Value::String(s1), Value::String(s2)) => Some(s1.cmp(s2)),
            _ => None,
        }
    }

    /// Check if two JSON values are equal
    fn values_equal(v1: &Value, v2: &Value) -> bool {
        match (v1, v2) {
            (Value::Null, Value::Null) => true,
            (Value::Bool(b1), Value::Bool(b2)) => b1 == b2,
            (Value::Number(n1), Value::Number(n2)) => {
                n1.as_f64()
                    .and_then(|f1| n2.as_f64().map(|f2| (f1 - f2).abs() < f64::EPSILON))
                    .unwrap_or(false)
            }
            (Value::String(s1), Value::String(s2)) => s1 == s2,
            (Value::Array(a1), Value::Array(a2)) => {
                a1.len() == a2.len() && a1.iter().zip(a2.iter()).all(|(v1, v2)| Self::values_equal(v1, v2))
            }
            (Value::Object(o1), Value::Object(o2)) => {
                o1.len() == o2.len()
                    && o1.iter().all(|(k, v1)| {
                        o2.get(k)
                            .map_or(false, |v2| Self::values_equal(v1, v2))
                    })
            }
            _ => false,
        }
    }

    /// Check if a value contains another value
    fn value_contains(container: &Value, item: &Value) -> bool {
        match container {
            Value::Array(arr) => arr.iter().any(|v| Self::values_equal(v, item)),
            Value::String(s) => {
                if let Value::String(item_str) = item {
                    s.contains(item_str)
                } else {
                    false
                }
            }
            _ => false,
        }
    }
}

impl Default for MetadataFilter {
    fn default() -> Self {
        Self::new()
    }
}

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

    fn create_test_results() -> Vec<VectorSearchResult> {
        vec![
            VectorSearchResult {
                id: "vec_1".to_string(),
                distance: 0.1,
                shard_id: 0,
            },
            VectorSearchResult {
                id: "vec_2".to_string(),
                distance: 0.2,
                shard_id: 0,
            },
            VectorSearchResult {
                id: "vec_3".to_string(),
                distance: 0.3,
                shard_id: 1,
            },
        ]
    }

    fn create_test_metadata() -> HashMap<String, HashMap<String, Value>> {
        let mut store = HashMap::new();

        let mut meta1 = HashMap::new();
        meta1.insert("category".to_string(), Value::String("article".to_string()));
        meta1.insert("author".to_string(), Value::String("John".to_string()));
        meta1.insert("views".to_string(), Value::Number(serde_json::Number::from(100)));
        store.insert("vec_1".to_string(), meta1);

        let mut meta2 = HashMap::new();
        meta2.insert("category".to_string(), Value::String("article".to_string()));
        meta2.insert("author".to_string(), Value::String("Jane".to_string()));
        meta2.insert("views".to_string(), Value::Number(serde_json::Number::from(50)));
        store.insert("vec_2".to_string(), meta2);

        let mut meta3 = HashMap::new();
        meta3.insert("category".to_string(), Value::String("blog".to_string()));
        meta3.insert("author".to_string(), Value::String("John".to_string()));
        store.insert("vec_3".to_string(), meta3);

        store
    }

    #[test]
    fn test_empty_filter() {
        let filter = MetadataFilter::new();
        let results = create_test_results();
        let metadata = create_test_metadata();

        let filtered = filter.apply(&results, &metadata);
        assert_eq!(filtered.len(), 3);
    }

    #[test]
    fn test_eq_filter() {
        let filter = MetadataFilter::new().and(
            "category",
            FilterOp::Eq,
            Value::String("article".to_string()),
        );
        let results = create_test_results();
        let metadata = create_test_metadata();

        let filtered = filter.apply(&results, &metadata);
        assert_eq!(filtered.len(), 2);
        assert_eq!(filtered[0].id, "vec_1");
        assert_eq!(filtered[1].id, "vec_2");
    }

    #[test]
    fn test_and_filter() {
        let filter = MetadataFilter::new()
            .and("category", FilterOp::Eq, Value::String("article".to_string()))
            .and("author", FilterOp::Eq, Value::String("John".to_string()));
        let results = create_test_results();
        let metadata = create_test_metadata();

        let filtered = filter.apply(&results, &metadata);
        assert_eq!(filtered.len(), 1);
        assert_eq!(filtered[0].id, "vec_1");
    }

    #[test]
    fn test_or_filter() {
        let filter = MetadataFilter::new()
            .or("author", FilterOp::Eq, Value::String("John".to_string()))
            .or("author", FilterOp::Eq, Value::String("Jane".to_string()));
        let results = create_test_results();
        let metadata = create_test_metadata();

        let filtered = filter.apply(&results, &metadata);
        // Should match vec_1 (John), vec_2 (Jane), and vec_3 (John) = 3 results
        assert_eq!(filtered.len(), 3);
        // Verify all results have matching authors
        assert!(filtered.iter().all(|r| {
            let meta = metadata.get(&r.id).unwrap();
            let author = meta.get("author").and_then(|v| v.as_str()).unwrap_or("");
            author == "John" || author == "Jane"
        }));
    }

    #[test]
    fn test_gt_filter() {
        let filter = MetadataFilter::new().and(
            "views",
            FilterOp::Gt,
            Value::Number(serde_json::Number::from(75)),
        );
        let results = create_test_results();
        let metadata = create_test_metadata();

        let filtered = filter.apply(&results, &metadata);
        assert_eq!(filtered.len(), 1);
        assert_eq!(filtered[0].id, "vec_1");
    }

    #[test]
    fn test_in_filter() {
        let filter = MetadataFilter::new().and(
            "author",
            FilterOp::In,
            Value::Array(vec![
                Value::String("John".to_string()),
                Value::String("Bob".to_string()),
            ]),
        );
        let results = create_test_results();
        let metadata = create_test_metadata();

        let filtered = filter.apply(&results, &metadata);
        assert_eq!(filtered.len(), 2); // vec_1 and vec_3
    }

    #[test]
    fn test_exists_filter() {
        let filter = MetadataFilter::new().and("views", FilterOp::Exists, Value::Null);
        let results = create_test_results();
        let metadata = create_test_metadata();

        let filtered = filter.apply(&results, &metadata);
        assert_eq!(filtered.len(), 2); // vec_1 and vec_2 have views
    }

    #[test]
    fn test_not_exists_filter() {
        let filter = MetadataFilter::new().and("views", FilterOp::NotExists, Value::Null);
        let results = create_test_results();
        let metadata = create_test_metadata();

        let filtered = filter.apply(&results, &metadata);
        assert_eq!(filtered.len(), 1); // vec_3 doesn't have views
    }
}
