//! Exit criteria integration tests for Milestone 7: Nested Documents.

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_m7_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);
}

/// Exit Criterion 1: Cross-object matching prevented.
///
/// Without nested, searching for seller=Alice AND price=1299 would incorrectly
/// match because the field values are flattened. With nested, each offer is
/// isolated.
#[test]
fn cross_object_matching_prevented() {
    let path = test_dir("cross_object");
    let schema = Mapping::builder()
        .field("product", FieldType::Text)
        .field("offers", FieldType::Nested)
        .field("offers.seller", FieldType::Keyword)
        .field("offers.price", FieldType::Keyword)
        .build();
    let mut index = Index::create_with_mapping(&path, schema).unwrap();

    index.add(json!({
        "product": "laptop",
        "offers": [
            {"seller": "Alice", "price": "999"},
            {"seller": "Bob", "price": "1299"}
        ]
    })).unwrap();
    index.commit().unwrap();

    // This should NOT match: Alice sells at 999, Bob at 1299.
    // With proper nested isolation, seller=Alice AND price=1299 are in
    // different nested objects and shouldn't intersect.
    let results = index.search(json!({
        "nested": {
            "path": "offers",
            "query": {
                "bool": {
                    "must": [
                        {"term": {"offers.seller": "Alice"}},
                        {"term": {"offers.price": "1299"}}
                    ]
                }
            }
        }
    }), 10).unwrap();

    assert_eq!(results.total_hits.value, 0,
        "cross-object match should be prevented: Alice doesn't sell at 1299");

    // This SHOULD match: Alice sells at 999
    let results = index.search(json!({
        "nested": {
            "path": "offers",
            "query": {
                "bool": {
                    "must": [
                        {"term": {"offers.seller": "Alice"}},
                        {"term": {"offers.price": "999"}}
                    ]
                }
            }
        }
    }), 10).unwrap();

    assert_eq!(results.total_hits.value, 1,
        "Alice selling at 999 should match");

    cleanup(&path);
}

/// Exit Criterion 2: Nested query returns parent document.
#[test]
fn nested_returns_parent() {
    let path = test_dir("parent_return");
    let schema = Mapping::builder()
        .field("title", FieldType::Text)
        .field("tags", FieldType::Nested)
        .field("tags.name", FieldType::Keyword)
        .field("tags.score", FieldType::Keyword)
        .build();
    let mut index = Index::create_with_mapping(&path, schema).unwrap();

    index.add(json!({
        "title": "article one",
        "tags": [
            {"name": "rust", "score": "high"},
            {"name": "search", "score": "medium"}
        ]
    })).unwrap();
    index.add(json!({
        "title": "article two",
        "tags": [
            {"name": "python", "score": "high"}
        ]
    })).unwrap();
    index.commit().unwrap();

    // Search for nested tag name=rust — should return "article one"
    let results = index.search(json!({
        "nested": {
            "path": "tags",
            "query": {"term": {"tags.name": "rust"}}
        }
    }), 10).unwrap();

    assert_eq!(results.total_hits.value, 1);
    let source = results.hits[0].source.as_ref().unwrap();
    assert_eq!(source["title"], "article one",
        "tags.name=rust should return 'article one'");

    // Search for nested tag name=python — should return "article two", not doc 0
    let results = index.search(json!({
        "nested": {
            "path": "tags",
            "query": {"term": {"tags.name": "python"}}
        }
    }), 10).unwrap();

    assert_eq!(results.total_hits.value, 1);
    let source = results.hits[0].source.as_ref().unwrap();
    assert_eq!(source["title"], "article two",
        "tags.name=python should return 'article two', not doc 0");

    cleanup(&path);
}

/// Verify that nested queries correctly identify distinct parents.
///
/// Regression test: a bug in mark_parent() caused the parent bitset to never
/// be set, so find_parent() always returned doc 0. Tests with 1-2 parents
/// didn't catch this because doc 0 was always a valid match. This test uses
/// multiple parents with distinct nested values and asserts on the returned
/// document _source to verify the correct parent is matched.
#[test]
fn nested_matches_correct_parent() {
    let path = test_dir("correct_parent");
    let schema = Mapping::builder()
        .field("product", FieldType::Text)
        .field("offers", FieldType::Nested)
        .field("offers.seller", FieldType::Keyword)
        .build();
    let mut index = Index::create_with_mapping(&path, schema).unwrap();

    // Doc 0: only has "alice"
    index.add(json!({
        "product": "laptop",
        "offers": [{"seller": "alice"}]
    })).unwrap();
    // Doc 1: only has "bob"
    index.add(json!({
        "product": "phone",
        "offers": [{"seller": "bob"}]
    })).unwrap();
    // Doc 2: only has "charlie"
    index.add(json!({
        "product": "tablet",
        "offers": [{"seller": "charlie"}]
    })).unwrap();
    // Doc 3: has "diana" and "eve"
    index.add(json!({
        "product": "monitor",
        "offers": [{"seller": "diana"}, {"seller": "eve"}]
    })).unwrap();
    index.commit().unwrap();

    // Search for seller=diana — must return "monitor", NOT "laptop" (doc 0)
    let results = index.search(json!({
        "nested": {
            "path": "offers",
            "query": {"term": {"offers.seller": "diana"}}
        }
    }), 10).unwrap();

    assert_eq!(results.total_hits.value, 1, "only one parent has seller=diana");
    let source = results.hits[0].source.as_ref().unwrap();
    assert_eq!(source["product"], "monitor",
        "seller=diana should match 'monitor', not doc 0");

    // Search for seller=bob — must return "phone", NOT "laptop" (doc 0)
    let results = index.search(json!({
        "nested": {
            "path": "offers",
            "query": {"term": {"offers.seller": "bob"}}
        }
    }), 10).unwrap();

    assert_eq!(results.total_hits.value, 1, "only one parent has seller=bob");
    let source = results.hits[0].source.as_ref().unwrap();
    assert_eq!(source["product"], "phone",
        "seller=bob should match 'phone', not doc 0");

    cleanup(&path);
}

/// Exit Criterion 3: Multiple nested objects per document.
#[test]
fn multiple_nested_objects() {
    let path = test_dir("multi_nested");
    let schema = Mapping::builder()
        .field("name", FieldType::Text)
        .field("skills", FieldType::Nested)
        .field("skills.name", FieldType::Keyword)
        .build();
    let mut index = Index::create_with_mapping(&path, schema).unwrap();

    index.add(json!({
        "name": "developer",
        "skills": [
            {"name": "rust"},
            {"name": "python"},
            {"name": "javascript"}
        ]
    })).unwrap();
    index.commit().unwrap();

    // Should match — has a nested skill with name=python
    let results = index.search(json!({
        "nested": {
            "path": "skills",
            "query": {"term": {"skills.name": "python"}}
        }
    }), 10).unwrap();

    assert_eq!(results.total_hits.value, 1);

    // Should not match — no nested skill with name=go
    let results = index.search(json!({
        "nested": {
            "path": "skills",
            "query": {"term": {"skills.name": "go"}}
        }
    }), 10).unwrap();

    assert_eq!(results.total_hits.value, 0);

    cleanup(&path);
}
