use std::{str::FromStr, sync::Arc};

use anyhow::Result;
use dittolive_ditto::{fs::TempRoot, prelude::*};

pub fn get_ditto(
    database_id: Option<DatabaseId>,
    use_server_mode: bool,
) -> Result<(TempRoot, Ditto)> {
    let database_id = database_id.unwrap_or_else(DatabaseId::generate);

    let connect = if use_server_mode {
        DittoConfigConnect::Server {
            url: format!("https://{}.cloud.ditto.live", database_id)
                .parse()
                .unwrap(),
        }
    } else {
        DittoConfigConnect::SmallPeersOnly { private_key: None }
    };
    let temp_root = TempRoot::new();
    let config = DittoConfig::new(database_id.to_string(), connect)
        .with_persistence_directory(temp_root.root_path());
    let ditto = Ditto::open_sync(config)?;
    if !use_server_mode {
        let _ = ditto.set_license_from_env("DITTO_LICENSE");
    }
    Ok((temp_root, ditto))
}

/// This test checks that unorthodox, mixed-case DatabaseIds will continue to work when using
/// SmallPeersOnly mode. We have some customers who use mixed-case DatabaseIds.
#[tokio::test]
#[ignore = "Requires DITTO_LICENSE environment variable"]
async fn mixed_case_database_id_succeeds_with_shared_key_authentication() -> Result<()> {
    let database_id = DatabaseId::from_str("heLLo")?;
    let (_root1, ditto1) = get_ditto(Some(database_id.clone()), false)?;
    let (_root2, ditto2) = get_ditto(Some(database_id), false)?;

    let mut server = TransportConfig::new();
    server.listen.tcp = TcpListenConfig {
        enabled: true,
        interface_ip: "127.0.0.1".into(),
        port: 53644,
    };
    ditto1.set_transport_config(server);

    let mut client = TransportConfig::new();
    client
        .connect
        .tcp_servers
        .insert("localhost:53644".to_string());
    ditto2.set_transport_config(client);

    ditto1.sync().start()?;
    ditto2.sync().start()?;

    let ditto2 = Arc::new(ditto2);
    assert_connected_peers(Arc::clone(&ditto2), 1).await;

    Ok(())
}

/// DatabaseIds are case-insensitive
#[tokio::test]
#[ignore = "Requires DITTO_LICENSE environment variable"]
async fn mixed_case_database_id_succeeds_with_shared_key_authentication_on_peers_with_different_cases(
) -> Result<()> {
    let database_id_1 = DatabaseId::from_str("HeLLo")?;
    let database_id_2 = DatabaseId::from_str("hello")?;
    let (_root1, ditto1) = get_ditto(Some(database_id_1), false)?;
    let (_root2, ditto2) = get_ditto(Some(database_id_2), false)?;

    let mut server = TransportConfig::new();
    server.listen.tcp = TcpListenConfig {
        enabled: true,
        interface_ip: "127.0.0.1".into(),
        port: 53645,
    };
    ditto1.set_transport_config(server);

    let mut client = TransportConfig::new();
    client
        .connect
        .tcp_servers
        .insert("localhost:53645".to_string());
    ditto2.set_transport_config(client);

    ditto1.sync().start()?;
    ditto2.sync().start()?;

    let ditto2 = Arc::new(ditto2);
    assert_connected_peers(Arc::clone(&ditto2), 1).await;

    Ok(())
}

/// This test checks that uuid-based DatabaseIds are enforced for Server connection mode.
#[tokio::test]
async fn database_id_must_be_a_uuid_with_online_with_authentication() -> Result<()> {
    assert!(get_ditto(Some(DatabaseId::from_str("hello")?), true).is_err());
    assert!(get_ditto(Some(DatabaseId::from_str("HELLO")?), true).is_err());
    assert!(get_ditto(Some(DatabaseId::from_str("E4CAB6FB-6B3F")?), true).is_err());
    assert!(get_ditto(
        Some(DatabaseId::from_str(
            "E4CAB6FB_6B3F_4173_95EB_B5162034C235"
        )?),
        true,
    )
    .is_err(),);

    // DatabaseIds are case-insensitive
    assert!(get_ditto(
        Some(DatabaseId::from_str(
            "E4CAB6FB-6B3F-4173-95EB-B5162034C235"
        )?),
        true,
    )
    .is_ok(),);
    assert!(get_ditto(
        Some(DatabaseId::from_str(
            "e4cab6fb-6b3f-4173-95eb-b5162034c235"
        )?),
        true,
    )
    .is_ok());
    assert!(get_ditto(
        Some(DatabaseId::from_str(
            "E4CAB6FB-6b3f-4173-95eb-b5162034c235"
        )?),
        true,
    )
    .is_ok());

    Ok(())
}

/// Check whether the provided Ditto instance has `count` number of connections. Retry for 10
/// seconds. This function will panic if the `count` requirement is not met within 10 seconds.
async fn assert_connected_peers(ditto: Arc<Ditto>, count: usize) {
    let f = tokio::spawn(async move {
        let mut cur_peers = 0;
        for _ in 0..10 {
            cur_peers = ditto.presence().graph().remote_peers.len();
            if cur_peers == count {
                return Ok(());
            }

            tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        }

        Err(cur_peers)
    });

    let result = tokio::time::timeout(std::time::Duration::from_secs(11), f)
        .await
        .unwrap()
        .unwrap();

    if let Err(peers_count) = result {
        panic!("Expected {count} connected peer(s), got {peers_count}");
    }
}
