//! Metadata filtering for queries
//!
//! Provides a flexible filtering system for metadata-based queries.

use serde::{Deserialize, Serialize};
use serde_json::Value;

/// Filter operation types
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FilterOp {
    /// Equal
    Eq,
    /// Not equal
    Ne,
    /// Greater than
    Gt,
    /// Greater than or equal
    Gte,
    /// Less than
    Lt,
    /// Less than or equal
    Lte,
    /// Contains (for strings and arrays)
    Contains,
    /// Starts with (for strings)
    StartsWith,
    /// Ends with (for strings)
    EndsWith,
    /// In list
    In,
    /// Not in list
    NotIn,
    /// Is null
    IsNull,
    /// Is not null
    IsNotNull,
    /// Exists (field is present)
    Exists,
}

/// A single filter condition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilterCondition {
    /// Field name (dot notation supported for nested fields)
    pub field: String,
    /// Filter operation
    pub op: FilterOp,
    /// Value to compare against
    pub value: Value,
}

impl FilterCondition {
    /// Create a new filter condition
    pub fn new(field: impl Into<String>, op: FilterOp, value: Value) -> Self {
        Self {
            field: field.into(),
            op,
            value,
        }
    }
    
    /// Create an equality filter
    pub fn eq(field: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::new(field, FilterOp::Eq, value.into())
    }
    
    /// Create a not-equal filter
    pub fn ne(field: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::new(field, FilterOp::Ne, value.into())
    }
    
    /// Create a greater-than filter
    pub fn gt(field: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::new(field, FilterOp::Gt, value.into())
    }
    
    /// Create a greater-than-or-equal filter
    pub fn gte(field: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::new(field, FilterOp::Gte, value.into())
    }
    
    /// Create a less-than filter
    pub fn lt(field: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::new(field, FilterOp::Lt, value.into())
    }
    
    /// Create a less-than-or-equal filter
    pub fn lte(field: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::new(field, FilterOp::Lte, value.into())
    }
    
    /// Create a contains filter
    pub fn contains(field: impl Into<String>, value: impl Into<Value>) -> Self {
        Self::new(field, FilterOp::Contains, value.into())
    }
    
    /// Create an "in" filter
    pub fn is_in(field: impl Into<String>, values: Vec<Value>) -> Self {
        Self::new(field, FilterOp::In, Value::Array(values))
    }
    
    /// Evaluate this condition against a value
    pub fn evaluate(&self, data: &Value) -> bool {
        let field_value = get_nested_value(data, &self.field);
        
        match self.op {
            FilterOp::Eq => field_value.map_or(false, |v| v == &self.value),
            FilterOp::Ne => field_value.map_or(true, |v| v != &self.value),
            FilterOp::Gt => compare_values(field_value, &self.value, |a, b| a > b),
            FilterOp::Gte => compare_values(field_value, &self.value, |a, b| a >= b),
            FilterOp::Lt => compare_values(field_value, &self.value, |a, b| a < b),
            FilterOp::Lte => compare_values(field_value, &self.value, |a, b| a <= b),
            FilterOp::Contains => evaluate_contains(field_value, &self.value),
            FilterOp::StartsWith => evaluate_starts_with(field_value, &self.value),
            FilterOp::EndsWith => evaluate_ends_with(field_value, &self.value),
            FilterOp::In => evaluate_in(field_value, &self.value),
            FilterOp::NotIn => !evaluate_in(field_value, &self.value),
            FilterOp::IsNull => field_value.map_or(true, |v| v.is_null()),
            FilterOp::IsNotNull => field_value.map_or(false, |v| !v.is_null()),
            FilterOp::Exists => field_value.is_some(),
        }
    }
}

/// Composite filter combining multiple conditions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Filter {
    /// Conditions combined with AND
    pub and: Vec<FilterCondition>,
    /// Conditions combined with OR (evaluated after AND)
    pub or: Vec<FilterCondition>,
}

impl Filter {
    /// Create an empty filter (matches everything)
    pub fn new() -> Self {
        Self {
            and: Vec::new(),
            or: Vec::new(),
        }
    }
    
    /// Create from a single condition
    pub fn from_condition(condition: FilterCondition) -> Self {
        Self {
            and: vec![condition],
            or: Vec::new(),
        }
    }
    
    /// Add an AND condition
    pub fn and(mut self, condition: FilterCondition) -> Self {
        self.and.push(condition);
        self
    }
    
    /// Add an OR condition
    pub fn or(mut self, condition: FilterCondition) -> Self {
        self.or.push(condition);
        self
    }
    
    /// Check if filter is empty (matches everything)
    pub fn is_empty(&self) -> bool {
        self.and.is_empty() && self.or.is_empty()
    }
    
    /// Evaluate the filter against a value
    pub fn evaluate(&self, data: &Value) -> bool {
        // All AND conditions must be true
        let and_result = self.and.iter().all(|c| c.evaluate(data));
        
        if !and_result {
            return false;
        }
        
        // If no OR conditions, return AND result
        if self.or.is_empty() {
            return true;
        }
        
        // At least one OR condition must be true
        self.or.iter().any(|c| c.evaluate(data))
    }
}

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

/// Get a nested value from JSON using dot notation
fn get_nested_value<'a>(data: &'a Value, path: &str) -> Option<&'a Value> {
    let mut current = data;
    
    for key in path.split('.') {
        current = match current {
            Value::Object(map) => map.get(key)?,
            Value::Array(arr) => {
                let index: usize = key.parse().ok()?;
                arr.get(index)?
            }
            _ => return None,
        };
    }
    
    Some(current)
}

/// Compare two values using a comparison function
fn compare_values<F>(a: Option<&Value>, b: &Value, cmp: F) -> bool
where
    F: Fn(f64, f64) -> bool,
{
    match (a, b) {
        (Some(Value::Number(na)), Value::Number(nb)) => {
            match (na.as_f64(), nb.as_f64()) {
                (Some(fa), Some(fb)) => cmp(fa, fb),
                _ => false,
            }
        }
        (Some(Value::String(sa)), Value::String(sb)) => {
            cmp(sa.as_str().cmp(sb.as_str()) as i32 as f64, 0.0)
        }
        _ => false,
    }
}

/// Evaluate contains operation
fn evaluate_contains(field_value: Option<&Value>, search_value: &Value) -> bool {
    match field_value {
        Some(Value::String(s)) => {
            if let Value::String(search) = search_value {
                s.contains(search.as_str())
            } else {
                false
            }
        }
        Some(Value::Array(arr)) => arr.contains(search_value),
        _ => false,
    }
}

/// Evaluate starts_with operation
fn evaluate_starts_with(field_value: Option<&Value>, prefix: &Value) -> bool {
    match (field_value, prefix) {
        (Some(Value::String(s)), Value::String(p)) => s.starts_with(p.as_str()),
        _ => false,
    }
}

/// Evaluate ends_with operation
fn evaluate_ends_with(field_value: Option<&Value>, suffix: &Value) -> bool {
    match (field_value, suffix) {
        (Some(Value::String(s)), Value::String(suf)) => s.ends_with(suf.as_str()),
        _ => false,
    }
}

/// Evaluate "in" operation
fn evaluate_in(field_value: Option<&Value>, list: &Value) -> bool {
    match (field_value, list) {
        (Some(v), Value::Array(arr)) => arr.contains(v),
        _ => false,
    }
}

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

    #[test]
    fn test_filter_eq() {
        let data = json!({
            "name": "Alice",
            "age": 30
        });
        
        let filter = FilterCondition::eq("name", "Alice");
        assert!(filter.evaluate(&data));
        
        let filter = FilterCondition::eq("name", "Bob");
        assert!(!filter.evaluate(&data));
    }

    #[test]
    fn test_filter_comparison() {
        let data = json!({ "age": 30 });
        
        assert!(FilterCondition::gt("age", 25).evaluate(&data));
        assert!(FilterCondition::gte("age", 30).evaluate(&data));
        assert!(FilterCondition::lt("age", 35).evaluate(&data));
        assert!(FilterCondition::lte("age", 30).evaluate(&data));
        
        assert!(!FilterCondition::gt("age", 30).evaluate(&data));
    }

    #[test]
    fn test_filter_contains() {
        let data = json!({
            "text": "Hello, World!",
            "tags": ["a", "b", "c"]
        });
        
        assert!(FilterCondition::contains("text", "World").evaluate(&data));
        assert!(!FilterCondition::contains("text", "Foo").evaluate(&data));
        
        assert!(FilterCondition::contains("tags", "b").evaluate(&data));
        assert!(!FilterCondition::contains("tags", "d").evaluate(&data));
    }

    #[test]
    fn test_filter_nested() {
        let data = json!({
            "user": {
                "profile": {
                    "name": "Alice"
                }
            }
        });
        
        let filter = FilterCondition::eq("user.profile.name", "Alice");
        assert!(filter.evaluate(&data));
    }

    #[test]
    fn test_composite_filter() {
        let data = json!({
            "status": "active",
            "age": 25,
            "role": "admin"
        });
        
        // status == "active" AND age >= 18
        let filter = Filter::new()
            .and(FilterCondition::eq("status", "active"))
            .and(FilterCondition::gte("age", 18));
        
        assert!(filter.evaluate(&data));
        
        // status == "inactive" (should fail)
        let filter = Filter::new()
            .and(FilterCondition::eq("status", "inactive"));
        
        assert!(!filter.evaluate(&data));
    }

    #[test]
    fn test_filter_in() {
        let data = json!({ "role": "admin" });
        
        let filter = FilterCondition::is_in(
            "role",
            vec![json!("admin"), json!("moderator")]
        );
        assert!(filter.evaluate(&data));
        
        let filter = FilterCondition::is_in(
            "role",
            vec![json!("user"), json!("guest")]
        );
        assert!(!filter.evaluate(&data));
    }
}
