use std::sync::Mutex;

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(CLogLevel::Info)
        .build()?;
    ditto.set_license_from_env("DITTO_LICENSE").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(CLogLevel::Info)
        .build()?;
    ditto.set_license_from_env("DITTO_LICENSE").unwrap();
    Ok(ditto)
}

#[test]
fn test_default() {
    let ditto = get_offline_ditto().unwrap();
    let effective_config = ditto.effective_transport_config().unwrap();
    assert!(effective_config.connect.websocket_urls.is_empty());
}

#[test]
fn test_start_sync() {
    let ditto = get_offline_ditto().unwrap();
    let mut config = TransportConfig::new();
    config.peer_to_peer.bluetooth_le.enabled = true;
    config.peer_to_peer.lan.enabled = true;
    config
        .connect
        .websocket_urls
        .insert(String::from("ws://ditto.com"));
    ditto.set_transport_config(config);
    ditto.start_sync().unwrap();
    let effective_config = ditto.effective_transport_config().unwrap();
    assert!(!effective_config.connect.websocket_urls.is_empty());
    let tr = ditto.transports.read().unwrap();
    assert!(!tr.ws_clients.is_empty());
}

#[test]
fn test_stop_sync() {
    let ditto = get_offline_ditto().unwrap();
    let mut config = TransportConfig::new();
    config.peer_to_peer.bluetooth_le.enabled = true;
    config.peer_to_peer.lan.enabled = true;
    config
        .connect
        .websocket_urls
        .insert(String::from("ws://ditto.com"));
    ditto.set_transport_config(config);
    ditto.start_sync().unwrap();
    let effective_config = ditto.effective_transport_config().unwrap();
    assert!(!effective_config.connect.websocket_urls.is_empty());
    ditto.stop_sync();
    let tr = ditto.transports.read().unwrap();
    assert!(tr.ws_clients.is_empty());
}

#[test]
fn test_cloud_url_persistence() {
    let ditto = get_offline_ditto().unwrap();
    let mut config = TransportConfig::new();
    config.peer_to_peer.bluetooth_le.enabled = true;
    config
        .connect
        .websocket_urls
        .insert(String::from("ws://ditto.com"));

    ditto.set_transport_config(config);
    let config = ditto.current_transport_config().unwrap();
    assert!(!config.connect.websocket_urls.is_empty());
    let effective_config = ditto.effective_transport_config().unwrap();
    assert!(effective_config.connect.websocket_urls.is_empty());

    ditto.start_sync().unwrap();
    let config = ditto.current_transport_config().unwrap();
    assert!(!config.connect.websocket_urls.is_empty());
    let effective_config = ditto.effective_transport_config().unwrap();
    assert!(!effective_config.connect.websocket_urls.is_empty());
}

#[test]
fn test_observe_peers() {
    let ditto = get_offline_ditto().unwrap();
    ditto.start_sync().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.start_sync().unwrap();

    let is_observed = Arc::new(Mutex::new(false));
    let is_observed_2 = is_observed.retain();
    let _obs = ditto.presence().observe(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.start_sync().unwrap();

    let graph = ditto.presence().exec();
    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.start_sync().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().observe(move |_presence_graph| {
        let mut observed = is_observed_1_copy.lock().unwrap();
        *observed = true;
    });

    let _obs_2 = ditto.presence().observe(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.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();
    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.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.start_sync().unwrap();
    ditto.stop_sync();
    ditto.close();
    let ditto = get_offline_ditto_with_root(tmp_path).unwrap();
    ditto.start_sync().unwrap();
    ditto.stop_sync();
}

#[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");
    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.persistence_directory().into();
    root.push("randomDirectory");
    root.push("randomFile");
    assert!(root.exists());
    root.pop();
    root.push("ditto_data");
    assert!(root.exists().not());
}

#[test]
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();
    // write a document with content
    let store = ditto.store();
    let collection = store.collection("test").unwrap();
    let doc_id = collection.upsert(json!({"Seth": 7})).unwrap();
    // close it
    ditto.close();

    // create Ditto at ditto/
    let ditto = get_offline_ditto_with_root(tmp).unwrap();
    let store = ditto.store();
    let collection = store.collection("test").unwrap();
    let doc = collection.find_by_id(doc_id.clone()).exec().unwrap();
    // read document
    let value: u32 = doc.get("Seth").unwrap();
    assert_eq!(value, 7);
    ditto.close();

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