//! Pattern enrichment service.
//!
//! Matches patterns to extractor metadata and applies enrichment (category, verdict, explanation, authority).
//! Transforms "md5: true" → "MD5 is deprecated (NIST 2010)".

use std::collections::HashMap;

use crate::extractors::{ExtractorRegistry, PatternMetadata};

/// Pattern enrichment service.
///
/// Matches observations to metadata using:
/// 1. Exact match (tail_path + predicate + value)
/// 2. Wildcard match (tail_path + predicate, any value)
/// 3. Heuristic scoring (noise detection for unenriched patterns)
pub struct PatternEnricher {
    /// Exact matches: (tail_path, predicate, value) → metadata
    exact_matches: HashMap<(String, String, String), PatternMetadata>,

    /// Wildcard matches: (tail_path, predicate) → metadata
    wildcard_matches: HashMap<(String, String), PatternMetadata>,
}

impl PatternEnricher {
    /// Create a new enricher from an extractor registry.
    pub fn from_registry(registry: &ExtractorRegistry) -> Self {
        let mut exact_matches = HashMap::new();
        let mut wildcard_matches = HashMap::new();

        for extractor in registry.extractors() {
            for metadata in extractor.pattern_metadata() {
                let key_tail_pred = (metadata.tail_path.clone(), metadata.predicate.clone());

                if let Some(value) = &metadata.value {
                    // Exact match: tail_path + predicate + value
                    let key_exact =
                        (metadata.tail_path.clone(), metadata.predicate.clone(), value.clone());
                    exact_matches.insert(key_exact, metadata.clone());
                } else {
                    // Wildcard match: tail_path + predicate (any value)
                    wildcard_matches.insert(key_tail_pred, metadata);
                }
            }
        }

        Self { exact_matches, wildcard_matches }
    }

    /// Enrich a pattern with metadata.
    ///
    /// Returns (category, verdict, explanation, authority_source) if a match is found.
    pub fn enrich(&self, tail_path: &str, predicate: &str, value: &str) -> Option<Enrichment> {
        // 1. Try exact match first
        let key_exact = (tail_path.to_string(), predicate.to_string(), value.to_string());
        if let Some(metadata) = self.exact_matches.get(&key_exact) {
            return Some(Enrichment {
                category: Some(metadata.category.clone()),
                verdict: Some(metadata.verdict.clone()),
                explanation: Some(metadata.explanation.clone()),
                authority_source: metadata.authority_source.clone(),
            });
        }

        // 2. Try wildcard match (tail_path + predicate, any value)
        let key_wildcard = (tail_path.to_string(), predicate.to_string());
        if let Some(metadata) = self.wildcard_matches.get(&key_wildcard) {
            return Some(Enrichment {
                category: Some(metadata.category.clone()),
                verdict: Some(metadata.verdict.clone()),
                explanation: Some(metadata.explanation.clone()),
                authority_source: metadata.authority_source.clone(),
            });
        }

        // 3. Apply noise detection heuristics
        if Self::is_noise_pattern(tail_path, predicate) {
            return Some(Enrichment {
                category: Some("noise".to_string()),
                verdict: Some("noise".to_string()),
                explanation: Some("Common pattern with low signal".to_string()),
                authority_source: None,
            });
        }

        None
    }

    /// Detect noise patterns using heuristics.
    ///
    /// Noise patterns include:
    /// - Standard library imports (std, core, tokio, serde)
    /// - Generic predicates (enabled, present)
    /// - Common infrastructure patterns
    fn is_noise_pattern(tail_path: &str, _predicate: &str) -> bool {
        // Common noise patterns: std library imports, generic imports
        let noise_patterns = [
            "imports/std",
            "imports/core",
            "imports/alloc",
            "imports/serde",
            "imports/tokio",
            "imports/async_trait",
            "imports/tracing",
            "imports/anyhow",
            "imports/thiserror",
        ];

        for pattern in &noise_patterns {
            if tail_path.contains(pattern) {
                return true;
            }
        }

        false
    }
}

/// Enrichment metadata for a pattern.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enrichment {
    /// Pattern category (e.g., "security", "architecture", "performance").
    pub category: Option<String>,
    /// Verdict (e.g., "deprecated", "recommended", "emerging", "common", "noise").
    pub verdict: Option<String>,
    /// Human-readable explanation.
    pub explanation: Option<String>,
    /// Authority source (e.g., "RFC 8996", "NIST 2010").
    pub authority_source: Option<String>,
}

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

    #[test]
    fn test_exact_match() {
        // Create a default config and registry (includes WeakCryptoExtractor)
        let config = AphoriaConfig::default();
        let registry = ExtractorRegistry::new(&config);
        let enricher = PatternEnricher::from_registry(&registry);

        // WeakCryptoExtractor provides MD5 metadata
        let enrichment = enricher
            .enrich("crypto/hashing/algorithm", "algorithm", "md5")
            .expect("Should match MD5");

        assert_eq!(enrichment.category, Some("security".to_string()));
        assert_eq!(enrichment.verdict, Some("deprecated".to_string()));
        assert!(enrichment.explanation.is_some());
        assert!(enrichment.authority_source.is_some());
    }

    #[test]
    fn test_wildcard_match() {
        // TLS version extractor provides metadata for all TLS versions
        let config = AphoriaConfig::default();
        let registry = ExtractorRegistry::new(&config);
        let enricher = PatternEnricher::from_registry(&registry);

        // Match TLS 1.0 (should be deprecated)
        let enrichment =
            enricher.enrich("tls/min_version", "version", "1.0").expect("Should match TLS 1.0");

        assert_eq!(enrichment.category, Some("security".to_string()));
        assert_eq!(enrichment.verdict, Some("deprecated".to_string()));
        assert!(enrichment.authority_source.is_some());
    }

    #[test]
    fn test_noise_detection() {
        let config = AphoriaConfig::default();
        let registry = ExtractorRegistry::new(&config);
        let enricher = PatternEnricher::from_registry(&registry);

        let enrichment =
            enricher.enrich("imports/std", "imported", "true").expect("Should detect noise");

        assert_eq!(enrichment.category, Some("noise".to_string()));
        assert_eq!(enrichment.verdict, Some("noise".to_string()));
    }

    #[test]
    fn test_no_match() {
        let config = AphoriaConfig::default();
        let registry = ExtractorRegistry::new(&config);
        let enricher = PatternEnricher::from_registry(&registry);

        let enrichment = enricher.enrich("custom/path", "custom_pred", "value");

        assert!(enrichment.is_none());
    }
}
