use_prelude!();
use serde_json::json;

use super::*;
use crate::{
    prelude::DocumentId,
    store::collection::document::{DittoDocument as _, DittoMutDocument as _},
    test_helpers::setup_ditto,
    utils::prelude::ErrorKind,
};

#[test]
fn serde_json() {
    let counter = DittoCounter::new_with_value(24.0);
    let serialized = serde_json::to_value(&counter).unwrap();
    let counter_type = serialized
        .as_object()
        .unwrap()
        .get("_ditto_internal_type_jkb12973t4b")
        .unwrap()
        .as_u64()
        .unwrap();
    let counter_value = serialized
        .as_object()
        .unwrap()
        .get("_value")
        .unwrap()
        .as_f64()
        .unwrap();
    assert_eq!(counter_type, 0);
    assert_eq!(counter_value, 24.0);

    let serialized_counter = serde_json::Value::from(24.0);
    let new_counter: DittoCounter = serde_json::from_value(serialized_counter).unwrap();
    assert_eq!(counter, new_counter);
}

#[test]
fn create_in_document() {
    let ditto = setup_ditto().unwrap();
    let store = ditto.store();
    let collection = store.collection("test").unwrap();
    let doc_id = DocumentId::new(&"test_counter").unwrap();
    let doc = collection.find_by_id(doc_id).exec();
    assert!(doc.is_err());
    let e = doc.err().unwrap();
    let error_kind = e.kind();
    assert_eq!(error_kind, ErrorKind::NonExtant);
    let content = json!({"_id": "test_counter" ,"counter": DittoCounter::new_with_value(12.12)});
    let doc_id = collection.upsert(content).unwrap();
    let doc = collection.find_by_id(doc_id).exec();
    assert!(doc.is_ok());
}

#[test]
fn regular_insertion() {
    let ditto = setup_ditto().unwrap();
    let store = ditto.store();
    let collection = store.collection("test").unwrap();
    let doc_id = collection.upsert(json!({})).unwrap();

    collection
        .find_by_id(&doc_id)
        .update(|mut_doc| {
            let mut_doc = mut_doc.unwrap();
            mut_doc.set("counter", DittoCounter::new()).unwrap();
        })
        .unwrap();

    let doc = collection.find_by_id(&doc_id).exec().unwrap();
    let counter: DittoCounter = doc.get("counter").unwrap();
    assert_eq!(counter.value(), 0.);

    collection
        .find_by_id(&doc_id)
        .update(|mut_doc| {
            let mut_doc = mut_doc.unwrap();
            mut_doc.increment("counter", 23.0).unwrap();
        })
        .unwrap();

    let doc = collection.find_by_id(doc_id).exec().unwrap();
    let counter: DittoCounter = doc.get("counter").unwrap();
    assert_eq!(counter.value(), 23.0);
}

#[test]
fn update_in_document() -> Result<(), AutoUnwrap> {
    let ditto = setup_ditto()?;
    let store = ditto.store();
    let collection = store.collection("foo")?;

    let id_1 = collection.upsert(json!({ "counter": DittoCounter::new() }))?;
    let doc = collection.find_by_id(&id_1).exec()?;
    let counter: DittoCounter = doc.get("counter")?;
    assert_eq!(counter.value(), 0.0);

    collection
        .find_by_id(&id_1)
        .update(|mut_doc| {
            let mut_doc = mut_doc.unwrap();
            mut_doc
                .get_mut::<DittoMutableCounter<'_>>("counter")
                .unwrap()
                .increment(5.0)
                .unwrap();
            // `set` with a counter now works as if it was an update
            mut_doc
                .set("counter", DittoCounter::new_with_value(23.))
                .unwrap();
        })
        .unwrap();
    let doc = collection.find_by_id(&id_1).exec()?;
    let counter = doc.get::<DittoCounter>("counter")?;
    assert_eq!(counter.value(), 28.);

    collection
        .find_by_id(&id_1)
        .update(|mut_doc| {
            let mut_doc = mut_doc.unwrap();
            mut_doc
                .get_mut::<DittoMutableCounter<'_>>("counter")
                .unwrap()
                .increment(13.0)
                .unwrap();
        })
        .unwrap();

    let doc = collection.find_by_id(id_1).exec()?;
    let counter = doc.get::<DittoCounter>("counter")?;
    assert_eq!(counter.value(), 41.0);
    Ok(())
}

#[test]
fn remove_in_document() {
    let ditto = setup_ditto().unwrap();
    let store = ditto.store();
    let collection = store.collection("foo").unwrap();

    let doc_id = collection
        .upsert(json!({ "counter": DittoCounter::new() }))
        .unwrap();
    let mut doc = collection.find_by_id(doc_id).exec().unwrap();
    let counter: DittoCounter = doc.get("counter").unwrap();
    assert_eq!(counter.value(), 0.);
    doc.remove("counter").unwrap();
    assert!(doc.get::<DittoCounter>("counter").is_err());

    doc.remove("counter").unwrap();
}

#[test]
fn upsert_in_collection() {
    let ditto = setup_ditto().unwrap();
    let store = ditto.store();
    let collection = store.collection("foo").unwrap();

    let doc_id = collection
        .upsert(json!({ "counter": DittoCounter::new() }))
        .unwrap();

    collection
        .find_by_id(&doc_id)
        .update(|mut_doc| {
            let mut_doc = mut_doc.unwrap();
            mut_doc
                .get_mut::<DittoMutableCounter<'_>>("counter")
                .unwrap()
                .increment(13.0)
                .unwrap();
        })
        .unwrap();

    // Valid to call upsert with a counter value
    let doc_id = collection
        .upsert(json!({"_id": doc_id, "counter": DittoCounter::new_with_value(12.5)}))
        .unwrap();

    let doc = collection.find_by_id(doc_id).exec().unwrap();
    let counter: DittoCounter = doc.get("counter").unwrap();
    assert_eq!(counter.value(), 25.5);
}

#[test]
fn upsert_in_collection_with_strategy() {
    let ditto = setup_ditto().unwrap();
    let store = ditto.store();
    let collection = store.collection("foo").unwrap();

    let doc_id = collection
        .upsert(json!({ "counter": DittoCounter::new() }))
        .unwrap();

    collection
        .find_by_id(&doc_id)
        .update(|mut_doc| {
            let mut_doc = mut_doc.unwrap();
            mut_doc
                .get_mut::<DittoMutableCounter<'_>>("counter")
                .unwrap()
                .increment(13.0)
                .unwrap();
        })
        .unwrap();

    let doc_id = collection
        .upsert_with_strategy(
            json!({"_id": doc_id, "counter": DittoCounter::new_with_value(12.5)}),
            WriteStrategy::Merge,
        )
        .unwrap();
    let doc = collection.find_by_id(&doc_id).exec().unwrap();
    let counter: DittoCounter = doc.get("counter").unwrap();
    assert_eq!(counter.value(), 25.5);

    // `WriteStrategy::InsertIfAbsent` doesn't merge, exits because the document already exists
    let doc_id = collection
        .upsert_with_strategy(
            json!({"_id": doc_id, "counter": DittoCounter::new_with_value(4.5)}),
            WriteStrategy::InsertIfAbsent,
        )
        .unwrap();
    let doc = collection.find_by_id(doc_id).exec().unwrap();
    let counter: DittoCounter = doc.get("counter").unwrap();
    assert_eq!(counter.value(), 25.5);
}

/// Convenience helper for `?` to stand for `.unwrap()`.
#[derive(Debug)]
enum AutoUnwrap {}

// impl From<AutoUnwrap> for AutoUnwrap {}
impl<T: ::core::fmt::Display> From<T> for AutoUnwrap {
    #[inline]
    #[track_caller]
    fn from(err: T) -> AutoUnwrap {
        panic!("{}", err);
    }
}
