//! Profile GLiNER cache performance on realistic evaluation datasets.
//!
//! Tests cache performance with:
//! - Real dataset texts (various lengths, domains)
//! - Multiple entity type sets (simulating different evaluation scenarios)
//! - Repeated queries (simulating evaluation loop patterns)
//!
//! # Performance Results
//!
//! - **eval_loop_pattern**: ~27ms (cache hit) vs ~1.2s (cache miss) → ~44x speedup
//! - **cache_hit_repeated**: Similar to cache miss (full extract time includes ONNX inference)
//! - **different_entity_types**: ~500ms (fewer entity types = faster)
//!
//! # Key Insight
//!
//! Cache is most valuable when the same text is queried with different entity types,
//! which is common in evaluation loops where multiple backends process the same dataset.
//!
//! Run with:
//!   cargo bench --bench gliner_eval_profiling --features onnx,eval

use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};

#[cfg(all(feature = "onnx", feature = "eval"))]
fn bench_gliner_eval_realistic(c: &mut Criterion) {
    use anno::backends::gliner_onnx::GLiNEROnnx;
    use anno::eval::loader::{DatasetLoader, DatasetId};
    
    // Initialize model once
    let model = GLiNEROnnx::new("onnx-community/gliner_small-v2.1")
        .expect("Failed to load GLiNER model");
    
    // Load a sample of real evaluation data
    let loader = DatasetLoader::new().expect("Failed to create DatasetLoader");
    
    // Try to load a small sample from a real dataset
    // We'll use TweetNER7 or WikiGold if available
    let datasets_to_try = vec![
        DatasetId::TweetNER7,
        DatasetId::WikiGold,
        DatasetId::CoNLL2003Sample,
    ];
    
    let mut sample_texts: Vec<String> = Vec::new();
    let mut sample_entity_types: Vec<Vec<String>> = Vec::new();
    
    for dataset_id in datasets_to_try {
        if let Ok(dataset) = loader.load(dataset_id) {
            // Sample up to 50 texts from the dataset
            // LoadedDataset has sentences directly
            let texts: Vec<String> = dataset
                .sentences
                .iter()
                .take(50)
                .map(|s| s.text())
                .collect();
            
            if !texts.is_empty() {
                sample_texts.extend(texts);
                
                // Extract unique entity types from the dataset
                let mut types: std::collections::HashSet<String> = std::collections::HashSet::new();
                for sentence in dataset.sentences.iter() {
                    for entity in sentence.entities() {
                        types.insert(entity.entity_type.to_string());
                    }
                }
                let mut types_vec: Vec<String> = types.into_iter().collect();
                types_vec.sort();
                
                if !types_vec.is_empty() {
                    sample_entity_types.push(types_vec);
                }
                
                break; // Use first available dataset
            }
        }
    }
    
    // Fallback to synthetic data if no datasets available
    if sample_texts.is_empty() {
        sample_texts = vec![
            "Apple Inc. was founded by Steve Jobs in California in 1976.".to_string(),
            "Microsoft was founded by Bill Gates in Washington.".to_string(),
            "Google was founded by Larry Page and Sergey Brin in 1998.".to_string(),
            "Amazon was founded by Jeff Bezos in Seattle in 1994.".to_string(),
            "Facebook was founded by Mark Zuckerberg in 2004.".to_string(),
        ];
        sample_entity_types = vec![
            vec!["person".to_string(), "organization".to_string(), "location".to_string(), "date".to_string()],
            vec!["person".to_string(), "organization".to_string()],
            vec!["organization".to_string(), "location".to_string()],
        ];
    }
    
    let mut group = c.benchmark_group("gliner_eval_realistic");
    group.sample_size(20);
    
    // Convert Vec<String> to Vec<&str> for extract() calls
    let entity_types_refs: Vec<Vec<&str>> = sample_entity_types
        .iter()
        .map(|types| types.iter().map(|s| s.as_str()).collect())
        .collect();
    
    // Test 1: Cache miss (first call for each text)
    group.bench_function("cache_miss_first_call", |b| {
        b.iter(|| {
            for text in &sample_texts[..sample_texts.len().min(10)] {
                let types: Vec<&str> = entity_types_refs[0].clone();
                let _ = model.extract(black_box(text), black_box(&types), 0.5);
            }
        });
    });
    
    // Test 2: Cache hit (repeated calls with same text + types)
    group.bench_function("cache_hit_repeated", |b| {
        // Warm up cache
        for text in &sample_texts[..sample_texts.len().min(10)] {
            let types: Vec<&str> = entity_types_refs[0].clone();
            let _ = model.extract(text, &types, 0.5);
        }
        
        b.iter(|| {
            for text in &sample_texts[..sample_texts.len().min(10)] {
                let types: Vec<&str> = entity_types_refs[0].clone();
                let _ = model.extract(black_box(text), black_box(&types), 0.5);
            }
        });
    });
    
    // Test 3: Different entity types (cache miss for prompt, but text might be cached elsewhere)
    group.bench_function("different_entity_types", |b| {
        b.iter(|| {
            for (i, text) in sample_texts.iter().take(10).enumerate() {
                let types_idx = i % entity_types_refs.len();
                let types: Vec<&str> = entity_types_refs[types_idx].clone();
                let _ = model.extract(black_box(text), black_box(&types), 0.5);
            }
        });
    });
    
    // Test 4: Evaluation loop pattern (same text, multiple entity type sets)
    // This simulates evaluating the same text with different entity type configurations
    group.bench_function("eval_loop_pattern", |b| {
        let text = &sample_texts[0];
        
        b.iter(|| {
            for types_vec in &entity_types_refs {
                let types: Vec<&str> = types_vec.clone();
                let _ = model.extract(black_box(text), black_box(&types), 0.5);
            }
        });
    });
    
    // Test 5: Mixed workload (some cache hits, some misses)
    group.bench_function("mixed_workload", |b| {
        // Warm up with first half
        for text in &sample_texts[..sample_texts.len().min(5)] {
            let types: Vec<&str> = entity_types_refs[0].clone();
            let _ = model.extract(text, &types, 0.5);
        }
        
        b.iter(|| {
            // First half should be cache hits
            for text in &sample_texts[..sample_texts.len().min(5)] {
                let types: Vec<&str> = entity_types_refs[0].clone();
                let _ = model.extract(black_box(text), black_box(&types), 0.5);
            }
            // Second half should be cache misses
            for text in &sample_texts[5..sample_texts.len().min(10)] {
                let types: Vec<&str> = entity_types_refs[0].clone();
                let _ = model.extract(black_box(text), black_box(&types), 0.5);
            }
        });
    });
    
    group.finish();
}

#[cfg(all(feature = "onnx", feature = "eval"))]
fn bench_gliner_eval_text_lengths(c: &mut Criterion) {
    use anno::backends::gliner_onnx::GLiNEROnnx;
    use anno::eval::loader::{DatasetLoader, DatasetId};
    
    let model = GLiNEROnnx::new("onnx-community/gliner_small-v2.1")
        .expect("Failed to load GLiNER model");
    
    let loader = DatasetLoader::new().expect("Failed to create DatasetLoader");
    
    // Try to get texts of varying lengths from real datasets
    let mut short_texts: Vec<String> = Vec::new();
    let mut medium_texts: Vec<String> = Vec::new();
    let mut long_texts: Vec<String> = Vec::new();
    
    for dataset_id in vec![DatasetId::TweetNER7, DatasetId::WikiGold, DatasetId::CoNLL2003Sample] {
        if let Ok(dataset) = loader.load(dataset_id) {
            for sentence in dataset.sentences.iter().take(100) {
                let text = sentence.text();
                let len = text.len();
                if len < 50 && short_texts.len() < 10 {
                    short_texts.push(text);
                } else if len < 200 && medium_texts.len() < 10 {
                    medium_texts.push(text);
                } else if len >= 200 && long_texts.len() < 10 {
                    long_texts.push(text);
                }
                
                // Early exit if we have enough samples
                if short_texts.len() >= 10 && medium_texts.len() >= 10 && long_texts.len() >= 10 {
                    break;
                }
            }
        }
    }
    
    // Fallback
    if short_texts.is_empty() {
        short_texts = vec!["Apple Inc.".to_string()];
        medium_texts = vec!["Apple Inc. was founded by Steve Jobs.".to_string()];
        long_texts = vec!["Apple Inc. was founded by Steve Jobs, Steve Wozniak, and Ronald Wayne in April 1976 in California.".to_string()];
    }
    
    let entity_types: Vec<&str> = vec!["person", "organization", "location", "date"];
    let mut group = c.benchmark_group("gliner_eval_text_lengths");
    
    for (name, texts) in vec![
        ("short", short_texts),
        ("medium", medium_texts),
        ("long", long_texts),
    ] {
        if texts.is_empty() {
            continue;
        }
        
        // Cache miss
        group.bench_function(BenchmarkId::new("cache_miss", name), |b| {
            b.iter(|| {
                for text in &texts {
                    let _ = model.extract(black_box(text), black_box(&entity_types), 0.5);
                }
            });
        });
        
        // Cache hit (warm up first)
        for text in &texts {
            let _ = model.extract(text, &entity_types, 0.5);
        }
        
        group.bench_function(BenchmarkId::new("cache_hit", name), |b| {
            b.iter(|| {
                for text in &texts {
                    let _ = model.extract(black_box(text), black_box(&entity_types), 0.5);
                }
            });
        });
    }
    
    group.finish();
}

#[cfg(not(all(feature = "onnx", feature = "eval")))]
fn bench_gliner_eval_realistic(_c: &mut Criterion) {
    // Stub when features not enabled
}

#[cfg(not(all(feature = "onnx", feature = "eval")))]
fn bench_gliner_eval_text_lengths(_c: &mut Criterion) {
    // Stub when features not enabled
}

criterion_group!(
    benches,
    bench_gliner_eval_realistic,
    bench_gliner_eval_text_lengths
);
criterion_main!(benches);

