use std::{sync::Mutex, time::Duration};

use super::*;
use crate::{
    ditto::init::{DittoConfig, DittoConfigConnect},
    fs::TempRoot,
};

pub fn get_offline_ditto() -> Result<(TempRoot, Ditto), DittoError> {
    let root = TempRoot::new();
    let config = DittoConfig::new(
        DatabaseId::generate().to_string(),
        DittoConfigConnect::SmallPeersOnly { private_key: None },
    )
    .with_persistence_directory(root.root_path());
    let ditto = Ditto::open_sync(config)?;
    ditto.set_license_from_env("DITTO_LICENSE").unwrap();
    Ok((root, ditto))
}

#[test]
fn test_presence_observe() {
    let (_temp_root, 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 (_temp_root, 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 (_temp_root, 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_item() {
    let (_temp_root, ditto) = get_offline_ditto().unwrap();
    let tree = ditto.disk_usage().item();
    assert!(!tree.path.is_empty());
}

#[test]
fn test_disk_usage_observe() {
    let (tx, rx) = std::sync::mpsc::sync_channel(1);
    let (_temp_root, 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_observe() {
    let (tx, rx) = std::sync::mpsc::sync_channel(1);
    let (_temp_root, 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 (_temp_root, ditto) = get_offline_ditto().unwrap();
    let persistence_dir = ditto.absolute_persistence_directory();
    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 config = DittoConfig::new(
                DatabaseId::generate().to_string(),
                DittoConfigConnect::SmallPeersOnly { private_key: None },
            )
            .with_persistence_directory(&persistence_dir);
            let ditto = Ditto::open_sync(config).unwrap();
            ditto.set_license_from_env("DITTO_LICENSE").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.sync().start().unwrap();
    ditto.sync().stop();
}

#[test]
fn test_sync_stop_and_is_active() {
    let (_temp_root, ditto) = get_offline_ditto().unwrap();

    // Sync should not be active initially
    assert!(!ditto.sync().is_active());

    // Start sync
    ditto.sync().start().unwrap();
    assert!(ditto.sync().is_active());

    // Stop sync using new API
    ditto.sync().stop();
    assert!(!ditto.sync().is_active());

    // Restart and stop again
    ditto.sync().start().unwrap();
    assert!(ditto.sync().is_active());
    ditto.sync().stop();
    assert!(!ditto.sync().is_active());
}

#[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_presence_reports_correct_sdk_version() {
    let (_temp_root, ditto) = get_offline_ditto().unwrap();
    ditto.sync().start().unwrap();

    let graph = ditto.presence().graph();
    let local_version = graph
        .local_peer
        .ditto_sdk_version
        .as_deref()
        .expect("local peer should have a ditto_sdk_version");
    assert_ne!(
        local_version, "0.0.0",
        "SDK version was not initialized before presence graph"
    );
    assert_eq!(local_version, env!("CARGO_PKG_VERSION"));
}
