//! Tests for pattern storage.

#[cfg(test)]
mod tests {
    use crate::learning::store::{LocalPatternStore, PatternStore};
    use crate::learning::types::{ClaimTemplate, LearnedPattern, ValueType};
    use crate::types::Language;
    use chrono::Utc;
    use tempfile::TempDir;

    fn create_test_pattern(normalized: &str, language: Language, project: &str) -> LearnedPattern {
        LearnedPattern::new(
            "example code",
            normalized,
            ClaimTemplate::new("test/subject", "predicate", ValueType::Text, "description"),
            language,
            project,
            0.85,
        )
    }

    #[test]
    fn test_store_creation() {
        let temp = TempDir::new().expect("temp dir");
        let store = LocalPatternStore::new(temp.path()).expect("create store");
        assert_eq!(store.pattern_count(), 0);
    }

    #[test]
    fn test_record_and_retrieve() {
        let temp = TempDir::new().expect("temp dir");
        let store = LocalPatternStore::new(temp.path()).expect("create store");

        let pattern = create_test_pattern("verify_ssl = <boolean>", Language::Python, "project1");
        store.record_pattern(&pattern, None).expect("record");

        assert_eq!(store.pattern_count(), 1);

        // Find similar
        let found = store.find_similar("verify_ssl = <boolean>", Language::Python, 0.8);
        assert!(found.is_some());
        assert_eq!(found.as_ref().map(|p| &p.id), Some(&pattern.id));

        // Different language should not match
        let not_found = store.find_similar("verify_ssl = <boolean>", Language::Go, 0.8);
        assert!(not_found.is_none());
    }

    #[test]
    fn test_persistence() {
        let temp = TempDir::new().expect("temp dir");

        // Create and populate store
        {
            let store = LocalPatternStore::new(temp.path()).expect("create store");
            let pattern = create_test_pattern("pool_size: <number>", Language::Yaml, "project1");
            store.record_pattern(&pattern, None).expect("record");
        }

        // Reopen and verify
        {
            let store = LocalPatternStore::new(temp.path()).expect("reopen store");
            assert_eq!(store.pattern_count(), 1);

            let found = store.find_similar("pool_size: <number>", Language::Yaml, 0.8);
            assert!(found.is_some());
        }
    }

    #[test]
    fn test_promotion_candidates() {
        let temp = TempDir::new().expect("temp dir");
        let store = LocalPatternStore::new(temp.path()).expect("create store");

        // Create pattern with few projects
        let mut pattern = create_test_pattern("tls_min = <string:version>", Language::Rust, "p1");
        store.record_pattern(&pattern, None).expect("record");

        // Should not be a candidate (only 1 project)
        let candidates = store.get_promotion_candidates(3, 0.8);
        assert!(candidates.is_empty());

        // Add more projects
        for i in 2..=4 {
            pattern.record_observation(format!("p{}", i), 0.9, Utc::now());
        }
        store.record_pattern(&pattern, None).expect("update");

        // Now should be a candidate
        let candidates = store.get_promotion_candidates(3, 0.8);
        assert_eq!(candidates.len(), 1);
    }

    #[test]
    fn test_mark_promoted() {
        let temp = TempDir::new().expect("temp dir");
        let store = LocalPatternStore::new(temp.path()).expect("create store");

        let pattern = create_test_pattern("skip_auth: <boolean>", Language::Python, "project1");
        let id = pattern.id;
        store.record_pattern(&pattern, None).expect("record");

        store.mark_promoted(&id, "skip_auth_extractor").expect("mark promoted");

        // Should no longer appear in candidates
        let candidates = store.get_promotion_candidates(0, 0.0);
        assert!(candidates.is_empty());

        // Should not match in find_similar (skip promoted)
        let found = store.find_similar("skip_auth: <boolean>", Language::Python, 0.8);
        assert!(found.is_none());
    }

    #[test]
    fn test_prune_stale() {
        let temp = TempDir::new().expect("temp dir");
        let store = LocalPatternStore::new(temp.path()).expect("create store");

        // Create an old pattern
        let mut old_pattern =
            create_test_pattern("old_setting: <number>", Language::Yaml, "project1");
        old_pattern.last_seen = Utc::now() - chrono::Duration::days(100);
        store.record_pattern(&old_pattern, None).expect("record old");

        // Create a recent pattern
        let new_pattern = create_test_pattern("new_setting: <number>", Language::Yaml, "project2");
        store.record_pattern(&new_pattern, None).expect("record new");

        assert_eq!(store.pattern_count(), 2);

        // Prune patterns older than 90 days
        let pruned = store.prune_stale(90).expect("prune");
        assert_eq!(pruned, 1);
        assert_eq!(store.pattern_count(), 1);

        // The new pattern should remain
        let found = store.find_similar("new_setting: <number>", Language::Yaml, 0.8);
        assert!(found.is_some());
    }

    #[test]
    fn test_prune_keeps_promoted() {
        let temp = TempDir::new().expect("temp dir");
        let store = LocalPatternStore::new(temp.path()).expect("create store");

        // Create an old but promoted pattern
        let mut promoted =
            create_test_pattern("promoted_setting: <boolean>", Language::Rust, "project1");
        promoted.last_seen = Utc::now() - chrono::Duration::days(200);
        promoted.promoted = true;
        promoted.promoted_to = Some("extractor_name".to_string());
        store.record_pattern(&promoted, None).expect("record promoted");

        assert_eq!(store.pattern_count(), 1);

        // Prune should keep promoted patterns
        let pruned = store.prune_stale(90).expect("prune");
        assert_eq!(pruned, 0);
        assert_eq!(store.pattern_count(), 1);
    }

    #[test]
    fn test_similarity_matching() {
        let temp = TempDir::new().expect("temp dir");
        let store = LocalPatternStore::new(temp.path()).expect("create store");

        let pattern = create_test_pattern("verify_ssl = <boolean>", Language::Python, "project1");
        store.record_pattern(&pattern, None).expect("record");

        // Exact match
        let found = store.find_similar("verify_ssl = <boolean>", Language::Python, 0.8);
        assert!(found.is_some());

        // Very similar (should match at 0.8 threshold)
        let found = store.find_similar("verify_ssl: <boolean>", Language::Python, 0.8);
        assert!(found.is_some());

        // Very different (should not match)
        let found = store.find_similar("something_completely_different", Language::Python, 0.8);
        assert!(found.is_none());
    }

    #[test]
    fn test_max_patterns_limit() {
        let temp = TempDir::new().expect("temp dir");
        let store = LocalPatternStore::new(temp.path()).expect("create store");

        // Use completely different patterns to avoid similarity matching confusion
        let mut p1 = create_test_pattern("verify_ssl = <boolean>", Language::Python, "project1");
        p1.last_seen = Utc::now() - chrono::Duration::hours(3); // oldest
        let mut p2 = create_test_pattern("pool_size: <number>", Language::Python, "project2");
        p2.last_seen = Utc::now() - chrono::Duration::hours(2);
        let mut p3 = create_test_pattern("timeout_ms = <number>", Language::Python, "project3");
        p3.last_seen = Utc::now() - chrono::Duration::hours(1);

        store.record_pattern(&p1, Some(3)).expect("record p1");
        store.record_pattern(&p2, Some(3)).expect("record p2");
        store.record_pattern(&p3, Some(3)).expect("record p3");

        assert_eq!(store.pattern_count(), 3);

        // Adding a 4th should evict the oldest (p1)
        let p4 = create_test_pattern("debug_mode: <boolean>", Language::Python, "project4");
        store.record_pattern(&p4, Some(3)).expect("record p4");

        // Still 3 patterns (oldest was evicted)
        assert_eq!(store.pattern_count(), 3);

        // p1 should have been evicted (oldest) - use exact match threshold
        let found_p1 = store.find_similar("verify_ssl = <boolean>", Language::Python, 0.99);
        assert!(found_p1.is_none(), "p1 should have been evicted");

        // p4 should exist (use exact match threshold)
        let found_p4 = store.find_similar("debug_mode: <boolean>", Language::Python, 0.99);
        assert!(found_p4.is_some(), "p4 should exist");
    }

    #[test]
    fn test_max_patterns_preserves_promoted() {
        let temp = TempDir::new().expect("temp dir");
        let store = LocalPatternStore::new(temp.path()).expect("create store");

        // Create a promoted pattern (should not be evicted) - use distinct pattern
        let mut promoted =
            create_test_pattern("verify_ssl = <boolean>", Language::Python, "project1");
        promoted.promoted = true;
        promoted.last_seen = Utc::now() - chrono::Duration::hours(2); // older
        store.record_pattern(&promoted, Some(2)).expect("record promoted");

        // Create another pattern (newer than promoted) - use distinct pattern
        let mut p2 = create_test_pattern("pool_size: <number>", Language::Python, "project2");
        p2.last_seen = Utc::now() - chrono::Duration::hours(1);
        store.record_pattern(&p2, Some(2)).expect("record p2");

        assert_eq!(store.pattern_count(), 2);

        // Add a third - should evict p2 (the only non-promoted pattern)
        let p3 = create_test_pattern("timeout_ms = <number>", Language::Python, "project3");
        store.record_pattern(&p3, Some(2)).expect("record p3");

        assert_eq!(store.pattern_count(), 2);

        // p2 should have been evicted (promoted is protected) - use exact threshold
        let found_p2 = store.find_similar("pool_size: <number>", Language::Python, 0.99);
        assert!(found_p2.is_none(), "p2 should have been evicted");

        // p3 should exist - use exact threshold
        let found_p3 = store.find_similar("timeout_ms = <number>", Language::Python, 0.99);
        assert!(found_p3.is_some(), "p3 should exist");
    }
}
