//! Matching Rule Tests
//! Tests RULE-001 thru RULE-010

use std::collections::HashMap;
use uuid::Uuid;
use chrono::Utc;
use cannon_core::types::NormalizedEntity;
use cannon_core::matching::{MatchRule, ExactMatch, LevenshteinMatch, FuzzyStringMatch, RangeMatch, DomainMatch, CompositeMatch};

// Helper to create a normalized entity
fn create_entity(data: Vec<(&str, &str)>) -> NormalizedEntity {
    let mut map = HashMap::new();
    for (k, v) in data {
        map.insert(k.to_string(), v.to_string());
    }
    NormalizedEntity {
        id: Uuid::new_v4(),
        tenant_id: Uuid::new_v4(),
        external_id: "ext".to_string(),
        entity_type: "customer".to_string(),
        data: map,
        source_name: "test".to_string(),
        valid_from: None,
        valid_to: None,
        last_updated: Utc::now(),
    }
}

// ============================================================================
// RULE-001: Exact Match (Success)
// ============================================================================
#[test]
fn rule_001_exact_match_success() {
    let a = create_entity(vec![("email", "test@example.com")]);
    let b = create_entity(vec![("email", "test@example.com")]);
    let rule = ExactMatch {
        field: "email".to_string(),
        weight: 1.0,
        case_insensitive: true,
        normalize: true,
        normalizer: None,
        rule_name: "email_exact".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    assert!(result.matched);
    assert_eq!(result.score, 1.0);
}

// ============================================================================
// RULE-002: Exact Match (Mismatch)
// ============================================================================
#[test]
fn rule_002_exact_match_mismatch() {
    let a = create_entity(vec![("email", "test@example.com")]);
    let b = create_entity(vec![("email", "other@example.com")]);
    let rule = ExactMatch {
        field: "email".to_string(),
        weight: 1.0,
        case_insensitive: true,
        normalize: true,
        normalizer: None,
        rule_name: "email_exact".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    assert!(!result.matched);
    assert_eq!(result.score, 0.0);
}

// ============================================================================
// RULE-003: Similarity - Levenshtein
// ============================================================================
#[test]
fn rule_003_levenshtein_similarity() {
    let a = create_entity(vec![("name", "John Doe")]);
    let b = create_entity(vec![("name", "Jon Doe")]);
    let rule = LevenshteinMatch {
        field: "name".to_string(),
        threshold: 0.8,
        weight: 1.0,
        normalizer: None,
        rule_name: "name_levenshtein".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    assert!(result.matched);
    assert!(result.score > 0.8 && result.score < 1.0);
}

// ============================================================================
// RULE-004: Similarity - Jaro-Winkler
// ============================================================================
#[test]
fn rule_004_jaro_winkler_similarity() {
    let a = create_entity(vec![("name", "MARTHA")]);
    let b = create_entity(vec![("name", "MARHTA")]);
    let rule = FuzzyStringMatch {
        field: "name".to_string(),
        threshold: 0.9,
        weight: 1.0,
        normalizer: None,
        rule_name: "name_fuzzy".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    assert!(result.matched);
    assert!(result.score > 0.9);
}

// ============================================================================
// RULE-005: Range Match (Numeric)
// ============================================================================
#[test]
fn rule_005_range_match_numeric() {
    let a = create_entity(vec![("age", "30")]);
    let b = create_entity(vec![("age", "32")]);
    let rule = RangeMatch {
        field: "age".to_string(),
        tolerance: 5.0,
        weight: 1.0,
        normalizer: None,
        rule_name: "age_range".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    assert!(result.matched);
    assert_eq!(result.score, 1.0);
}

// ============================================================================
// RULE-006: Domain Match (Email)
// ============================================================================
#[test]
fn rule_006_domain_match() {
    let a = create_entity(vec![("email", "user1@gmail.com")]);
    let b = create_entity(vec![("email", "user2@gmail.com")]);
    let rule = DomainMatch {
        fields: vec!["email".to_string()],
        weight: 1.0,
        rule_name: "email_domain".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    assert!(result.matched);
    assert_eq!(result.score, 1.0);
}

// ============================================================================
// RULE-007: Composite Match (AND)
// ============================================================================
#[test]
fn rule_007_composite_and_match() {
    let a = create_entity(vec![("email", "test@example.com"), ("name", "John")]);
    let b = create_entity(vec![("email", "test@example.com"), ("name", "John")]);

    let sub_rule1 = Box::new(ExactMatch {
        field: "email".to_string(),
        weight: 1.0,
        case_insensitive: true,
        normalize: true,
        normalizer: None,
        rule_name: "email_exact".to_string(),
    });
    let sub_rule2 = Box::new(ExactMatch {
        field: "name".to_string(),
        weight: 1.0,
        case_insensitive: true,
        normalize: true,
        normalizer: None,
        rule_name: "name_exact".to_string(),
    });

    let rule = CompositeMatch {
        rules: vec![sub_rule1, sub_rule2],
        weight: 1.0,
        require_all: true,
        rule_name: "email_and_name".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    assert!(result.matched);
    assert_eq!(result.score, 1.0);
}

// ============================================================================
// RULE-008: Composite Match (OR)
// ============================================================================
#[test]
fn rule_008_composite_or_match() {
    let a = create_entity(vec![("email", "test@example.com"), ("name", "John")]);
    let b = create_entity(vec![("email", "other@example.com"), ("name", "John")]);

    let sub_rule1 = Box::new(ExactMatch {
        field: "email".to_string(),
        weight: 1.0,
        case_insensitive: true,
        normalize: true,
        normalizer: None,
        rule_name: "email_exact".to_string(),
    });
    let sub_rule2 = Box::new(ExactMatch {
        field: "name".to_string(),
        weight: 1.0,
        case_insensitive: true,
        normalize: true,
        normalizer: None,
        rule_name: "name_exact".to_string(),
    });

    let rule = CompositeMatch {
        rules: vec![sub_rule1, sub_rule2],
        weight: 1.0,
        require_all: false,
        rule_name: "email_or_name".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    assert!(result.matched);
    assert_eq!(result.score, 0.5); // (0.0 + 1.0) / 2
}

// ============================================================================
// RULE-009: Missing Field Handling
// ============================================================================
#[test]
fn rule_009_missing_field_handling() {
    let a = create_entity(vec![("email", "test@example.com")]);
    let b = create_entity(vec![]); // Missing email
    let rule = ExactMatch {
        field: "email".to_string(),
        weight: 1.0,
        case_insensitive: true,
        normalize: true,
        normalizer: None,
        rule_name: "email_exact".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    assert!(!result.matched);
    assert_eq!(result.score, 0.0);
}

// ============================================================================
// RULE-010: Null/Empty Value Handling
// ============================================================================
#[test]
fn rule_010_empty_value_handling() {
    let a = create_entity(vec![("email", "")]);
    let b = create_entity(vec![("email", "  ")]);
    let rule = ExactMatch {
        field: "email".to_string(),
        weight: 1.0,
        case_insensitive: true,
        normalize: true,
        normalizer: None,
        rule_name: "email_exact".to_string(),
    };

    let result = rule.evaluate(&a, &b);
    // Both are empty/whitespace, with normalization they should match
    assert!(result.matched);
}
