use {
	crate::utils::{discover_all, timeout_s},
	futures::future::join_all,
	mosaik::{
		groups::{Group, StateMachine},
		primitives::Short,
		*,
	},
};

#[tokio::test]
async fn members_can_see_each_other() -> anyhow::Result<()> {
	// bond formation timeout in seconds
	const T: u64 = 5;

	let network_id = NetworkId::random();

	let n0 = Network::new(network_id).await?;
	let n1 = Network::new(network_id).await?;
	let n2 = Network::new(network_id).await?;
	let n3 = Network::new(network_id).await?;

	discover_all([&n0, &n1, &n2, &n3]).await?;

	let key1 = GroupKey::from_secret("group1secret");
	let key2 = GroupKey::from_secret("group2secret");
	let key3 = GroupKey::random();

	tracing::debug!("group1 key: {key1}");
	tracing::debug!("group2 key: {key2}");
	tracing::debug!("group3 key: {key3}");

	// group1: n0 and n1
	let g1_0 = n0.groups().with_key(key1).join();
	let g1_1 = n1.groups().with_key(key1).join();

	// group2: n0, n2 and n3
	let g2_0 = n0.groups().with_key(key2).join();
	let g2_2 = n2.groups().with_key(key2).join();
	let g2_3 = n3.groups().with_key(key2).join();

	// group3: n1 and n2
	let g3_1 = n1.groups().with_key(key3).join();
	let g3_2 = n2.groups().with_key(key3).join();

	tracing::debug!("group1 id: {}", Short(g1_0.id()));
	tracing::debug!("group2 id: {}", Short(g2_0.id()));
	tracing::debug!("group3 id: {}", Short(g3_1.id()));

	join_all([
		timeout_s(T, ensure_bonds_formed(&g1_0, &n0, &[&n1], "g1_0")),
		timeout_s(T, ensure_bonds_formed(&g1_1, &n1, &[&n0], "g1_1")),
		timeout_s(T, ensure_bonds_formed(&g2_0, &n0, &[&n2, &n3], "g2_0")),
		timeout_s(T, ensure_bonds_formed(&g2_2, &n2, &[&n0, &n3], "g2_2")),
		timeout_s(T, ensure_bonds_formed(&g2_3, &n3, &[&n0, &n2], "g2_3")),
		timeout_s(T, ensure_bonds_formed(&g3_1, &n1, &[&n2], "g3_1")),
		timeout_s(T, ensure_bonds_formed(&g3_2, &n2, &[&n1], "g3_2")),
	])
	.await
	.into_iter()
	.collect::<Result<Vec<_>, _>>()
	.expect("not all groups formed successfully");

	Ok(())
}

async fn ensure_bonds_formed<M: StateMachine>(
	group: &Group<M>,
	local: &Network,
	peers: &[&Network],
	name: &str,
) {
	loop {
		let bonds = group.bonds();

		if bonds.is_empty() {
			group.bonds().changed().await;
		}

		tracing::info!(
			"{name} bonds changed (local = {})",
			Short(local.local().id())
		);

		for bond in group.bonds().iter() {
			tracing::info!("- {name} bonded with: {}", Short(bond.peer().id()));
		}
		if peers.iter().all(|p| {
			group
				.bonds()
				.iter()
				.any(|b| *b.peer().id() == p.local().id())
		}) {
			tracing::info!("{name} group fully formed");
			break;
		}

		group.bonds().changed().await;
	}
}
