#![allow(deprecated)]
use std::{sync::Mutex, time::Duration};

use super::*;
pub fn get_offline_ditto() -> Result<Ditto, DittoError> {
    let ditto = Ditto::builder()
        .with_temp_dir()
        .with_identity(|ditto_root| {
            identity::OfflinePlayground::new(ditto_root, AppId::generate())
        })?
        .with_minimum_log_level(LogLevel::Error)
        .build()?;
    ditto.set_license_from_env("DITTO_LICENSE").ok();
    ditto.disable_sync_with_v3().unwrap();
    Ok(ditto)
}

pub fn get_offline_ditto_with_root(root: Arc<dyn DittoRoot>) -> Result<Ditto, DittoError> {
    let ditto = Ditto::builder()
        .with_root(root)
        .with_identity(|ditto_root| {
            identity::OfflinePlayground::new(ditto_root, AppId::generate())
        })?
        .with_minimum_log_level(LogLevel::Error)
        .build()?;
    ditto.set_license_from_env("DITTO_LICENSE").ok();
    ditto.disable_sync_with_v3().unwrap();
    Ok(ditto)
}

#[test]
fn test_get_version() {
    let version = Ditto::version();
    assert_ne!(version, "0.0.0");
    assert_eq!(version, env!("CARGO_PKG_VERSION"));
}

#[test]
fn test_observe_peers() {
    let ditto = get_offline_ditto().unwrap();
    ditto.sync().start().unwrap();

    let is_observed = Arc::new(Mutex::new(false));
    let is_observed_2 = is_observed.retain();
    #[allow(deprecated)]
    let _obs = ditto.observe_peers(move |_v2_presence| {
        let mut observed = is_observed_2.lock().unwrap();
        *observed = true;
    });

    // CHECKME : can do better ?
    while !*is_observed.lock().unwrap() {}
}

#[test]
fn test_presence_observe() {
    let ditto = get_offline_ditto().unwrap();
    ditto.sync().start().unwrap();

    let is_observed = Arc::new(Mutex::new(false));
    let is_observed_2 = is_observed.retain();
    let _obs = ditto.presence().register_observer(move |_graph| {
        let mut observed = is_observed_2.lock().unwrap();
        *observed = true;
    });

    // CHECKME : can do better ?
    while !*is_observed.lock().unwrap() {}
}

#[test]
fn test_presence_exec() {
    let ditto = get_offline_ditto().unwrap();
    ditto.sync().start().unwrap();

    let graph = ditto.presence().graph();
    assert!(graph.local_peer.connections.is_empty());
    assert!(graph.remote_peers.is_empty());
}

#[test]
fn test_observe_multiple_peers() {
    let ditto = get_offline_ditto().unwrap();
    ditto.sync().start().unwrap();

    let is_observed_1 = Arc::new(Mutex::new(false));
    let is_observed_1_copy = is_observed_1.retain();
    let is_observed_2 = Arc::new(Mutex::new(false));
    let is_observed_2_copy = is_observed_2.retain();
    let _obs_1 = ditto.presence().register_observer(move |_presence_graph| {
        let mut observed = is_observed_1_copy.lock().unwrap();
        *observed = true;
    });

    let _obs_2 = ditto.presence().register_observer(move |_presence_graph| {
        let mut observed = is_observed_2_copy.lock().unwrap();
        *observed = true;
    });

    // CHECKME : can do better ?
    while !(*is_observed_1.lock().unwrap() && *is_observed_2.lock().unwrap()) {}
}

#[test]
fn test_disk_usage_exec() {
    let ditto = get_offline_ditto().unwrap();
    let tree = ditto.disk_usage().exec();
    assert_eq!(tree.path, "ditto");
}

#[test]
fn test_disk_usage_observe() {
    let (tx, rx) = std::sync::mpsc::sync_channel(1);
    let ditto = get_offline_ditto().unwrap();
    let _observer = ditto.disk_usage().observe(move |_| {
        tx.send(()).unwrap();
    });
    let mut path = ditto.absolute_persistence_directory().to_path_buf();
    path.push("file.tmp");
    std::fs::File::create(path).unwrap();
    rx.recv().unwrap();
}

#[test]
fn test_store_disk_usage_exec() {
    let ditto = get_offline_ditto().unwrap();
    #[allow(deprecated)]
    let tree = ditto.store.disk_usage().exec();
    assert_eq!(PathBuf::from(tree.path), PathBuf::from("ditto/ditto_store"));
}

#[test]
fn test_store_disk_usage_observe() {
    let (tx, rx) = std::sync::mpsc::sync_channel(1);
    let ditto = get_offline_ditto().unwrap();
    let _observer = ditto.disk_usage().observe(move |_| {
        tx.send(()).unwrap();
    });
    let mut path = ditto.absolute_persistence_directory().to_path_buf();
    path.push("ditto_store");
    path.push("file.tmp");
    std::fs::File::create(path).unwrap();
    rx.recv().unwrap();
}

#[test]
fn test_multiple_restart() {
    let ditto = get_offline_ditto().unwrap();
    let tmp_path = ditto.root();
    ditto.sync().start().unwrap();
    ditto.sync().stop();
    ditto.close();
    let ditto_to_be_created_rx;
    {
        let tx;
        (tx, ditto_to_be_created_rx) = ::std::sync::mpsc::channel();
        ::std::thread::spawn(move || {
            let ditto = get_offline_ditto_with_root(tmp_path).unwrap();
            tx.send(ditto).unwrap();
        });
    };
    let Ok(ditto) = ditto_to_be_created_rx.recv_timeout(Duration::from_secs(5)) else {
        // (FIXME: investigate why this can occur, and fix the underlying issue)
        panic!("timed out creating new ditto instance");
    };
    ditto.disable_sync_with_v3().unwrap();
    ditto.sync().start().unwrap();
    ditto.sync().stop();
}

#[test]
fn test_ditto_data_migration() {
    let tmp = Arc::new(TempRoot::new());

    let mut path: PathBuf = tmp.root_path().into();
    path.push("ditto_data");
    // creating folder first since creating file may fail if the directory doesn't exist,
    // depending of the platform.
    path.push("randomDirectory");
    #[allow(clippy::disallowed_methods)]
    std::fs::create_dir_all(&path).unwrap();
    // creating file
    path.push("randomFile");
    std::fs::File::create(path).unwrap();

    let ditto = get_offline_ditto_with_root(tmp).unwrap();
    let mut root: PathBuf = ditto.absolute_persistence_directory();
    root.push("randomDirectory");
    root.push("randomFile");
    assert!(root.exists());
    root.pop();
    root.push("ditto_data");
    assert!(root.exists().not());
}

#[tokio::test]
async fn test_ditto_deep_data_migration() {
    use serde_json::json;
    // base
    let tmp = Arc::new(TempRoot::new());
    let mut path: PathBuf = tmp.root_path().into();
    path.push("ditto_data");
    // create Ditto within a ditto_data folder
    let data_root = Arc::new(PersistentRoot::new(path.clone()).unwrap());
    let ditto = get_offline_ditto_with_root(data_root).unwrap();
    ditto.disable_sync_with_v3().unwrap();
    // write a document with content
    let store = ditto.store();
    let doc_id = store
        .execute_v2((
            "INSERT INTO test DOCUMENTS (:doc)",
            json!({"doc": {"Seth": 7}}),
        ))
        .await
        .unwrap()
        .mutated_document_ids()[0]
        .clone();
    // close it
    ditto.close();

    // create Ditto at ditto/
    let ditto = get_offline_ditto_with_root(tmp).unwrap();
    let store = ditto.store();
    let docs = store
        .execute_v2(("SELECT * FROM test WHERE _id = :id", json!({"id": doc_id})))
        .await
        .unwrap()
        .iter()
        .collect::<Vec<_>>();
    // read document
    let doc = &docs[0];
    let value: u64 = doc
        .deserialize_value::<serde_json::Value>()
        .as_ref()
        .unwrap()["Seth"]
        .as_u64()
        .unwrap();
    assert_eq!(value, 7);
    ditto.close();

    assert!(path.exists().not());
}
