use std::collections::HashMap;

use dittolive_ditto::prelude::*;
use serde_json::json;

mod common;

use common::*;

#[::tokio::test]
async fn test_local_attachment_roundtrip_using_store_ap_is_dql() {
    let ditto = get_builder_ditto(None).unwrap();
    test_local_attachment_roundtrip_using_store_ap_is_dql_impl(ditto).await;

    let ditto = get_revamp_ditto(None).unwrap();
    test_local_attachment_roundtrip_using_store_ap_is_dql_impl(ditto).await;
}

async fn test_local_attachment_roundtrip_using_store_ap_is_dql_impl(
    ditto: dittolive_ditto::prelude::Ditto,
) {
    let store = ditto.store();
    ditto.disable_sync_with_v3().unwrap();

    #[rustfmt::skip]
    macro_rules! ATTACHMENT_FILE_PATH {() => (
        concat!(env!("CARGO_MANIFEST_DIR"), "/tests/resources/image.png")
    )}

    let attachment_data = include_bytes!(ATTACHMENT_FILE_PATH!());
    let metadata: HashMap<_, _> = [("name".into(), "my_image.png".into())]
        .into_iter()
        .collect();

    let attachment = store
        .new_attachment(ATTACHMENT_FILE_PATH!(), metadata.clone())
        .await
        .unwrap();
    assert!(attachment.id().len() > 10);
    assert_eq!(attachment.len(), attachment_data.len() as u64);

    let doc = &json!({
        "some": "string",
        "my_attachment": &attachment,
    });

    let insert_query_result = ::futures::executor::block_on(store.execute_v2((
        "INSERT INTO COLLECTION test (my_attachment ATTACHMENT) DOCUMENTS (:doc)",
        json!({ "doc": doc }),
    )))
    .unwrap();

    let mutated_document_ids = insert_query_result.mutated_document_ids();
    assert_eq!(mutated_document_ids.len(), 1);

    let doc_id = &mutated_document_ids[0];

    let select_query_result = ::futures::executor::block_on(store.execute_v2((
        "SELECT * FROM COLLECTION test (my_attachment ATTACHMENT) WHERE _id = :id",
        json!({
            "id": doc_id,
        }),
    )))
    .unwrap();

    let value = select_query_result.get_item(0).unwrap().value();
    let attachment_token = value.get("my_attachment").unwrap().as_object().unwrap();
    assert_eq!(
        attachment.id(),
        attachment_token
            .get(&CborValue::Text("id".into()))
            .expect("id")
            .as_str()
            .expect("str")
    );
    assert_eq!(
        attachment.len(),
        attachment_token
            .get(&CborValue::Text("len".into()))
            .expect("len")
            .as_u64()
            .expect("u64")
    );

    let (tx, rx) = ::std::sync::mpsc::sync_channel(0);
    let attachment_fetcher = store
        .fetch_attachment(attachment_token, move |event| match event {
            DittoAttachmentFetchEvent::Completed {
                attachment: fetched_attachment,
            } => {
                let fetched_attachment_data = ::std::fs::read(fetched_attachment.path()).unwrap();
                assert_eq!(attachment.id(), fetched_attachment.id());
                assert_eq!(attachment.len(), fetched_attachment.len());
                assert_eq!(fetched_attachment_data, attachment_data);
                assert_eq!(fetched_attachment.metadata(), &metadata);
                tx.send(()).unwrap();
            }
            DittoAttachmentFetchEvent::Progress { .. } => {}
            DittoAttachmentFetchEvent::Deleted { .. } => panic!(
                "Got an attachment fetch event of something other than completed or progress"
            ),
        })
        .unwrap();

    rx.recv().unwrap();
    drop(attachment_fetcher);
}
