use crate::types::{NormalizedEntity, RuleResult};
use crate::normalization::*;
use strsim::{jaro_winkler, normalized_levenshtein};

pub trait MatchRule: Send + Sync {
    fn name(&self) -> &str;
    fn evaluate(&self, a: &NormalizedEntity, b: &NormalizedEntity) -> RuleResult;
}

fn apply_normalizer(name: &str, value: &str) -> String {
    let result = match name {
        "phone" => PhoneNormalizer.normalize(value),
        "email" => EmailNormalizer.normalize(value),
        "name" => NameNormalizer.normalize(value),
        "domain" => DomainNormalizer.normalize(value),
        _ => GenericNormalizer.normalize(value),
    };
    match result {
        NormalizationResult::Normalized(s) => s,
        NormalizationResult::Invalid(_) => value.to_string(),
    }
}

pub struct ExactMatch {
    pub field: String,
    pub weight: f64,
    pub case_insensitive: bool,
    pub normalize: bool,
    pub normalizer: Option<String>,
    pub rule_name: String,
}

impl MatchRule for ExactMatch {
    fn name(&self) -> &str {
        &self.rule_name
    }

    fn evaluate(&self, a: &NormalizedEntity, b: &NormalizedEntity) -> RuleResult {
        let val_a = a.data.get(&self.field).map(|v| v.as_str());
        let val_b = b.data.get(&self.field).map(|v| v.as_str());

        let matched = match (val_a, val_b) {
            (Some(a), Some(b)) => {
                let mut s_a = a.to_string();
                let mut s_b = b.to_string();

                if let Some(ref n) = self.normalizer {
                    s_a = apply_normalizer(n, &s_a);
                    s_b = apply_normalizer(n, &s_b);
                }

                if self.normalize {
                    s_a = s_a.trim().to_string();
                    s_b = s_b.trim().to_string();
                }

                if self.case_insensitive {
                    s_a = s_a.to_lowercase();
                    s_b = s_b.to_lowercase();
                }

                s_a == s_b
            },
            _ => false,
        };

        RuleResult {
            rule_name: self.rule_name.clone(),
            score: if matched { 1.0 } else { 0.0 },
            weight: self.weight,
            matched,
            explanation: if matched {
                format!("Exact match on field {}", self.field)
            } else {
                format!("No match on field {}", self.field)
            },
        }
    }
}

pub struct RangeMatch {
    pub field: String,
    pub tolerance: f64,
    pub weight: f64,
    pub normalizer: Option<String>,
    pub rule_name: String,
}

impl MatchRule for RangeMatch {
    fn name(&self) -> &str {
        &self.rule_name
    }

    fn evaluate(&self, a: &NormalizedEntity, b: &NormalizedEntity) -> RuleResult {
        fn parse_f64(s: &str) -> Option<f64> {
            s.parse::<f64>().ok()
        }

        let val_a = a.data.get(&self.field).and_then(|s| parse_f64(s));
        let val_b = b.data.get(&self.field).and_then(|s| parse_f64(s));

        let (matched, score) = match (val_a, val_b) {
            (Some(a), Some(b)) => {
                let diff = (a - b).abs();
                if diff <= self.tolerance {
                    (true, 1.0)
                } else {
                    (false, 0.0)
                }
            },
            _ => (false, 0.0),
        };

        RuleResult {
            rule_name: self.rule_name.clone(),
            score,
            weight: self.weight,
            matched,
            explanation: if matched {
                format!("Range match on field {} (diff within {})", self.field, self.tolerance)
            } else {
                format!("Range mismatch on field {}", self.field)
            },
        }
    }
}

pub struct FuzzyStringMatch {
    pub field: String,
    pub threshold: f64,
    pub weight: f64,
    pub normalizer: Option<String>,
    pub rule_name: String,
}

impl MatchRule for FuzzyStringMatch {
    fn name(&self) -> &str {
        &self.rule_name
    }

    fn evaluate(&self, a: &NormalizedEntity, b: &NormalizedEntity) -> RuleResult {
        let val_a = a.data.get(&self.field);
        let val_b = b.data.get(&self.field);

        let score = match (val_a, val_b) {
            (Some(a), Some(b)) => {
                let (a_str, b_str) = if let Some(ref n) = self.normalizer {
                    (apply_normalizer(n, a), apply_normalizer(n, b))
                } else {
                    (a.to_string(), b.to_string())
                };
                jaro_winkler(&a_str, &b_str)
            }
            _ => 0.0,
        };

        let matched = score >= self.threshold;

        RuleResult {
            rule_name: self.rule_name.clone(),
            score,
            weight: self.weight,
            matched,
            explanation: if matched {
                format!("Fuzzy match on field {} (score: {:.2})", self.field, score)
            } else {
                format!("Insufficient fuzzy score on field {} (score: {:.2})", self.field, score)
            },
        }
    }
}

pub struct LevenshteinMatch {
    pub field: String,
    pub threshold: f64,
    pub weight: f64,
    pub normalizer: Option<String>,
    pub rule_name: String,
}

impl MatchRule for LevenshteinMatch {
    fn name(&self) -> &str {
        &self.rule_name
    }

    fn evaluate(&self, a: &NormalizedEntity, b: &NormalizedEntity) -> RuleResult {
        let val_a = a.data.get(&self.field);
        let val_b = b.data.get(&self.field);

        let score = match (val_a, val_b) {
            (Some(a), Some(b)) => {
                let (a_str, b_str) = if let Some(ref n) = self.normalizer {
                    (apply_normalizer(n, a), apply_normalizer(n, b))
                } else {
                    (a.to_string(), b.to_string())
                };
                normalized_levenshtein(&a_str, &b_str)
            }
            _ => 0.0,
        };

        let matched = score >= self.threshold;

        RuleResult {
            rule_name: self.rule_name.clone(),
            score,
            weight: self.weight,
            matched,
            explanation: if matched {
                format!("Levenshtein match on field {} (score: {:.2})", self.field, score)
            } else {
                format!("Insufficient Levenshtein score on field {} (score: {:.2})", self.field, score)
            },
        }
    }
}

pub struct CompositeMatch {
    pub rules: Vec<Box<dyn MatchRule>>,
    pub weight: f64,
    pub require_all: bool,
    pub rule_name: String,
}

impl MatchRule for CompositeMatch {
    fn name(&self) -> &str {
        &self.rule_name
    }

    fn evaluate(&self, a: &NormalizedEntity, b: &NormalizedEntity) -> RuleResult {
        let results: Vec<RuleResult> = self.rules.iter().map(|r| r.evaluate(a, b)).collect();
        
        let matched = if self.require_all {
            results.iter().all(|r| r.matched)
        } else {
            results.iter().any(|r| r.matched)
        };

        let score = if results.is_empty() {
            0.0
        } else {
            results.iter().map(|r| r.score).sum::<f64>() / results.len() as f64
        };

        RuleResult {
            rule_name: self.rule_name.clone(),
            score,
            weight: self.weight,
            matched,
            explanation: format!(
                "Composite match ({} component rules, matched: {})",
                results.len(),
                matched
            ),
        }
    }
}
