use super::value::FieldValue;
use crate::observability::config::{FieldRuleConfig, FieldRuleOp};
use anyhow::{anyhow, bail, Result};
use regex::Regex;
use serde_json::Value;
use std::cmp::Ordering;
use std::collections::HashMap;
use tracing::{field::Visit, Event};

const MAX_REGEX_RULES: usize = 32;
const MAX_REGEX_PATTERN_LEN: usize = 256;

/// Compiled event field rule.
#[derive(Debug, Clone)]
pub struct FieldRule {
    /// Event field name.
    pub field: String,
    /// Operation to apply.
    pub op: FieldRuleOp,
    expected: Option<FieldValue>,
    expected_many: Vec<FieldValue>,
    regex: Option<Regex>,
}

impl FieldRule {
    /// Compiles a deserialized field rule.
    ///
    /// Regex patterns are validated up front so reload can fail without
    /// replacing the currently effective rule set.
    pub fn compile(config: FieldRuleConfig) -> Result<Self> {
        let mut regex = None;
        if config.op == FieldRuleOp::Regex {
            let pattern = config.value.as_str().ok_or_else(|| {
                anyhow!("field regex rule `{}` requires string value", config.field)
            })?;
            validate_regex_pattern(pattern)?;
            regex = Some(Regex::new(pattern)?);
        }

        let expected_many = if config.op == FieldRuleOp::In {
            match &config.value {
                Value::Array(values) => values
                    .iter()
                    .map(json_to_field_value)
                    .collect::<Result<_>>()?,
                _ => bail!("field in rule `{}` requires array value", config.field),
            }
        } else {
            Vec::new()
        };

        let expected = match config.op {
            FieldRuleOp::Exists | FieldRuleOp::NotExists | FieldRuleOp::Regex | FieldRuleOp::In => {
                None
            }
            _ => Some(json_to_field_value(&config.value)?),
        };

        Ok(Self {
            field: config.field,
            op: config.op,
            expected,
            expected_many,
            regex,
        })
    }

    fn matches(&self, fields: &HashMap<String, FieldValue>) -> bool {
        let actual = fields.get(&self.field);
        self.matches_value(actual)
    }

    fn matches_value(&self, actual: Option<&FieldValue>) -> bool {
        match self.op {
            FieldRuleOp::Exists => actual.is_some(),
            FieldRuleOp::NotExists => actual.is_none(),
            FieldRuleOp::Eq => actual
                .zip(self.expected.as_ref())
                .map(|(left, right)| field_value_eq(left, right))
                .unwrap_or(false),
            FieldRuleOp::Ne => actual
                .zip(self.expected.as_ref())
                .map(|(left, right)| !field_value_eq(left, right))
                .unwrap_or(false),
            FieldRuleOp::Contains => string_match(actual, self.expected.as_ref(), |left, right| {
                left.contains(right)
            }),
            FieldRuleOp::StartsWith => {
                string_match(actual, self.expected.as_ref(), |left, right| {
                    left.starts_with(right)
                })
            }
            FieldRuleOp::EndsWith => string_match(actual, self.expected.as_ref(), |left, right| {
                left.ends_with(right)
            }),
            FieldRuleOp::Regex => actual
                .and_then(FieldValue::as_str)
                .zip(self.regex.as_ref())
                .map(|(left, regex)| regex.is_match(left))
                .unwrap_or(false),
            FieldRuleOp::In => actual
                .map(|left| {
                    self.expected_many
                        .iter()
                        .any(|right| field_value_eq(left, right))
                })
                .unwrap_or(false),
            FieldRuleOp::Gt => compare_match(actual, self.expected.as_ref(), Ordering::Greater),
            FieldRuleOp::Gte => actual
                .zip(self.expected.as_ref())
                .and_then(|(left, right)| left.compare(right))
                .map(|ordering| ordering == Ordering::Greater || ordering == Ordering::Equal)
                .unwrap_or(false),
            FieldRuleOp::Lt => compare_match(actual, self.expected.as_ref(), Ordering::Less),
            FieldRuleOp::Lte => actual
                .zip(self.expected.as_ref())
                .and_then(|(left, right)| left.compare(right))
                .map(|ordering| ordering == Ordering::Less || ordering == Ordering::Equal)
                .unwrap_or(false),
        }
    }
}

fn field_value_eq(left: &FieldValue, right: &FieldValue) -> bool {
    left.compare(right)
        .map(|ordering| ordering == Ordering::Equal)
        .unwrap_or_else(|| left == right)
}

/// Event field filter with AND semantics.
#[derive(Debug, Clone)]
pub struct FieldFilter {
    rules: Vec<FieldRule>,
    rule_field_indices: Vec<usize>,
    field_indices: HashMap<String, usize>,
}

impl FieldFilter {
    /// Compiles field rules into a runtime filter.
    ///
    /// Multiple rules use AND semantics. An empty rule list should be represented
    /// as `None` by callers so event fields are not visited on the hot path.
    pub fn compile(configs: Vec<FieldRuleConfig>) -> Result<Option<Self>> {
        if configs.is_empty() {
            return Ok(None);
        }

        let regex_count = configs
            .iter()
            .filter(|rule| rule.op == FieldRuleOp::Regex)
            .count();
        if regex_count > MAX_REGEX_RULES {
            bail!(
                "too many field regex rules: {} > {}",
                regex_count,
                MAX_REGEX_RULES
            );
        }

        let rules = configs
            .into_iter()
            .map(FieldRule::compile)
            .collect::<Result<Vec<_>>>()?;
        let mut field_indices = HashMap::new();
        let mut rule_field_indices = Vec::with_capacity(rules.len());
        for rule in &rules {
            let next_index = field_indices.len();
            let field_index = *field_indices
                .entry(rule.field.clone())
                .or_insert(next_index);
            rule_field_indices.push(field_index);
        }

        Ok(Some(Self {
            rules,
            rule_field_indices,
            field_indices,
        }))
    }

    /// Returns true when all field rules match the event.
    pub fn matches(&self, event: &Event<'_>) -> bool {
        let mut values = Vec::with_capacity(self.field_indices.len());
        values.resize_with(self.field_indices.len(), || None);
        let mut visitor = FieldVisitor {
            field_indices: &self.field_indices,
            values: &mut values,
        };
        event.record(&mut visitor);
        self.rules
            .iter()
            .zip(&self.rule_field_indices)
            .all(|(rule, field_index)| rule.matches_value(values[*field_index].as_ref()))
    }
}

struct FieldVisitor<'a> {
    field_indices: &'a HashMap<String, usize>,
    values: &'a mut [Option<FieldValue>],
}

impl FieldVisitor<'_> {
    fn record_value(&mut self, field: &tracing::field::Field, value: FieldValue) {
        if let Some(index) = self.field_indices.get(field.name()) {
            self.values[*index] = Some(value);
        }
    }
}

impl Visit for FieldVisitor<'_> {
    fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
        self.record_value(field, FieldValue::I64(value));
    }

    fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
        self.record_value(field, FieldValue::U64(value));
    }

    fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
        self.record_value(field, FieldValue::Bool(value));
    }

    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
        if let Some(index) = self.field_indices.get(field.name()) {
            self.values[*index] = Some(FieldValue::String(value.to_string()));
        }
    }

    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
        if let Some(index) = self.field_indices.get(field.name()) {
            self.values[*index] = Some(FieldValue::String(format!("{:?}", value)));
        }
    }
}

fn string_match(
    actual: Option<&FieldValue>,
    expected: Option<&FieldValue>,
    op: impl FnOnce(&str, &str) -> bool,
) -> bool {
    actual
        .and_then(FieldValue::as_str)
        .zip(expected.and_then(FieldValue::as_str))
        .map(|(left, right)| op(left, right))
        .unwrap_or(false)
}

fn compare_match(
    actual: Option<&FieldValue>,
    expected: Option<&FieldValue>,
    expected_ordering: Ordering,
) -> bool {
    actual
        .zip(expected)
        .and_then(|(left, right)| left.compare(right))
        .map(|ordering| ordering == expected_ordering)
        .unwrap_or(false)
}

fn json_to_field_value(value: &Value) -> Result<FieldValue> {
    match value {
        Value::String(value) => Ok(FieldValue::String(value.clone())),
        Value::Number(value) => {
            if let Some(value) = value.as_i64() {
                Ok(FieldValue::I64(value))
            } else if let Some(value) = value.as_u64() {
                Ok(FieldValue::U64(value))
            } else {
                Ok(FieldValue::String(value.to_string()))
            }
        }
        Value::Bool(value) => Ok(FieldValue::Bool(*value)),
        Value::Null => bail!("field rule value cannot be null"),
        Value::Array(_) | Value::Object(_) => bail!("field rule value must be scalar"),
    }
}

fn validate_regex_pattern(pattern: &str) -> Result<()> {
    if pattern.len() > MAX_REGEX_PATTERN_LEN {
        bail!(
            "field regex pattern too long: {} > {}",
            pattern.len(),
            MAX_REGEX_PATTERN_LEN
        );
    }
    Ok(())
}

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

    fn fields(values: Vec<(&str, FieldValue)>) -> HashMap<String, FieldValue> {
        values
            .into_iter()
            .map(|(key, value)| (key.to_string(), value))
            .collect()
    }

    #[test]
    fn eq_and_contains_match() {
        let id_rule = FieldRule::compile(FieldRuleConfig {
            field: "id".to_string(),
            op: FieldRuleOp::Eq,
            value: json!(100),
        })
        .unwrap();
        let msg_rule = FieldRule::compile(FieldRuleConfig {
            field: "msg".to_string(),
            op: FieldRuleOp::Contains,
            value: json!("decode"),
        })
        .unwrap();
        let values = fields(vec![
            ("id", FieldValue::I64(100)),
            ("msg", FieldValue::String("start decode".to_string())),
        ]);

        assert!(id_rule.matches(&values));
        assert!(msg_rule.matches(&values));
    }

    #[test]
    fn eq_matches_numeric_values_across_signedness() {
        let id_rule = FieldRule::compile(FieldRuleConfig {
            field: "id".to_string(),
            op: FieldRuleOp::Eq,
            value: json!(100),
        })
        .unwrap();
        let values = fields(vec![("id", FieldValue::U64(100))]);

        assert!(id_rule.matches(&values));
    }

    #[test]
    fn in_and_numeric_compare_match() {
        let in_rule = FieldRule::compile(FieldRuleConfig {
            field: "error_code".to_string(),
            op: FieldRuleOp::In,
            value: json!([1001, 1002]),
        })
        .unwrap();
        let gt_rule = FieldRule::compile(FieldRuleConfig {
            field: "cost".to_string(),
            op: FieldRuleOp::Gt,
            value: json!(10),
        })
        .unwrap();
        let values = fields(vec![
            ("error_code", FieldValue::I64(1002)),
            ("cost", FieldValue::U64(11)),
        ]);

        assert!(in_rule.matches(&values));
        assert!(gt_rule.matches(&values));
    }

    #[test]
    fn in_matches_numeric_values_across_signedness() {
        let in_rule = FieldRule::compile(FieldRuleConfig {
            field: "id".to_string(),
            op: FieldRuleOp::In,
            value: json!([99, 100]),
        })
        .unwrap();
        let values = fields(vec![("id", FieldValue::U64(100))]);

        assert!(in_rule.matches(&values));
    }

    #[test]
    fn exists_and_not_exists_follow_missing_field_rules() {
        let exists = FieldRule::compile(FieldRuleConfig {
            field: "id".to_string(),
            op: FieldRuleOp::Exists,
            value: Value::Null,
        })
        .unwrap();
        let not_exists = FieldRule::compile(FieldRuleConfig {
            field: "missing".to_string(),
            op: FieldRuleOp::NotExists,
            value: Value::Null,
        })
        .unwrap();
        let values = fields(vec![("id", FieldValue::U64(100))]);

        assert!(exists.matches(&values));
        assert!(not_exists.matches(&values));
    }

    #[test]
    fn invalid_regex_fails_at_compile_time() {
        let err = FieldRule::compile(FieldRuleConfig {
            field: "msg".to_string(),
            op: FieldRuleOp::Regex,
            value: json!("("),
        })
        .unwrap_err();

        assert!(err.to_string().contains("regex parse error"));
    }

    #[test]
    fn string_field_operations_match_expected_values() {
        let values = fields(vec![(
            "msg",
            FieldValue::String("decode transaction finished".to_string()),
        )]);

        for (op, expected, should_match) in [
            (FieldRuleOp::Ne, json!("other"), true),
            (FieldRuleOp::Ne, json!("decode transaction finished"), false),
            (FieldRuleOp::StartsWith, json!("decode"), true),
            (FieldRuleOp::StartsWith, json!("transaction"), false),
            (FieldRuleOp::EndsWith, json!("finished"), true),
            (FieldRuleOp::EndsWith, json!("decode"), false),
            (FieldRuleOp::Regex, json!("transaction.*finished"), true),
            (FieldRuleOp::Regex, json!("^finished"), false),
        ] {
            let rule = FieldRule::compile(FieldRuleConfig {
                field: "msg".to_string(),
                op,
                value: expected,
            })
            .unwrap();
            assert_eq!(rule.matches(&values), should_match);
        }
    }

    #[test]
    fn numeric_comparison_operations_match_expected_values() {
        let values = fields(vec![("cost", FieldValue::U64(10))]);

        for (op, expected, should_match) in [
            (FieldRuleOp::Gt, json!(9), true),
            (FieldRuleOp::Gt, json!(10), false),
            (FieldRuleOp::Gte, json!(10), true),
            (FieldRuleOp::Gte, json!(11), false),
            (FieldRuleOp::Lt, json!(11), true),
            (FieldRuleOp::Lt, json!(10), false),
            (FieldRuleOp::Lte, json!(10), true),
            (FieldRuleOp::Lte, json!(9), false),
        ] {
            let rule = FieldRule::compile(FieldRuleConfig {
                field: "cost".to_string(),
                op,
                value: expected,
            })
            .unwrap();
            assert_eq!(rule.matches(&values), should_match);
        }
    }

    #[test]
    fn bool_and_missing_field_operations_match_expected_values() {
        let values = fields(vec![("retry", FieldValue::Bool(false))]);

        let bool_eq = FieldRule::compile(FieldRuleConfig {
            field: "retry".to_string(),
            op: FieldRuleOp::Eq,
            value: json!(false),
        })
        .unwrap();
        let bool_ne = FieldRule::compile(FieldRuleConfig {
            field: "retry".to_string(),
            op: FieldRuleOp::Ne,
            value: json!(true),
        })
        .unwrap();
        let missing_eq = FieldRule::compile(FieldRuleConfig {
            field: "missing".to_string(),
            op: FieldRuleOp::Eq,
            value: json!(false),
        })
        .unwrap();
        let missing_exists = FieldRule::compile(FieldRuleConfig {
            field: "missing".to_string(),
            op: FieldRuleOp::Exists,
            value: Value::Null,
        })
        .unwrap();
        let missing_not_exists = FieldRule::compile(FieldRuleConfig {
            field: "missing".to_string(),
            op: FieldRuleOp::NotExists,
            value: Value::Null,
        })
        .unwrap();

        assert!(bool_eq.matches(&values));
        assert!(bool_ne.matches(&values));
        assert!(!missing_eq.matches(&values));
        assert!(!missing_exists.matches(&values));
        assert!(missing_not_exists.matches(&values));
    }

    #[test]
    fn multiple_rules_for_same_field_use_and_semantics() {
        let filter = FieldFilter::compile(vec![
            FieldRuleConfig {
                field: "msg".to_string(),
                op: FieldRuleOp::Contains,
                value: json!("decode"),
            },
            FieldRuleConfig {
                field: "msg".to_string(),
                op: FieldRuleOp::EndsWith,
                value: json!("finished"),
            },
        ])
        .unwrap()
        .unwrap();

        assert_eq!(filter.field_indices.len(), 1);
        assert_eq!(filter.rule_field_indices.len(), 2);
    }

    #[test]
    fn compile_tracks_only_fields_required_by_rules() {
        let filter = FieldFilter::compile(vec![
            FieldRuleConfig {
                field: "id".to_string(),
                op: FieldRuleOp::Eq,
                value: json!(100),
            },
            FieldRuleConfig {
                field: "id".to_string(),
                op: FieldRuleOp::Gt,
                value: json!(0),
            },
            FieldRuleConfig {
                field: "msg".to_string(),
                op: FieldRuleOp::Contains,
                value: json!("decode"),
            },
        ])
        .unwrap()
        .unwrap();

        assert_eq!(filter.field_indices.len(), 2);
        assert!(filter.field_indices.contains_key("id"));
        assert!(filter.field_indices.contains_key("msg"));
        assert_eq!(filter.rule_field_indices.len(), 3);
    }
}
