//! End-to-end integration tests for the Collection API.
//!
//! These tests exercise the new Qdrant-style Collection API exposed on Database,
//! including collection management, point operations, and vector search.

use manifoldb::collection::{DistanceMetric, PointStruct, SparseDistanceMetric, Vector};
use manifoldb::Database;
use serde_json::json;

// ============================================================================
// Collection Management Tests
// ============================================================================

#[test]
fn test_create_collection_with_dense_vector() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("documents")
        .expect("failed to get builder")
        .with_dense_vector("embedding", 128, DistanceMetric::Cosine)
        .build()
        .expect("failed to create collection");

    assert_eq!(collection.name(), "documents");
    assert!(collection.has_vector("embedding"));
}

#[test]
fn test_create_collection_with_multiple_vectors() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("multi_modal")
        .expect("failed to get builder")
        .with_dense_vector("text", 384, DistanceMetric::Cosine)
        .with_dense_vector("image", 512, DistanceMetric::DotProduct)
        .with_sparse_vector("keywords")
        .build()
        .expect("failed to create collection");

    assert!(collection.has_vector("text"));
    assert!(collection.has_vector("image"));
    assert!(collection.has_vector("keywords"));
}

#[test]
fn test_open_existing_collection() {
    let db = Database::in_memory().expect("failed to create db");

    // Create collection
    db.create_collection("test_open")
        .expect("failed to get builder")
        .with_dense_vector("vec", 64, DistanceMetric::Euclidean)
        .build()
        .expect("failed to create collection");

    // Open it again
    let collection = db.collection("test_open").expect("failed to open collection");
    assert_eq!(collection.name(), "test_open");
    assert!(collection.has_vector("vec"));
}

#[test]
fn test_open_nonexistent_collection_fails() {
    let db = Database::in_memory().expect("failed to create db");

    let result = db.collection("nonexistent");
    assert!(result.is_err(), "should fail to open nonexistent collection");
}

// NOTE: list_collections requires integration between CollectionHandle and CollectionManager.
// The Collection API creates collections via PointStore but list_collections uses CollectionManager.
// This test verifies the collection can be reopened after creation (which works).
#[test]
fn test_collections_can_be_reopened() {
    let db = Database::in_memory().expect("failed to create db");

    // Create some collections
    db.create_collection("alpha")
        .expect("builder")
        .with_dense_vector("v", 32, DistanceMetric::Cosine)
        .build()
        .expect("create");
    db.create_collection("beta")
        .expect("builder")
        .with_dense_vector("v", 32, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Verify they can be reopened
    let alpha = db.collection("alpha").expect("should open alpha");
    assert_eq!(alpha.name(), "alpha");

    let beta = db.collection("beta").expect("should open beta");
    assert_eq!(beta.name(), "beta");
}

// NOTE: drop_collection requires integration between CollectionHandle and CollectionManager.
// The Collection API creates collections via PointStore but drop_collection uses CollectionManager.
// These tests are marked as ignored until integration is complete.
#[test]
#[ignore = "requires CollectionHandle/CollectionManager integration"]
fn test_drop_collection() {
    let db = Database::in_memory().expect("failed to create db");

    // Create collection
    db.create_collection("to_drop")
        .expect("builder")
        .with_dense_vector("v", 32, DistanceMetric::Cosine)
        .build()
        .expect("create");

    assert!(db.collection("to_drop").is_ok());

    // Drop it
    db.drop_collection("to_drop").expect("failed to drop");

    // Should no longer exist
    assert!(db.collection("to_drop").is_err());
    let collections = db.list_collections().expect("list");
    assert!(!collections.contains(&"to_drop".to_string()));
}

#[test]
#[ignore = "requires CollectionHandle/CollectionManager integration"]
fn test_drop_collection_with_data() {
    let db = Database::in_memory().expect("failed to create db");

    // Create collection and add data
    let collection = db
        .create_collection("with_data")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"title": "Test"}))
                .with_vector("v", vec![0.1, 0.2, 0.3, 0.4]),
        )
        .expect("insert");

    assert_eq!(collection.count_points().expect("count"), 1);

    // Drop collection
    db.drop_collection("with_data").expect("drop");

    // Verify it's gone
    assert!(db.collection("with_data").is_err());
}

#[test]
fn test_create_collection_no_vectors_fails() {
    let db = Database::in_memory().expect("failed to create db");

    let result = db.create_collection("empty").expect("builder").build(); // No vectors added

    assert!(result.is_err(), "should fail without vectors");
}

// ============================================================================
// Point Operations Tests
// ============================================================================

#[test]
fn test_upsert_and_get_point() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("points")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Upsert a point
    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"title": "Document 1", "category": "tech"}))
                .with_vector("v", vec![1.0, 0.0, 0.0, 0.0]),
        )
        .expect("upsert");

    // Get payload
    let payload = collection.get_payload(1.into()).expect("get").expect("should exist");
    assert_eq!(payload["title"], "Document 1");
    assert_eq!(payload["category"], "tech");

    // Get vector
    let vector = collection.get_vector(1.into(), "v").expect("get").expect("should exist");
    match vector {
        Vector::Dense(v) => {
            assert_eq!(v.len(), 4);
            assert!((v[0] - 1.0).abs() < 0.001);
        }
        _ => panic!("expected dense vector"),
    }
}

#[test]
fn test_upsert_updates_existing_point() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("updates")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Insert initial point
    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"version": 1}))
                .with_vector("v", vec![1.0, 0.0, 0.0, 0.0]),
        )
        .expect("first upsert");

    // Update with new values
    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"version": 2}))
                .with_vector("v", vec![0.0, 1.0, 0.0, 0.0]),
        )
        .expect("second upsert");

    // Verify updated
    let payload = collection.get_payload(1.into()).expect("get").expect("exists");
    assert_eq!(payload["version"], 2);

    let vector = collection.get_vector(1.into(), "v").expect("get").expect("exists");
    match vector {
        Vector::Dense(v) => {
            assert!((v[1] - 1.0).abs() < 0.001);
        }
        _ => panic!("expected dense vector"),
    }
}

#[test]
fn test_insert_point_fails_if_exists() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("insert_test")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // First insert should work
    collection
        .insert_point(PointStruct::new(1).with_vector("v", vec![1.0, 0.0, 0.0, 0.0]))
        .expect("first insert");

    // Second insert with same ID should fail
    let result =
        collection.insert_point(PointStruct::new(1).with_vector("v", vec![0.0, 1.0, 0.0, 0.0]));

    assert!(result.is_err(), "should fail on duplicate insert");
}

#[test]
fn test_delete_point() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("delete_test")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Insert
    collection
        .upsert_point(PointStruct::new(1).with_vector("v", vec![1.0, 0.0, 0.0, 0.0]))
        .expect("insert");

    assert!(collection.point_exists(1.into()).expect("check"));

    // Delete
    let deleted = collection.delete_point(1.into()).expect("delete");
    assert!(deleted, "should return true for deleted point");

    // Verify gone
    assert!(!collection.point_exists(1.into()).expect("check"));
}

#[test]
fn test_delete_nonexistent_point() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("delete_none")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    let deleted = collection.delete_point(999.into()).expect("delete");
    assert!(!deleted, "should return false for nonexistent point");
}

#[test]
fn test_update_payload_only() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("payload_update")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Insert with vector and payload
    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"status": "draft"}))
                .with_vector("v", vec![1.0, 0.0, 0.0, 0.0]),
        )
        .expect("insert");

    // Update only payload
    collection
        .update_payload(1.into(), json!({"status": "published", "views": 100}))
        .expect("update");

    // Verify payload updated
    let payload = collection.get_payload(1.into()).expect("get").expect("exists");
    assert_eq!(payload["status"], "published");
    assert_eq!(payload["views"], 100);

    // Verify vector unchanged
    let vector = collection.get_vector(1.into(), "v").expect("get").expect("exists");
    match vector {
        Vector::Dense(v) => assert!((v[0] - 1.0).abs() < 0.001),
        _ => panic!("expected dense vector"),
    }
}

#[test]
fn test_update_vector_only() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("vector_update")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Insert
    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"title": "Test"}))
                .with_vector("v", vec![1.0, 0.0, 0.0, 0.0]),
        )
        .expect("insert");

    // Update vector only
    collection.update_vector(1.into(), "v", vec![0.0, 0.0, 0.0, 1.0]).expect("update vector");

    // Verify vector updated
    let vector = collection.get_vector(1.into(), "v").expect("get").expect("exists");
    match vector {
        Vector::Dense(v) => assert!((v[3] - 1.0).abs() < 0.001),
        _ => panic!("expected dense vector"),
    }

    // Verify payload unchanged
    let payload = collection.get_payload(1.into()).expect("get").expect("exists");
    assert_eq!(payload["title"], "Test");
}

#[test]
fn test_list_and_count_points() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("list_test")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Initially empty
    assert_eq!(collection.count_points().expect("count"), 0);
    assert!(collection.list_points().expect("list").is_empty());

    // Insert points
    for i in 1..=10 {
        collection
            .upsert_point(PointStruct::new(i as u64).with_vector("v", vec![i as f32; 4]))
            .expect("insert");
    }

    // Verify count
    assert_eq!(collection.count_points().expect("count"), 10);

    // Verify list
    let points = collection.list_points().expect("list");
    assert_eq!(points.len(), 10);
}

#[test]
fn test_batch_upsert() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("batch")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Batch upsert
    let points: Vec<_> = (1..=100)
        .map(|i| PointStruct::new(i as u64).with_vector("v", vec![i as f32 / 100.0; 4]))
        .collect();

    collection.upsert_points(points).expect("batch upsert");

    assert_eq!(collection.count_points().expect("count"), 100);
}

#[test]
fn test_batch_delete() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("batch_delete")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Insert points
    for i in 1..=10 {
        collection
            .upsert_point(PointStruct::new(i as u64).with_vector("v", vec![1.0; 4]))
            .expect("insert");
    }

    // Delete some
    let deleted =
        collection.delete_points([2, 4, 6, 8, 10].map(|i| i.into())).expect("batch delete");

    assert_eq!(deleted, 5);
    assert_eq!(collection.count_points().expect("count"), 5);
}

// ============================================================================
// Search Tests
// ============================================================================

#[test]
fn test_basic_vector_search() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("search_test")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::DotProduct)
        .build()
        .expect("create");

    // Insert points with different directions
    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"name": "point1"}))
                .with_vector("v", vec![1.0, 0.0, 0.0, 0.0]),
        )
        .expect("insert");

    collection
        .upsert_point(
            PointStruct::new(2)
                .with_payload(json!({"name": "point2"}))
                .with_vector("v", vec![0.0, 1.0, 0.0, 0.0]),
        )
        .expect("insert");

    collection
        .upsert_point(
            PointStruct::new(3)
                .with_payload(json!({"name": "point3"}))
                .with_vector("v", vec![0.7, 0.7, 0.0, 0.0]),
        )
        .expect("insert");

    // Search for vector similar to [1, 0, 0, 0]
    let results = collection
        .search("v")
        .query(vec![1.0, 0.0, 0.0, 0.0])
        .limit(3)
        .with_payload(true)
        .execute()
        .expect("search");

    assert_eq!(results.len(), 3);

    // Point 1 should be first (exact match)
    assert_eq!(results[0].id, 1.into());

    // Point 3 should be second (0.7 similarity)
    assert_eq!(results[1].id, 3.into());

    // Verify payloads are included
    assert!(results[0].payload.is_some());
    assert_eq!(results[0].payload.as_ref().unwrap()["name"], "point1");
}

#[test]
fn test_search_with_filter() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("filtered_search")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Insert points with different categories
    for i in 1..=10 {
        let category = if i <= 5 { "A" } else { "B" };
        collection
            .upsert_point(
                PointStruct::new(i as u64)
                    .with_payload(json!({"category": category, "value": i}))
                    .with_vector("v", vec![i as f32 / 10.0; 4]),
            )
            .expect("insert");
    }

    // Search with filter for category A
    let filter = manifoldb::collection::Filter::eq("category", "A");
    let results = collection
        .search("v")
        .query(vec![0.5; 4])
        .limit(10)
        .filter(filter)
        .with_payload(true)
        .execute()
        .expect("search");

    // Should only return category A points
    assert!(results.len() <= 5);
    for result in &results {
        let payload = result.payload.as_ref().expect("has payload");
        assert_eq!(payload["category"], "A");
    }
}

#[test]
fn test_search_with_limit_and_offset() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("pagination")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Insert 20 points
    for i in 1..=20 {
        collection
            .upsert_point(PointStruct::new(i as u64).with_vector("v", vec![i as f32 / 20.0; 4]))
            .expect("insert");
    }

    // Get first page
    let page1 =
        collection.search("v").query(vec![0.5; 4]).limit(5).offset(0).execute().expect("search");

    // Get second page
    let page2 =
        collection.search("v").query(vec![0.5; 4]).limit(5).offset(5).execute().expect("search");

    assert_eq!(page1.len(), 5);
    assert_eq!(page2.len(), 5);

    // Pages should have different points
    let page1_ids: Vec<_> = page1.iter().map(|p| p.id).collect();
    let page2_ids: Vec<_> = page2.iter().map(|p| p.id).collect();

    for id in &page2_ids {
        assert!(!page1_ids.contains(id), "pages should not overlap");
    }
}

#[test]
fn test_search_with_score_threshold() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("threshold")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::DotProduct)
        .build()
        .expect("create");

    // Insert points with varying similarity
    collection
        .upsert_point(PointStruct::new(1).with_vector("v", vec![1.0, 0.0, 0.0, 0.0]))
        .expect("insert");
    collection
        .upsert_point(PointStruct::new(2).with_vector("v", vec![0.5, 0.5, 0.0, 0.0]))
        .expect("insert");
    collection
        .upsert_point(PointStruct::new(3).with_vector("v", vec![0.0, 0.0, 1.0, 0.0]))
        .expect("insert");

    // Search with high threshold
    let results = collection
        .search("v")
        .query(vec![1.0, 0.0, 0.0, 0.0])
        .limit(10)
        .score_threshold(0.5)
        .execute()
        .expect("search");

    // Should only get points with score >= 0.5
    for result in &results {
        assert!(result.score >= 0.5, "score {} below threshold", result.score);
    }
}

// ============================================================================
// Multi-Vector Tests
// ============================================================================

#[test]
fn test_multi_vector_collection() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("multi_vec")
        .expect("builder")
        .with_dense_vector("text", 64, DistanceMetric::Cosine)
        .with_dense_vector("image", 128, DistanceMetric::DotProduct)
        .build()
        .expect("create");

    // Verify vectors are configured
    assert!(collection.has_vector("text"));
    assert!(collection.has_vector("image"));

    // Insert point with multiple vectors
    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"title": "Multi-modal document"}))
                .with_vector("text", vec![0.1; 64])
                .with_vector("image", vec![0.2; 128]),
        )
        .expect("insert");

    // Verify point exists
    assert!(collection.point_exists(1.into()).expect("check"));

    // Get individual vectors (more reliable than get_all_vectors which may have issues)
    let text_vec = collection.get_vector(1.into(), "text").expect("get text");
    assert!(text_vec.is_some(), "text vector should exist");
    if let Some(Vector::Dense(v)) = text_vec {
        assert_eq!(v.len(), 64);
    }

    let image_vec = collection.get_vector(1.into(), "image").expect("get image");
    assert!(image_vec.is_some(), "image vector should exist");
    if let Some(Vector::Dense(v)) = image_vec {
        assert_eq!(v.len(), 128);
    }
}

#[test]
fn test_sparse_vector_collection() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("sparse")
        .expect("builder")
        .with_sparse_vector_config("keywords", 10000, SparseDistanceMetric::DotProduct)
        .build()
        .expect("create");

    // Insert with sparse vector (index-value pairs)
    let sparse_vector = Vector::Sparse(vec![(10, 1.0), (50, 0.5), (100, 0.3), (500, 0.1)]);

    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"title": "Sparse doc"}))
                .with_vector("keywords", sparse_vector),
        )
        .expect("insert");

    // Verify point exists
    assert!(collection.point_exists(1.into()).expect("check"));

    // Get the sparse vector
    let vector = collection.get_vector(1.into(), "keywords").expect("get").expect("exists");
    match vector {
        Vector::Sparse(sv) => {
            assert_eq!(sv.len(), 4);
        }
        _ => panic!("expected sparse vector"),
    }
}

#[test]
fn test_hybrid_dense_sparse_collection() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("hybrid")
        .expect("builder")
        .with_dense_vector("semantic", 32, DistanceMetric::Cosine)
        .with_sparse_vector("lexical")
        .build()
        .expect("create");

    // Insert documents with both vector types
    collection
        .upsert_point(
            PointStruct::new(1)
                .with_payload(json!({"title": "Rust programming"}))
                .with_vector("semantic", vec![0.5; 32])
                .with_vector("lexical", Vector::Sparse(vec![(1, 1.0), (2, 0.8), (3, 0.5)])),
        )
        .expect("insert");

    collection
        .upsert_point(
            PointStruct::new(2)
                .with_payload(json!({"title": "Python programming"}))
                .with_vector("semantic", vec![0.4; 32])
                .with_vector("lexical", Vector::Sparse(vec![(1, 0.9), (4, 0.7), (5, 0.6)])),
        )
        .expect("insert");

    // Search on dense vector
    let dense_results =
        collection.search("semantic").query(vec![0.5; 32]).limit(2).execute().expect("search");

    assert_eq!(dense_results.len(), 2);
}

// ============================================================================
// Edge Cases and Error Handling
// ============================================================================

#[test]
fn test_get_nonexistent_point() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("missing")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Getting payload of nonexistent point returns None
    let payload = collection.get_payload(999.into()).expect("get");
    assert!(payload.is_none());

    // Getting vector of nonexistent point returns None
    let vector = collection.get_vector(999.into(), "v").expect("get");
    assert!(vector.is_none());
}

#[test]
fn test_search_empty_collection() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("empty_search")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    let results =
        collection.search("v").query(vec![1.0, 0.0, 0.0, 0.0]).limit(10).execute().expect("search");

    assert!(results.is_empty());
}

#[test]
fn test_update_nonexistent_point_fails() {
    let db = Database::in_memory().expect("failed to create db");

    let collection = db
        .create_collection("update_missing")
        .expect("builder")
        .with_dense_vector("v", 4, DistanceMetric::Cosine)
        .build()
        .expect("create");

    let result = collection.update_payload(999.into(), json!({"foo": "bar"}));
    assert!(result.is_err(), "should fail to update nonexistent point");
}

#[test]
fn test_collection_persistence_after_reopen() {
    let db = Database::in_memory().expect("failed to create db");

    // Create collection and add data
    {
        let collection = db
            .create_collection("persist")
            .expect("builder")
            .with_dense_vector("v", 4, DistanceMetric::Cosine)
            .build()
            .expect("create");

        collection
            .upsert_point(
                PointStruct::new(1)
                    .with_payload(json!({"data": "test"}))
                    .with_vector("v", vec![1.0; 4]),
            )
            .expect("insert");
    }

    // Reopen collection
    let collection = db.collection("persist").expect("open");

    // Data should still be there
    assert_eq!(collection.count_points().expect("count"), 1);
    let payload = collection.get_payload(1.into()).expect("get").expect("exists");
    assert_eq!(payload["data"], "test");
}

// ============================================================================
// Workflow Integration Tests
// ============================================================================

#[test]
fn test_semantic_search_workflow() {
    let db = Database::in_memory().expect("failed to create db");

    // Create a document collection
    let docs = db
        .create_collection("documents")
        .expect("builder")
        .with_dense_vector("embedding", 8, DistanceMetric::Cosine)
        .build()
        .expect("create");

    // Simulate document embeddings (in practice, these would come from an ML model)
    let documents = vec![
        (1, "Rust programming guide", vec![0.9, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        (2, "Python machine learning", vec![0.1, 0.9, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        (3, "Rust async programming", vec![0.85, 0.15, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
        (4, "JavaScript web development", vec![0.0, 0.0, 0.9, 0.1, 0.0, 0.0, 0.0, 0.0]),
        (5, "Rust systems programming", vec![0.88, 0.12, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
    ];

    // Index documents
    for (id, title, embedding) in documents {
        docs.upsert_point(
            PointStruct::new(id)
                .with_payload(json!({"title": title}))
                .with_vector("embedding", embedding),
        )
        .expect("insert");
    }

    // Search for "Rust" related documents
    let query = vec![0.9, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
    let results = docs
        .search("embedding")
        .query(query)
        .limit(3)
        .with_payload(true)
        .execute()
        .expect("search");

    // Should find Rust documents
    assert_eq!(results.len(), 3);

    let titles: Vec<_> =
        results.iter().map(|r| r.payload.as_ref().unwrap()["title"].as_str().unwrap()).collect();

    // All top results should be Rust-related
    for title in titles {
        assert!(title.contains("Rust"), "expected Rust doc, got: {}", title);
    }
}

#[test]
fn test_recommendation_workflow() {
    let db = Database::in_memory().expect("failed to create db");

    // Create product collection
    let products = db
        .create_collection("products")
        .expect("builder")
        .with_dense_vector("features", 4, DistanceMetric::DotProduct)
        .build()
        .expect("create");

    // Add products with feature vectors
    let items = vec![
        (1, "Laptop Pro", "electronics", vec![0.9, 0.1, 0.0, 0.0]),
        (2, "Laptop Basic", "electronics", vec![0.8, 0.2, 0.0, 0.0]),
        (3, "Running Shoes", "sports", vec![0.0, 0.0, 0.9, 0.1]),
        (4, "Headphones", "electronics", vec![0.7, 0.3, 0.0, 0.0]),
        (5, "Yoga Mat", "sports", vec![0.0, 0.0, 0.8, 0.2]),
    ];

    for (id, name, category, features) in items {
        products
            .upsert_point(
                PointStruct::new(id)
                    .with_payload(json!({"name": name, "category": category}))
                    .with_vector("features", features),
            )
            .expect("insert");
    }

    // User views "Laptop Pro" - recommend similar products
    let viewed_features = vec![0.9, 0.1, 0.0, 0.0];
    let recommendations = products
        .search("features")
        .query(viewed_features)
        .limit(3)
        .with_payload(true)
        .execute()
        .expect("search");

    // Top recommendations should be electronics
    for rec in &recommendations {
        let category = rec.payload.as_ref().unwrap()["category"].as_str().unwrap();
        assert_eq!(category, "electronics");
    }
}
