//! Integration tests for the Research Agent.

use tempfile::TempDir;

use super::*;
use crate::episteme::ConceptIndex;
use crate::types::ExtractedClaim;
use stemedb_core::types::ObjectValue;

fn make_claim(concept_path: &str, predicate: &str) -> ExtractedClaim {
    ExtractedClaim {
        concept_path: concept_path.to_string(),
        predicate: predicate.to_string(),
        value: ObjectValue::Boolean(true),
        file: "test.rs".to_string(),
        line: 42,
        matched_text: "test config".to_string(),
        confidence: 0.9,
        description: format!("Configuration for {}", concept_path),
    }
}

#[test]
fn test_gap_detection_integration() {
    // Create an empty index (no authoritative coverage)
    let index = ConceptIndex::build(&[]);

    // Create claims for topics with no coverage
    let claims = vec![
        make_claim("code://rust/myapp/redis/max_memory_policy", "config_value"),
        make_claim("code://rust/myapp/kafka/retention_ms", "config_value"),
        make_claim("code://rust/myapp/mongo/connection_timeout", "config_value"),
    ];

    // Detect gaps
    let gaps = detect_gaps(&claims, &index);

    assert_eq!(gaps.len(), 3);
    assert!(gaps.iter().any(|g| g.topic == "redis/max_memory_policy"));
    assert!(gaps.iter().any(|g| g.topic == "kafka/retention_ms"));
    assert!(gaps.iter().any(|g| g.topic == "mongo/connection_timeout"));
}

#[test]
fn test_gap_store_integration() {
    let temp_dir = TempDir::new().unwrap();
    let store_path = temp_dir.path().join("gaps.json");

    // Create gaps from multiple projects
    let gap = Gap {
        concept_path: "code://rust/test/redis/max_memory".to_string(),
        predicate: "config_value".to_string(),
        topic: "redis/max_memory".to_string(),
        source_file: "test.rs".to_string(),
        source_line: 1,
        description: "Redis max memory config".to_string(),
        confidence: 0.9,
    };

    // Open store and record gaps from multiple projects
    let mut store = GapStore::open(&store_path).unwrap();

    store.record_gaps(std::slice::from_ref(&gap), "project1");
    store.record_gaps(std::slice::from_ref(&gap), "project2");
    store.record_gaps(std::slice::from_ref(&gap), "project3");

    // Save and reopen
    store.save().unwrap();
    drop(store);

    // Verify persistence
    let store = GapStore::open(&store_path).unwrap();
    let record = store.get("redis/max_memory::config_value").unwrap();

    assert_eq!(record.project_count, 3);
    assert!(record.projects.contains(&"project1".to_string()));
    assert!(record.projects.contains(&"project2".to_string()));
    assert!(record.projects.contains(&"project3".to_string()));
}

#[test]
fn test_research_candidate_selection() {
    let temp_dir = TempDir::new().unwrap();
    let store_path = temp_dir.path().join("gaps.json");

    let mut store = GapStore::open(&store_path).unwrap();

    // Add gap seen in 5 projects (should be candidate)
    let high_gap = Gap {
        concept_path: "code://rust/test/redis/max_memory".to_string(),
        predicate: "config_value".to_string(),
        topic: "redis/max_memory".to_string(),
        source_file: "test.rs".to_string(),
        source_line: 1,
        description: "Common gap".to_string(),
        confidence: 0.9,
    };

    for i in 1..=5 {
        store.record_gaps(std::slice::from_ref(&high_gap), &format!("project{}", i));
    }

    // Add gap seen in only 1 project (not candidate)
    let low_gap = Gap {
        concept_path: "code://rust/test/obscure/setting".to_string(),
        predicate: "config_value".to_string(),
        topic: "obscure/setting".to_string(),
        source_file: "test.rs".to_string(),
        source_line: 1,
        description: "Rare gap".to_string(),
        confidence: 0.9,
    };

    store.record_gaps(&[low_gap], "project1");

    // With threshold 3, only high_gap should be candidate
    let candidates = store.get_research_candidates(3);
    assert_eq!(candidates.len(), 1);
    assert_eq!(candidates[0].topic, "redis/max_memory");
}

#[test]
fn test_quality_validation_integration() {
    use super::researcher::ResearchedClaim;

    let validator = QualityValidator::default();

    // High quality claim
    let good_claim = ResearchedClaim {
        subject: "vendor://redis/max_memory/policy".to_string(),
        predicate: "config_value".to_string(),
        value: ObjectValue::Text("volatile-lru".to_string()),
        description: "Redis max_memory_policy MUST be set to 'volatile-lru' for cache workloads to ensure proper eviction behavior".to_string(),
        source_url: "https://redis.io/docs/management/config/".to_string(),
        source_section: "Memory Management".to_string(),
        confidence: 0.95,
        tier: 2,
    };

    // Low quality claim (vague, no normative language)
    let bad_claim = ResearchedClaim {
        subject: "vendor://redis/config".to_string(),
        predicate: "setting".to_string(),
        value: ObjectValue::Boolean(true),
        description: "It depends".to_string(),
        source_url: "".to_string(),
        source_section: "".to_string(),
        confidence: 0.5,
        tier: 2,
    };

    let report = validator.validate(&[good_claim.clone(), bad_claim.clone()]);

    assert_eq!(report.passed, 1);
    assert_eq!(report.failed, 1);

    // Filter should only return the good claim
    let filtered = validator.filter_passed(vec![good_claim, bad_claim]);
    assert_eq!(filtered.len(), 1);
    assert_eq!(filtered[0].subject, "vendor://redis/max_memory/policy");
}

#[test]
fn test_end_to_end_research_flow() {
    let temp_dir = TempDir::new().unwrap();
    let store_path = temp_dir.path().join("gaps.json");

    // 1. Detect gaps from code claims
    let index = ConceptIndex::build(&[]);
    let claims = vec![
        make_claim("code://rust/app1/redis/connection_pool", "pool_size"),
        make_claim("code://rust/app2/redis/connection_pool", "pool_size"),
        make_claim("code://rust/app3/redis/connection_pool", "pool_size"),
    ];

    let gaps = detect_gaps(&claims, &index);
    assert_eq!(gaps.len(), 1);

    // 2. Store gaps from multiple projects
    let mut store = GapStore::open(&store_path).unwrap();
    store.record_gaps(&gaps, "app1");
    store.record_gaps(&gaps, "app2");
    store.record_gaps(&gaps, "app3");

    // 3. Check for research candidates
    let candidates = store.get_research_candidates(3);
    assert_eq!(candidates.len(), 1);

    // 4. Research would be triggered here (not actually calling network in tests)
    // The Researcher would:
    // - Find sources for "redis/connection_pool"
    // - Fetch documentation
    // - Extract normative statements
    // - Validate quality
    // - Return high-quality claims

    // Verify the gap record is ready for research
    let record = candidates[0];
    assert!(record.is_eligible_for_research(3));
    assert!(!record.research_attempted);
}

#[test]
fn test_gap_pruning() {
    let temp_dir = TempDir::new().unwrap();
    let store_path = temp_dir.path().join("gaps.json");

    let mut store = GapStore::open(&store_path).unwrap();

    // Add a gap
    let gap = Gap {
        concept_path: "code://rust/test/old/setting".to_string(),
        predicate: "config_value".to_string(),
        topic: "old/setting".to_string(),
        source_file: "test.rs".to_string(),
        source_line: 1,
        description: "Old gap".to_string(),
        confidence: 0.9,
    };

    store.record_gaps(&[gap], "project1");
    assert_eq!(store.len(), 1);

    // Prune with 0 days max age (should remove everything not researched)
    store.prune_old_gaps(0);
    assert_eq!(store.len(), 0);
}

#[test]
fn test_research_outcome_aggregation() {
    let mut outcome = ResearchOutcome::empty();

    // Simulate research results
    outcome.gaps_analyzed = 3;
    outcome.gaps_filled = 2;
    outcome.assertions_created = 5;
    outcome.gaps_failed = vec!["failed/topic".to_string()];

    assert!(outcome.has_results());
    assert_eq!(outcome.gaps_failed.len(), 1);
}
