//! The Live Query "Everything" Test
//! extracted into its own file (compilation unit)
//! for easier debuging

use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc, Mutex,
};

use common::*;
use dittolive_ditto::{
    observer::Observer,
    store::batch::{DocChangeKind, ScopedStore},
};
mod common;

#[test]
fn everything() -> Result<(), Box<dyn std::error::Error>> {
    env_logger::init();
    let ditto = common::get_ditto().unwrap();
    let store = ditto.store();
    let uuid1 = uuid::Uuid::new_v4().to_string();
    let col1_name = uuid1.clone();
    let uuid2 = uuid::Uuid::new_v4().to_string();
    let col2_name = uuid2.clone();
    let col1 = store.collection(&uuid1).unwrap();
    let col2 = store.collection(&uuid2).unwrap();

    let doc1 = TestType {
        make: String::from("Honda"),
        color: TestColor::Red,
        ..Default::default()
    };

    let redhonda_id = Arc::new(Mutex::new(col1.upsert(doc1)?));

    let doc2 = TestType {
        make: String::from("Honda"),
        color: TestColor::Crimson,
        ..Default::default()
    };

    let _crimsonhonda_id = Arc::new(col1.upsert(doc2)?);

    let doc3 = TestType {
        make: String::from("Bentley"),
        color: TestColor::Red,
        ..Default::default()
    };
    let doc4 = TestType {
        make: String::from("Jaguar"),
        color: TestColor::Red,
        ..Default::default()
    };

    let redbentley_id = Arc::new(Mutex::new(col2.upsert(doc3)?));
    let redjaguar_id = Arc::new(Mutex::new(col2.upsert(doc4)?));

    let redtoyota_id = Arc::new(Mutex::new(DocumentId::default()));
    let redford_id = Arc::new(Mutex::new(DocumentId::default()));
    let orangemazda_id = Arc::new(Mutex::new(DocumentId::default()));
    let yellowam_id = Arc::new(Mutex::new(DocumentId::default()));
    let redchevy_id = Arc::new(Mutex::new(DocumentId::default()));
    let redhyundai_id = Arc::new(Mutex::new(DocumentId::default()));
    let pinkjeep_id = Arc::new(Mutex::new(DocumentId::default()));
    let blackbmw_id = Arc::new(Mutex::new(DocumentId::default()));
    let redferrari_id = Arc::new(Mutex::new(DocumentId::default()));

    let counter = Arc::new(Mutex::new(0));
    let collection = col1.clone();

    let store_copy = store.clone(); // a clone of the store to move into the EventHandler

    let is_complete = Arc::new(AtomicBool::new(false));
    let is_complete_copy = Arc::clone(&is_complete);

    let handler = move |docs: Vec<BoxedDocument>, event: LiveQueryEvent| {
        if let Ok(mut counter) = counter.lock() {
            *counter += 1;
        }
        match event {
            LiveQueryEvent::Initial => {
                assert_eq!(*counter.lock().unwrap(), 1);
                assert_eq!(docs.len(), 1);
                let first_doc = docs[0].typed::<TestType>().unwrap();
                let expected_doc = TestType {
                    make: String::from("Honda"),
                    color: TestColor::Red,
                    id: Some(redhonda_id.lock().unwrap().clone()),
                    ..Default::default()
                };
                assert_eq!(first_doc, expected_doc);

                let redtoyota = TestType {
                    color: TestColor::Red,
                    make: String::from("Toyota"),
                    ..Default::default()
                };
                *redtoyota_id.lock().unwrap() = collection.upsert(redtoyota).unwrap();
            }
            LiveQueryEvent::Update {
                moves,
                insertions,
                deletions,
                updates,
                old_documents,
            } => {
                assert_eq!(moves.len(), 0);
                if let Ok(count) = counter.lock() {
                    match *count {
                        2 => {
                            assert_eq!(docs.len(), 2);
                            assert_eq!(insertions.len(), 1);
                            assert_eq!(deletions.len(), 0);
                            assert_eq!(updates.len(), 0);
                            assert_eq!(old_documents.len(), 1);

                            let redford = TestType {
                                make: String::from("Ford"),
                                color: TestColor::Red,
                                ..Default::default()
                            };
                            *redford_id.lock().unwrap() = collection.upsert(redford).unwrap();
                        }
                        3 => {
                            assert_eq!(docs.len(), 3);
                            assert_eq!(insertions.len(), 1);
                            assert_eq!(deletions.len(), 0);
                            assert_eq!(updates.len(), 0);
                            assert_eq!(old_documents.len(), 2);
                            // Assert there is a red Ford
                            let result = collection
                                .find_by_id(redford_id.lock().unwrap().clone())
                                .exec();
                            assert!(result.is_ok());
                            // Make the red toyota a red Maserati
                            collection
                                .find_by_id(redtoyota_id.lock().unwrap().clone())
                                .update(|result| {
                                    if let Some(doc) = result {
                                        doc.set("make", String::from("Maserati")).unwrap();
                                    }
                                })
                                .unwrap();
                        }
                        4 => {
                            assert_eq!(docs.len(), 3);
                            assert_eq!(insertions.len(), 0);
                            assert_eq!(deletions.len(), 0);
                            assert_eq!(updates.len(), 1);
                            assert_eq!(old_documents.len(), 3);

                            let orangemazda = TestType {
                                make: String::from("Mazda"),
                                color: TestColor::Orange,
                                ..Default::default()
                            };
                            let yellowam = TestType {
                                make: String::from("Aston Martin"),
                                color: TestColor::Yellow,
                                ..Default::default()
                            };
                            let redchevy = TestType {
                                make: String::from("Chevrolet"),
                                color: TestColor::Red,
                                ..Default::default()
                            };
                            *orangemazda_id.lock().unwrap() =
                                collection.upsert(orangemazda).unwrap();
                            *yellowam_id.lock().unwrap() = collection.upsert(yellowam).unwrap();
                            *redchevy_id.lock().unwrap() = collection.upsert(redchevy).unwrap();
                        }
                        5 => {
                            assert_eq!(docs.len(), 4);
                            assert_eq!(insertions.len(), 1);
                            assert_eq!(deletions.len(), 0);
                            assert_eq!(updates.len(), 0);
                            assert_eq!(old_documents.len(), 3);
                            // verify a red chevy is present
                            let result = collection
                                .find_by_id(redchevy_id.lock().unwrap().clone())
                                .exec();
                            assert!(result.is_ok());
                            // make the red honda blue
                            let _ = collection
                                .find_by_id(redhonda_id.lock().unwrap().clone())
                                .update(|result| {
                                    if let Some(doc) = result {
                                        doc.set("color", TestColor::Blue).unwrap();
                                    }
                                })
                                .unwrap();
                        }
                        6 => {
                            assert_eq!(docs.len(), 3);
                            assert_eq!(insertions.len(), 0);
                            assert_eq!(deletions.len(), 1);
                            assert_eq!(updates.len(), 0);
                            assert_eq!(old_documents.len(), 4);
                            // assert there is NOT a red honda
                            let results = store_copy.with_batched_write(
                                // 'batch 'store
                                |mut tx: ScopedStore<'_>| {
                                    let mut col_tx = tx.collection(col1_name.as_str());
                                    let redhyundai = TestType {
                                        make: String::from("Hyundai"),
                                        color: TestColor::Red,
                                        ..Default::default()
                                    };
                                    let pinkjeep = TestType {
                                        make: String::from("Jeep"),
                                        color: TestColor::Pink,
                                        ..Default::default()
                                    };
                                    *redhyundai_id.lock().unwrap() =
                                        col_tx.upsert(redhyundai).unwrap().clone();
                                    *pinkjeep_id.lock().unwrap() =
                                        col_tx.upsert(pinkjeep).unwrap().clone();
                                    // remove the red toyota
                                    col_tx
                                        .find_by_id(redtoyota_id.lock().unwrap().clone())
                                        .evict()
                                        .unwrap();
                                    // remove the orange mazda
                                    col_tx
                                        .find_by_id(orangemazda_id.lock().unwrap().clone())
                                        .evict()
                                        .unwrap();
                                    // make the Ford an audi
                                    col_tx
                                        .find_by_id(redford_id.lock().unwrap().clone())
                                        .update(|result| {
                                            if let Some(doc) = result {
                                                doc.set("make", String::from("Audi")).unwrap()
                                            }
                                        })
                                        .unwrap();
                                    // make the chevy white
                                    col_tx
                                        .find_by_id(redchevy_id.lock().unwrap().clone())
                                        .update(|result| {
                                            if let Some(doc) = result {
                                                doc.set("color", TestColor::White).unwrap()
                                            }
                                        })
                                        .unwrap();
                                    // make the aston martin red
                                    col_tx
                                        .find_by_id(yellowam_id.lock().unwrap().clone())
                                        .update(|result| {
                                            if let Some(doc) = result {
                                                doc.set("color", TestColor::Red).unwrap()
                                            }
                                        })
                                        .unwrap();
                                    // in the second collection
                                    // insert a black bmw, evict the red bentley, and make the
                                    // red jaguar a citroen

                                    let mut col2_tx = tx.collection(col2_name.as_str());
                                    let blackbmw = TestType {
                                        make: String::from("BMW"),
                                        color: TestColor::Black,
                                        ..Default::default()
                                    };
                                    *blackbmw_id.lock().unwrap() =
                                        col2_tx.upsert(blackbmw).unwrap().clone();
                                    col2_tx
                                        .find_by_id(redbentley_id.lock().unwrap().clone())
                                        .evict()
                                        .unwrap();
                                    col2_tx
                                        .find_by_id(redjaguar_id.lock().unwrap().clone())
                                        .update(|result| {
                                            if let Some(doc) = result {
                                                doc.set("make", String::from("Citroen")).unwrap();
                                            }
                                        })
                                        .unwrap();
                                    tx.commit_changes()
                                },
                            );
                            for (idx, result) in results.unwrap().iter().enumerate() {
                                match idx {
                                    0 => {
                                        assert_eq!(result.kind, DocChangeKind::Inserted);
                                    }
                                    1 => {
                                        assert_eq!(result.kind, DocChangeKind::Inserted);
                                    }
                                    2 => {
                                        assert_eq!(result.kind, DocChangeKind::Evicted);
                                    }
                                    3 => {
                                        assert_eq!(result.kind, DocChangeKind::Evicted);
                                    }
                                    4 => {
                                        assert_eq!(result.kind, DocChangeKind::Updated);
                                    }
                                    5 => {
                                        assert_eq!(result.kind, DocChangeKind::Updated);
                                    }
                                    6 => {
                                        assert_eq!(result.kind, DocChangeKind::Updated);
                                    }
                                    7 => {
                                        assert_eq!(result.kind, DocChangeKind::Inserted);
                                    }
                                    8 => {
                                        assert_eq!(result.kind, DocChangeKind::Evicted);
                                    }
                                    9 => {
                                        assert_eq!(result.kind, DocChangeKind::Updated);
                                    }
                                    _ => {
                                        panic!("Too many Results");
                                    }
                                }
                            }
                        }
                        7 => {
                            assert_eq!(docs.len(), 3);
                            assert_eq!(insertions.len(), 2);
                            assert_eq!(deletions.len(), 2);
                            assert_eq!(updates.len(), 1);
                            assert_eq!(old_documents.len(), 3);
                            // assert there is a red Audi, Hyundai, and Aston Martin
                            let redferrari = TestType {
                                make: String::from("Ferrari"),
                                color: TestColor::Red,
                                ..Default::default()
                            };
                            *redferrari_id.lock().unwrap() = collection.upsert(redferrari).unwrap();
                        }
                        8 => {
                            assert_eq!(docs.len(), 4);
                            assert_eq!(insertions.len(), 1);
                            assert_eq!(deletions.len(), 0);
                            assert_eq!(updates.len(), 0);
                            assert_eq!(old_documents.len(), 3);
                            // assert there is a red ferrari
                            //
                            let results = store_copy
                                .with_batched_write(|mut tx| {
                                    let mut coll = tx.collection(col1_name.as_str());
                                    // find the red ferrari, make it a porsche, then make it brown,
                                    // then evict it
                                    //
                                    let id = redferrari_id.lock().unwrap().clone();
                                    coll.find_by_id(id.clone())
                                        .update(|result| {
                                            if let Some(doc) = result {
                                                doc.set("make", String::from("Porsche")).unwrap()
                                            }
                                        })
                                        .unwrap();
                                    coll.find_by_id(id.clone())
                                        .update(|result| {
                                            if let Some(doc) = result {
                                                doc.set("color", TestColor::Brown).unwrap()
                                            }
                                        })
                                        .unwrap();
                                    coll.find_by_id(id).evict().unwrap();
                                    tx.commit_changes()
                                })
                                .unwrap();
                            for (idx, result) in results.iter().enumerate() {
                                match idx {
                                    0 => {
                                        assert_eq!(result.kind, DocChangeKind::Updated);
                                    }
                                    1 => {
                                        assert_eq!(result.kind, DocChangeKind::Updated);
                                    }
                                    2 => {
                                        assert_eq!(result.kind, DocChangeKind::Evicted);
                                    }
                                    _ => {
                                        panic!("Too many results");
                                    }
                                }
                            }
                        }
                        9 => {
                            assert_eq!(docs.len(), 3);
                            assert_eq!(insertions.len(), 0);
                            assert_eq!(deletions.len(), 1);
                            assert_eq!(updates.len(), 0);
                            assert_eq!(old_documents.len(), 4);
                            is_complete_copy.store(true, Ordering::SeqCst);
                        }
                        _ => {
                            panic!("Unexpected count {}", count);
                        }
                    }
                }
            }
        }
    };

    let lq = col1
        .find("color == \'Red\'")
        .observe_local(handler)
        .unwrap();

    while !is_complete.load(Ordering::SeqCst) {}
    lq.stop();
    Ok(())
}
