use async_trait::async_trait;
use motosan_index_core::core::error::Result;
use motosan_index_core::core::metadata::{
    FilterCondition, FilterOperator, MetadataFilter, MetadataFilters, MetadataValue,
};
use motosan_index_core::core::node::{Node, NodeWithScore};
use motosan_index_core::traits::post_processor::PostProcessor;

/// Apply metadata filters client-side on already-retrieved nodes.
///
/// Useful when the vector store doesn't support filter pushdown, or
/// for applying additional filters after retrieval.
pub struct MetadataFilterProcessor {
    filters: MetadataFilters,
}

impl MetadataFilterProcessor {
    pub fn new(filters: MetadataFilters) -> Self {
        Self { filters }
    }
}

#[async_trait]
impl PostProcessor for MetadataFilterProcessor {
    async fn process(&self, nodes: Vec<NodeWithScore>) -> Result<Vec<NodeWithScore>> {
        Ok(nodes
            .into_iter()
            .filter(|n| matches_filters(&n.node, &self.filters))
            .collect())
    }
}

fn matches_filters(node: &Node, filters: &MetadataFilters) -> bool {
    match filters.condition {
        FilterCondition::And => filters.filters.iter().all(|f| matches_filter(node, f)),
        FilterCondition::Or => filters.filters.iter().any(|f| matches_filter(node, f)),
    }
}

fn matches_filter(node: &Node, filter: &MetadataFilter) -> bool {
    let node_value = node.metadata.get(&filter.key);
    match filter.operator {
        FilterOperator::Eq => node_value == Some(&filter.value),
        FilterOperator::Ne => node_value != Some(&filter.value),
        FilterOperator::Gt => {
            node_value.and_then(|v| compare(v, &filter.value)) == Some(std::cmp::Ordering::Greater)
        }
        FilterOperator::Gte => node_value
            .and_then(|v| compare(v, &filter.value))
            .is_some_and(|o| o != std::cmp::Ordering::Less),
        FilterOperator::Lt => {
            node_value.and_then(|v| compare(v, &filter.value)) == Some(std::cmp::Ordering::Less)
        }
        FilterOperator::Lte => node_value
            .and_then(|v| compare(v, &filter.value))
            .is_some_and(|o| o != std::cmp::Ordering::Greater),
        FilterOperator::In => {
            if let (Some(v), MetadataValue::List(list)) = (node_value, &filter.value) {
                list.iter().any(|item| item == v)
            } else {
                false
            }
        }
    }
}

fn compare(a: &MetadataValue, b: &MetadataValue) -> Option<std::cmp::Ordering> {
    match (a, b) {
        (MetadataValue::Integer(a), MetadataValue::Integer(b)) => Some(a.cmp(b)),
        (MetadataValue::Float(a), MetadataValue::Float(b)) => a.partial_cmp(b),
        (MetadataValue::Integer(a), MetadataValue::Float(b)) => (*a as f64).partial_cmp(b),
        (MetadataValue::Float(a), MetadataValue::Integer(b)) => a.partial_cmp(&(*b as f64)),
        _ => None,
    }
}
