use search::{self, SearchClient, SearchDocument};

fn meili() -> (String, String) {
    (
        std::env::var("MEILI_URL").expect("MEILI_URL must be set"),
        std::env::var("MEILI_MASTER_KEY").expect("MEILI_MASTER_KEY must be set"),
    )
}

fn unique_index() -> String {
    format!("objects_test_{}", uuid::Uuid::new_v4().simple())
}

fn doc(id: &str, object_name: &str, fields_text: &[&str]) -> SearchDocument {
    SearchDocument {
        id: id.to_string(),
        object_number: format!("N-{id}"),
        object_name: object_name.to_string(),
        brief_description: None,
        current_owner: None,
        recorder: None,
        recording_date: None,
        visibility: "draft".to_string(),
        fields_text: fields_text.iter().map(|s| s.to_string()).collect(),
    }
}

#[tokio::test]
async fn index_search_and_remove() {
    let (url, key) = meili();
    let client = SearchClient::connect(&url, &key, &unique_index()).unwrap();
    client.ensure_index().await.unwrap();

    let vase = domain::ObjectId::new();
    let chair = domain::ObjectId::new();
    client
        .index_object(&doc(&vase.to_string(), "vase", &["wood", "trä"]))
        .await
        .unwrap();
    client
        .index_object(&doc(&chair.to_string(), "chair", &["oak"]))
        .await
        .unwrap();

    let hits = client.search("wood").await.unwrap();
    assert_eq!(hits, vec![vase]);

    let hits = client.search("chair").await.unwrap();
    assert_eq!(hits, vec![chair]);

    client.remove_object(vase).await.unwrap();
    assert!(client.search("wood").await.unwrap().is_empty());
}

#[tokio::test]
async fn search_objects_returns_hits_with_highlight_filter_and_paging() {
    let (url, key) = meili();
    let client = SearchClient::connect(&url, &key, &unique_index()).unwrap();
    client.ensure_index().await.unwrap();

    let a = domain::ObjectId::new();
    let b = domain::ObjectId::new();
    let c = domain::ObjectId::new();
    let mut bronze_a = doc(
        &a.to_string(),
        "Bronze figurine",
        &["cast bronze with green patina"],
    );
    bronze_a.visibility = "public".to_string();
    bronze_a.recording_date = Some("1962-04-03".to_string());
    let mut bronze_b = doc(&b.to_string(), "Ceremonial bowl", &["bronze alloy rim"]);
    bronze_b.visibility = "public".to_string();
    let mut bronze_c = doc(&c.to_string(), "Door fitting", &["bronze hinge"]);
    bronze_c.visibility = "draft".to_string();
    client.index_object(&bronze_a).await.unwrap();
    client.index_object(&bronze_b).await.unwrap();
    client.index_object(&bronze_c).await.unwrap();

    let results = client.search_objects("bronze", None, 0, 20).await.unwrap();
    assert_eq!(results.estimated_total, 3);
    assert_eq!(results.hits.len(), 3);

    let hit = results.hits.iter().find(|h| h.id == a.to_string()).unwrap();
    assert_eq!(hit.object_name, "Bronze figurine");
    assert_eq!(hit.object_number, format!("N-{a}"));
    let snippet = hit.snippet.as_ref().expect("a matched snippet");
    assert!(
        snippet.contains(search::HL_PRE),
        "snippet must mark the match"
    );
    assert!(snippet.contains(search::HL_POST));
    assert_eq!(hit.recording_date.as_deref(), Some("1962-04-03"));

    let public = client
        .search_objects("bronze", Some("public"), 0, 20)
        .await
        .unwrap();
    assert_eq!(public.estimated_total, 2);
    assert!(public.hits.iter().all(|h| h.visibility == "public"));

    let page = client.search_objects("bronze", None, 0, 1).await.unwrap();
    assert_eq!(page.hits.len(), 1);
    assert_eq!(page.estimated_total, 3);
}

#[tokio::test]
async fn ensure_index_is_idempotent() {
    let (url, key) = meili();
    let index = unique_index();
    let client = SearchClient::connect(&url, &key, &index).unwrap();
    client.ensure_index().await.unwrap();
    // second call against the now-existing index must succeed
    client.ensure_index().await.unwrap();

    // and the client still works
    let id = domain::ObjectId::new();
    client
        .index_object(&doc(&id.to_string(), "lamp", &[]))
        .await
        .unwrap();
    assert_eq!(client.search("lamp").await.unwrap(), vec![id]);
}
