#![allow(deprecated)]
use std::{path::PathBuf, sync::Arc};

use dittolive_ditto::prelude::*;

mod common;
use common::*;

/// Test that peers will recover and sync if a persistence directory is copied
/// and those copies are used on two devices simultaneously.
///
/// This is not recommended or supported by Ditto. If it happens anyway, upon
/// connecting to each other the two clones should recognise that their Peer Key
/// is no longer private to them. Both sides will decide to create a new key and
/// restart sync automatically.
///
/// The result from the user's perspective is that sync "just works".
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn recovery_from_duplicated_persistence_dir() {
    const COMPONENT_1: &str = "recovery_from_duplicated_persistence_dir_1";
    const COMPONENT_2: &str = "recovery_from_duplicated_persistence_dir_2";

    let app_id = ::uuid::Uuid::new_v4().to_string();

    let root = ::tempfile::tempdir().unwrap();
    let mut path_1 = root.path().to_path_buf();
    path_1.push(COMPONENT_1);
    let mut path_2 = root.path().to_path_buf();
    path_2.push(COMPONENT_2);

    let ditto_1 = Ditto::builder()
        .with_root(Arc::new(PersistentRoot::new(path_1.clone()).unwrap()))
        .with_identity(|ditto_root| OfflinePlayground::new(ditto_root, app_id.parse().unwrap()))
        .unwrap()
        .build()
        .unwrap();
    ditto_1.set_license_from_env("DITTO_LICENSE").ok();
    ditto_1.disable_sync_with_v3().unwrap();
    ditto_1.close();

    // Duplicate the persistence directory
    for entry in ::walkdir::WalkDir::new(path_1.clone()) {
        let entry = entry.unwrap();
        if !entry.file_type().is_file() {
            continue;
        }
        let path = entry.path();
        let dest = PathBuf::from(
            path.as_os_str()
                .to_string_lossy()
                .replace(COMPONENT_1, COMPONENT_2),
        );
        #[allow(clippy::disallowed_methods)]
        let _ = std::fs::create_dir_all(dest.parent().unwrap());
        std::fs::copy(path, dest).unwrap();
    }

    let ditto_1 = Ditto::builder()
        .with_root(Arc::new(PersistentRoot::new(path_1).unwrap()))
        .with_identity(|ditto_root| OfflinePlayground::new(ditto_root, app_id.parse().unwrap()))
        .unwrap()
        .with_transport_config(|_| {
            let mut config = TransportConfig::new();
            config.listen.tcp.enabled = true;
            config.listen.tcp.interface_ip = "127.0.0.1".to_owned();
            config.listen.tcp.port = 30001;
            config
        })
        .unwrap()
        .with_minimum_log_level(LogLevel::Debug)
        .build()
        .unwrap();
    let ditto_2 = Ditto::builder()
        .with_root(Arc::new(PersistentRoot::new(path_2).unwrap()))
        .with_identity(|ditto_root| OfflinePlayground::new(ditto_root, app_id.parse().unwrap()))
        .unwrap()
        .with_transport_config(|_| {
            let mut config = TransportConfig::new();
            config
                .connect
                .tcp_servers
                .insert("127.0.0.1:30001".to_owned());
            config
        })
        .unwrap()
        .with_minimum_log_level(LogLevel::Debug)
        .build()
        .unwrap();
    ditto_1.set_license_from_env("DITTO_LICENSE").ok();
    ditto_2.set_license_from_env("DITTO_LICENSE").ok();
    ditto_1.disable_sync_with_v3().unwrap();
    ditto_2.disable_sync_with_v3().unwrap();

    ditto_1.set_device_name("ditto_1");
    ditto_2.set_device_name("ditto_2");

    let (seen_2_tx, mut seen_2_rx) = ::tokio::sync::mpsc::unbounded_channel();
    let presence_observer_1 = ditto_1.presence().register_observer(move |graph| {
        if let Some(_seen_ditto_2) = graph
            .remote_peers
            .iter()
            .find(|peer| peer.device_name == "ditto_2")
        {
            seen_2_tx.send(()).ok();
        }
    });

    let (seen_1_tx, mut seen_1_rx) = ::tokio::sync::mpsc::unbounded_channel();
    let presence_observer_2 = ditto_2.presence().register_observer(move |graph| {
        if let Some(_seen_ditto_1) = graph
            .remote_peers
            .iter()
            .find(|peer| peer.device_name == "ditto_1")
        {
            seen_1_tx.send(()).ok();
        }
    });

    ditto_1.sync().start().unwrap();
    ditto_2.sync().start().unwrap();

    ::tokio::time::timeout(::std::time::Duration::from_secs(30), async {
        seen_1_rx.recv().await.unwrap();
        seen_2_rx.recv().await.unwrap();
    })
    .await
    .unwrap();

    ditto_1.sync().stop();
    ditto_2.sync().stop();
    drop(presence_observer_1);
    drop(presence_observer_2);
}
