//! Filter DSL for query string parsing.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;

/// Filter operation types.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum FilterOp {
    /// Equality comparison.
    Eq,
    /// Greater than or equal.
    Gte,
    /// Greater than.
    Gt,
    /// Less than or equal.
    Lte,
    /// Less than.
    Lt,
    /// String starts with.
    StartsWith,
    /// String ends with.
    EndsWith,
    /// String contains.
    Contains,
    /// Value in list.
    In,
    /// Negation of another operation.
    Not(Box<FilterOp>),
}

impl FilterOp {
    /// Parses an operation from a string.
    #[must_use]
    pub fn parse(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "eq" => Some(Self::Eq),
            "gte" => Some(Self::Gte),
            "gt" => Some(Self::Gt),
            "lte" => Some(Self::Lte),
            "lt" => Some(Self::Lt),
            "startswith" | "starts_with" => Some(Self::StartsWith),
            "endswith" | "ends_with" => Some(Self::EndsWith),
            "contains" => Some(Self::Contains),
            "in" => Some(Self::In),
            s if s.starts_with("not_") || s.starts_with("not.") => {
                let inner = &s[4..];
                Self::parse(inner).map(|op| Self::Not(Box::new(op)))
            }
            _ => None,
        }
    }

    /// Returns the string representation of the operation.
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Eq => "eq",
            Self::Gte => "gte",
            Self::Gt => "gt",
            Self::Lte => "lte",
            Self::Lt => "lt",
            Self::StartsWith => "startsWith",
            Self::EndsWith => "endsWith",
            Self::Contains => "contains",
            Self::In => "in",
            Self::Not(_) => "not",
        }
    }
}

/// A filter value that can be various types.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum FilterValue {
    /// String value.
    String(String),
    /// Integer value.
    Integer(i64),
    /// Floating point value.
    Float(f64),
    /// Boolean value.
    Bool(bool),
    /// Null value.
    Null,
    /// List of values (for `in` operator).
    List(Vec<FilterValue>),
}

impl FilterValue {
    /// Parses a value from a string, optionally using type hints from the schema.
    #[must_use]
    pub fn parse(s: &str, field_type: Option<FilterFieldType>) -> Self {
        match field_type {
            Some(FilterFieldType::Integer) => s
                .parse::<i64>()
                .map(Self::Integer)
                .unwrap_or_else(|_| Self::String(s.to_string())),
            Some(FilterFieldType::Float) => s
                .parse::<f64>()
                .map(Self::Float)
                .unwrap_or_else(|_| Self::String(s.to_string())),
            Some(FilterFieldType::Boolean) => match s.to_lowercase().as_str() {
                "true" | "1" | "yes" => Self::Bool(true),
                "false" | "0" | "no" => Self::Bool(false),
                _ => Self::String(s.to_string()),
            },
            Some(FilterFieldType::String) | None => {
                if s == "null" || s == "NULL" {
                    Self::Null
                } else if let Ok(i) = s.parse::<i64>() {
                    Self::Integer(i)
                } else if let Ok(f) = s.parse::<f64>() {
                    Self::Float(f)
                } else if s == "true" || s == "false" {
                    Self::Bool(s == "true")
                } else {
                    Self::String(s.to_string())
                }
            }
        }
    }

    /// Parses a comma-separated list of values.
    #[must_use]
    pub fn parse_list(s: &str, field_type: Option<FilterFieldType>) -> Self {
        let values: Vec<Self> = s
            .split(',')
            .map(|v| Self::parse(v.trim(), field_type))
            .collect();
        Self::List(values)
    }
}

/// A single filter condition.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Filter {
    /// Field name to filter on.
    pub field: String,
    /// Filter operation.
    pub op: FilterOp,
    /// Filter value.
    pub value: FilterValue,
}

impl Filter {
    /// Creates a new filter.
    #[must_use]
    pub fn new(field: impl Into<String>, op: FilterOp, value: FilterValue) -> Self {
        Self {
            field: field.into(),
            op,
            value,
        }
    }

    /// Creates an equality filter.
    #[must_use]
    pub fn eq(field: impl Into<String>, value: FilterValue) -> Self {
        Self::new(field, FilterOp::Eq, value)
    }

    /// Creates a greater-than filter.
    #[must_use]
    pub fn gt(field: impl Into<String>, value: FilterValue) -> Self {
        Self::new(field, FilterOp::Gt, value)
    }

    /// Creates a greater-than-or-equal filter.
    #[must_use]
    pub fn gte(field: impl Into<String>, value: FilterValue) -> Self {
        Self::new(field, FilterOp::Gte, value)
    }

    /// Creates a less-than filter.
    #[must_use]
    pub fn lt(field: impl Into<String>, value: FilterValue) -> Self {
        Self::new(field, FilterOp::Lt, value)
    }

    /// Creates a less-than-or-equal filter.
    #[must_use]
    pub fn lte(field: impl Into<String>, value: FilterValue) -> Self {
        Self::new(field, FilterOp::Lte, value)
    }

    /// Creates a contains filter.
    #[must_use]
    pub fn contains(field: impl Into<String>, value: impl Into<String>) -> Self {
        Self::new(field, FilterOp::Contains, FilterValue::String(value.into()))
    }

    /// Creates a starts-with filter.
    #[must_use]
    pub fn starts_with(field: impl Into<String>, value: impl Into<String>) -> Self {
        Self::new(
            field,
            FilterOp::StartsWith,
            FilterValue::String(value.into()),
        )
    }

    /// Creates an ends-with filter.
    #[must_use]
    pub fn ends_with(field: impl Into<String>, value: impl Into<String>) -> Self {
        Self::new(field, FilterOp::EndsWith, FilterValue::String(value.into()))
    }

    /// Creates an in-list filter.
    #[must_use]
    pub fn in_list(field: impl Into<String>, values: Vec<FilterValue>) -> Self {
        Self::new(field, FilterOp::In, FilterValue::List(values))
    }
}

/// Field type for schema validation.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum FilterFieldType {
    /// String field.
    String,
    /// Integer field.
    Integer,
    /// Float field.
    Float,
    /// Boolean field.
    Boolean,
}

/// Schema definition for a filterable field.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FilterField {
    /// Field type for parsing values.
    pub field_type: FilterFieldType,
    /// Allowed operations on this field.
    pub allowed_ops: Vec<FilterOp>,
}

impl FilterField {
    /// Creates a new filter field definition.
    #[must_use]
    pub fn new(field_type: FilterFieldType, allowed_ops: Vec<FilterOp>) -> Self {
        Self {
            field_type,
            allowed_ops,
        }
    }

    /// Creates a string field with default operations.
    #[must_use]
    pub fn string() -> Self {
        Self::new(
            FilterFieldType::String,
            vec![
                FilterOp::Eq,
                FilterOp::StartsWith,
                FilterOp::EndsWith,
                FilterOp::Contains,
                FilterOp::In,
            ],
        )
    }

    /// Creates a numeric field with default operations.
    #[must_use]
    pub fn integer() -> Self {
        Self::new(
            FilterFieldType::Integer,
            vec![
                FilterOp::Eq,
                FilterOp::Gt,
                FilterOp::Gte,
                FilterOp::Lt,
                FilterOp::Lte,
                FilterOp::In,
            ],
        )
    }

    /// Creates a float field with default operations.
    #[must_use]
    pub fn float() -> Self {
        Self::new(
            FilterFieldType::Float,
            vec![
                FilterOp::Eq,
                FilterOp::Gt,
                FilterOp::Gte,
                FilterOp::Lt,
                FilterOp::Lte,
            ],
        )
    }

    /// Creates a boolean field with default operations.
    #[must_use]
    pub fn boolean() -> Self {
        Self::new(FilterFieldType::Boolean, vec![FilterOp::Eq])
    }

    /// Checks if an operation is allowed for this field.
    #[must_use]
    pub fn is_op_allowed(&self, op: &FilterOp) -> bool {
        self.allowed_ops.iter().any(|allowed| match (allowed, op) {
            (FilterOp::Not(a), FilterOp::Not(b)) => self.ops_match(a, b),
            _ => self.ops_match(allowed, op),
        })
    }

    fn ops_match(&self, a: &FilterOp, b: &FilterOp) -> bool {
        std::mem::discriminant(a) == std::mem::discriminant(b)
    }
}

/// Schema for validating filters.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct FilterSchema {
    /// Map of field name to field definition.
    pub fields: HashMap<String, FilterField>,
}

impl FilterSchema {
    /// Creates a new empty schema.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Adds a field to the schema.
    #[must_use]
    pub fn with_field(mut self, name: impl Into<String>, field: FilterField) -> Self {
        self.fields.insert(name.into(), field);
        self
    }

    /// Gets a field definition by name.
    #[must_use]
    pub fn get(&self, name: &str) -> Option<&FilterField> {
        self.fields.get(name)
    }

    /// Validates a filter against this schema.
    pub fn validate(&self, filter: &Filter) -> Result<(), FilterError> {
        let field = self
            .fields
            .get(&filter.field)
            .ok_or_else(|| FilterError::UnknownField(filter.field.clone()))?;

        if !field.is_op_allowed(&filter.op) {
            return Err(FilterError::InvalidOperation {
                field: filter.field.clone(),
                op: filter.op.as_str().to_string(),
            });
        }

        Ok(())
    }
}

/// Errors that can occur during filter parsing.
#[derive(Debug, Clone, Error, PartialEq)]
pub enum FilterError {
    /// Unknown field name.
    #[error("unknown filter field: {0}")]
    UnknownField(String),

    /// Invalid operation for field.
    #[error("invalid operation '{op}' for field '{field}'")]
    InvalidOperation {
        /// Field name.
        field: String,
        /// Operation name.
        op: String,
    },

    /// Unknown operation name.
    #[error("unknown filter operation: {0}")]
    UnknownOperation(String),

    /// Invalid filter syntax.
    #[error("invalid filter syntax: {0}")]
    InvalidSyntax(String),

    /// Invalid value format.
    #[error("invalid value for field '{field}': {reason}")]
    InvalidValue {
        /// Field name.
        field: String,
        /// Reason for invalidity.
        reason: String,
    },
}

/// Parses filters from a query string.
///
/// Expected format: `filter.field.op=value`
/// Examples:
/// - `filter.name.startsWith=M`
/// - `filter.age.gte=18`
/// - `filter.status.in=active,pending`
///
/// # Errors
///
/// Returns `FilterError` if:
/// - A field is not in the schema
/// - An operation is not allowed for a field
/// - The operation name is unknown
/// - The syntax is invalid
pub fn parse_filters(query: &str, schema: &FilterSchema) -> Result<Vec<Filter>, FilterError> {
    let mut filters = Vec::new();

    for part in query.split('&') {
        let part = part.trim();
        if part.is_empty() {
            continue;
        }

        // Skip non-filter parameters
        if !part.starts_with("filter.") {
            continue;
        }

        let (key, value) = part
            .split_once('=')
            .ok_or_else(|| FilterError::InvalidSyntax(format!("missing '=' in: {part}")))?;

        let key = key.strip_prefix("filter.").unwrap_or(key);

        // Parse field.op format
        let (field, op_str) = key.rsplit_once('.').ok_or_else(|| {
            FilterError::InvalidSyntax(format!("expected 'field.op' format: {key}"))
        })?;

        let op = FilterOp::parse(op_str)
            .ok_or_else(|| FilterError::UnknownOperation(op_str.to_string()))?;

        let field_def = schema.get(field);
        let field_type = field_def.map(|f| f.field_type);

        let filter_value = if matches!(op, FilterOp::In) {
            FilterValue::parse_list(value, field_type)
        } else {
            FilterValue::parse(value, field_type)
        };

        let filter = Filter::new(field, op, filter_value);

        // Validate against schema
        schema.validate(&filter)?;

        filters.push(filter);
    }

    Ok(filters)
}

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

    fn test_schema() -> FilterSchema {
        FilterSchema::new()
            .with_field("name", FilterField::string())
            .with_field("age", FilterField::integer())
            .with_field("score", FilterField::float())
            .with_field("active", FilterField::boolean())
    }

    #[test]
    fn test_filter_op_parse() {
        assert_eq!(FilterOp::parse("eq"), Some(FilterOp::Eq));
        assert_eq!(FilterOp::parse("GTE"), Some(FilterOp::Gte));
        assert_eq!(FilterOp::parse("startsWith"), Some(FilterOp::StartsWith));
        assert_eq!(FilterOp::parse("starts_with"), Some(FilterOp::StartsWith));
        assert_eq!(
            FilterOp::parse("not_eq"),
            Some(FilterOp::Not(Box::new(FilterOp::Eq)))
        );
        assert_eq!(FilterOp::parse("invalid"), None);
    }

    #[test]
    fn test_filter_value_parse() {
        assert_eq!(
            FilterValue::parse("hello", None),
            FilterValue::String("hello".to_string())
        );
        assert_eq!(FilterValue::parse("42", None), FilterValue::Integer(42));
        assert_eq!(FilterValue::parse("2.5", None), FilterValue::Float(2.5));
        assert_eq!(FilterValue::parse("true", None), FilterValue::Bool(true));
        assert_eq!(FilterValue::parse("null", None), FilterValue::Null);
    }

    #[test]
    fn test_filter_value_parse_with_type() {
        assert_eq!(
            FilterValue::parse("42", Some(FilterFieldType::Integer)),
            FilterValue::Integer(42)
        );
        assert_eq!(
            FilterValue::parse("not_a_number", Some(FilterFieldType::Integer)),
            FilterValue::String("not_a_number".to_string())
        );
        assert_eq!(
            FilterValue::parse("yes", Some(FilterFieldType::Boolean)),
            FilterValue::Bool(true)
        );
    }

    #[test]
    fn test_filter_value_parse_list() {
        let list = FilterValue::parse_list("a,b,c", Some(FilterFieldType::String));
        assert_eq!(
            list,
            FilterValue::List(vec![
                FilterValue::String("a".to_string()),
                FilterValue::String("b".to_string()),
                FilterValue::String("c".to_string()),
            ])
        );

        let nums = FilterValue::parse_list("1,2,3", Some(FilterFieldType::Integer));
        assert_eq!(
            nums,
            FilterValue::List(vec![
                FilterValue::Integer(1),
                FilterValue::Integer(2),
                FilterValue::Integer(3),
            ])
        );
    }

    #[test]
    fn test_filter_constructors() {
        let eq = Filter::eq("name", FilterValue::String("Alice".to_string()));
        assert_eq!(eq.field, "name");
        assert_eq!(eq.op, FilterOp::Eq);

        let gt = Filter::gt("age", FilterValue::Integer(18));
        assert_eq!(gt.op, FilterOp::Gt);

        let contains = Filter::contains("description", "test");
        assert_eq!(contains.op, FilterOp::Contains);
    }

    #[test]
    fn test_filter_schema_validation() {
        let schema = test_schema();

        let valid = Filter::eq("name", FilterValue::String("test".to_string()));
        assert!(schema.validate(&valid).is_ok());

        let invalid_field = Filter::eq("unknown", FilterValue::String("test".to_string()));
        assert!(matches!(
            schema.validate(&invalid_field),
            Err(FilterError::UnknownField(_))
        ));

        let invalid_op = Filter::gt("name", FilterValue::String("test".to_string()));
        assert!(matches!(
            schema.validate(&invalid_op),
            Err(FilterError::InvalidOperation { .. })
        ));
    }

    #[test]
    fn test_parse_filters_basic() {
        let schema = test_schema();

        let filters = parse_filters("filter.name.startsWith=M&filter.age.gte=18", &schema)
            .expect("should parse");

        assert_eq!(filters.len(), 2);

        assert_eq!(filters[0].field, "name");
        assert_eq!(filters[0].op, FilterOp::StartsWith);
        assert_eq!(filters[0].value, FilterValue::String("M".to_string()));

        assert_eq!(filters[1].field, "age");
        assert_eq!(filters[1].op, FilterOp::Gte);
        assert_eq!(filters[1].value, FilterValue::Integer(18));
    }

    #[test]
    fn test_parse_filters_in_operator() {
        let schema = test_schema();

        let filters = parse_filters("filter.age.in=1,2,3", &schema).expect("should parse");

        assert_eq!(filters.len(), 1);
        assert_eq!(filters[0].op, FilterOp::In);
        assert_eq!(
            filters[0].value,
            FilterValue::List(vec![
                FilterValue::Integer(1),
                FilterValue::Integer(2),
                FilterValue::Integer(3),
            ])
        );
    }

    #[test]
    fn test_parse_filters_with_other_params() {
        let schema = test_schema();

        let filters = parse_filters("page=1&filter.name.eq=test&sort=-created", &schema)
            .expect("should parse");

        assert_eq!(filters.len(), 1);
        assert_eq!(filters[0].field, "name");
    }

    #[test]
    fn test_parse_filters_unknown_field() {
        let schema = test_schema();

        let result = parse_filters("filter.unknown.eq=value", &schema);
        assert!(matches!(result, Err(FilterError::UnknownField(_))));
    }

    #[test]
    fn test_parse_filters_unknown_operation() {
        let schema = test_schema();

        let result = parse_filters("filter.name.foobar=value", &schema);
        assert!(matches!(result, Err(FilterError::UnknownOperation(_))));
    }

    #[test]
    fn test_parse_filters_invalid_syntax() {
        let schema = test_schema();

        let result = parse_filters("filter.name", &schema);
        // Empty result because no = sign means it's skipped or errors
        assert!(result.is_err() || result.map(|f| f.is_empty()).unwrap_or(false));
    }

    #[test]
    fn test_parse_filters_empty() {
        let schema = test_schema();

        let filters = parse_filters("", &schema).expect("should parse empty");
        assert!(filters.is_empty());

        let filters = parse_filters("page=1&sort=name", &schema).expect("should parse");
        assert!(filters.is_empty());
    }

    #[test]
    fn test_filter_field_presets() {
        let string_field = FilterField::string();
        assert!(string_field.is_op_allowed(&FilterOp::Contains));
        assert!(!string_field.is_op_allowed(&FilterOp::Gt));

        let int_field = FilterField::integer();
        assert!(int_field.is_op_allowed(&FilterOp::Gte));
        assert!(!int_field.is_op_allowed(&FilterOp::Contains));

        let bool_field = FilterField::boolean();
        assert!(bool_field.is_op_allowed(&FilterOp::Eq));
        assert!(!bool_field.is_op_allowed(&FilterOp::Gt));
    }

    #[test]
    fn test_filter_serialization() {
        let filter = Filter::eq("name", FilterValue::String("test".to_string()));
        let json = serde_json::to_string(&filter).expect("should serialize");
        assert!(json.contains("\"field\":\"name\""));
        assert!(json.contains("\"value\":\"test\""));

        let deserialized: Filter = serde_json::from_str(&json).expect("should deserialize");
        assert_eq!(deserialized, filter);
    }
}
