//! Exit criteria integration tests for Milestone 6: Geospatial.

use luci_index::index::Index;
use luci_mapping::{FieldType,Mapping};
use serde_json::json;

fn test_dir(name: &str) -> std::path::PathBuf {
    let dir = std::env::temp_dir().join(format!(
        "luci_m6_integration_{}_{name}",
        std::process::id()
    ));
    let _ = std::fs::remove_dir_all(&dir);
    dir
}

fn cleanup(path: &std::path::Path) {
    let _ = std::fs::remove_dir_all(path);
}

fn build_geo_index(name: &str) -> (std::path::PathBuf, Index) {
    let path = test_dir(name);
    let schema = Mapping::builder()
        .field("name", FieldType::Text)
        .field("city", FieldType::Keyword)
        .field("location", FieldType::GeoPoint)
        .build();
    let mut index = Index::create_with_mapping(&path, schema).unwrap();

    // Real city coordinates
    index.add(json!({"name": "Times Square", "city": "nyc", "location": {"lat": 40.7580, "lon": -73.9855}})).unwrap();
    index.add(json!({"name": "Central Park", "city": "nyc", "location": {"lat": 40.7829, "lon": -73.9654}})).unwrap();
    index.add(json!({"name": "Brooklyn Bridge", "city": "nyc", "location": {"lat": 40.7061, "lon": -73.9969}})).unwrap();
    index.add(json!({"name": "Big Ben", "city": "london", "location": {"lat": 51.5007, "lon": -0.1246}})).unwrap();
    index.add(json!({"name": "Tower Bridge", "city": "london", "location": {"lat": 51.5055, "lon": -0.0754}})).unwrap();
    index.add(json!({"name": "Eiffel Tower", "city": "paris", "location": {"lat": 48.8584, "lon": 2.2945}})).unwrap();

    index.commit().unwrap();
    (path, index)
}

/// Exit Criterion 1: geo_distance query finds nearby points.
#[test]
fn geo_distance_query() {
    let (path, mut index) = build_geo_index("distance");

    // Find places within 5km of Times Square
    let results = index.search(json!({
        "geo_distance": {
            "distance": "5km",
            "location": {"lat": 40.7580, "lon": -73.9855}
        }
    }), 10).unwrap();

    // Should find NYC places only — London/Paris are 5000+ km away
    assert!(results.total_hits.value >= 2, "should find nearby NYC places, got {}", results.total_hits.value);
    assert!(results.total_hits.value <= 3);

    // Verify all returned docs are NYC, not London or Paris
    for hit in &results.hits {
        let source = hit.source.as_ref().unwrap();
        assert_eq!(source["city"], "nyc",
            "5km from Times Square should only return NYC places, got {:?}", source["name"]);
    }

    cleanup(&path);
}

/// Exit Criterion 2: geo_distance with large radius.
#[test]
fn geo_distance_large_radius() {
    let (path, mut index) = build_geo_index("large_radius");

    // Find places within 1000km of London — should include Paris (~340km)
    let results = index.search(json!({
        "geo_distance": {
            "distance": "1000km",
            "location": {"lat": 51.5, "lon": -0.1}
        }
    }), 10).unwrap();

    // London (2 places) + Paris (1 place) = 3
    assert!(results.total_hits.value >= 3, "should find London + Paris, got {}", results.total_hits.value);

    // Verify London and Paris are both represented
    let cities: Vec<&str> = results.hits.iter()
        .filter_map(|h| h.source.as_ref()?.get("city")?.as_str())
        .collect();
    assert!(cities.contains(&"london"), "should include London places");
    assert!(cities.contains(&"paris"), "should include Paris places");
    // NYC is ~5500km away — should NOT appear
    assert!(!cities.contains(&"nyc"), "NYC is too far from London for 1000km radius");

    cleanup(&path);
}

/// Exit Criterion 3: geo_bounding_box query.
#[test]
fn geo_bounding_box_query() {
    let (path, mut index) = build_geo_index("bbox");

    // Bounding box around Manhattan
    let results = index.search(json!({
        "geo_bounding_box": {
            "location": {
                "top_left": {"lat": 40.80, "lon": -74.02},
                "bottom_right": {"lat": 40.70, "lon": -73.95}
            }
        }
    }), 10).unwrap();

    // Should find NYC places within the box
    assert!(results.total_hits.value >= 2, "should find Manhattan places, got {}", results.total_hits.value);

    // Verify all returned docs are NYC
    for hit in &results.hits {
        let source = hit.source.as_ref().unwrap();
        assert_eq!(source["city"], "nyc",
            "Manhattan bounding box should only return NYC places, got {:?}", source["name"]);
    }

    cleanup(&path);
}

/// Exit Criterion 4: geo query combined with text query.
#[test]
fn geo_plus_text_query() {
    let (path, mut index) = build_geo_index("combined");

    // Bool: must match "bridge" AND be within 100km of London
    let results = index.search(json!({
        "bool": {
            "must": [{"match": {"name": "bridge"}}],
            "filter": [{
                "geo_distance": {
                    "distance": "100km",
                    "location": {"lat": 51.5, "lon": -0.1}
                }
            }]
        }
    }), 10).unwrap();

    // Only Tower Bridge is a bridge near London
    assert_eq!(results.total_hits.value, 1);
    let source = results.hits[0].source.as_ref().unwrap();
    assert_eq!(source["name"], "Tower Bridge",
        "bridge + London geo filter should return Tower Bridge");

    cleanup(&path);
}
