use crate::{arg_enums::SyncMode, params::node_key_params::NodeKeyParams};
use clap::Args;
use sc_network::{
	config::{
		NetworkConfiguration, NodeKeyConfig, NonReservedPeerMode, SetConfig, TransportConfig,
	},
	multiaddr::Protocol,
};
use sc_service::{
	config::{Multiaddr, MultiaddrWithPeerId},
	ChainSpec, ChainType,
};
use std::{borrow::Cow, num::NonZeroUsize, path::PathBuf};
#[derive(Debug, Clone, Args)]
pub struct NetworkParams {
	#[arg(long, value_name = "ADDR", num_args = 1..)]
	pub bootnodes: Vec<MultiaddrWithPeerId>,
	#[arg(long, value_name = "ADDR", num_args = 1..)]
	pub reserved_nodes: Vec<MultiaddrWithPeerId>,
	#[arg(long)]
	pub reserved_only: bool,
	#[arg(long, value_name = "PUBLIC_ADDR", num_args = 1..)]
	pub public_addr: Vec<Multiaddr>,
	#[arg(long, value_name = "LISTEN_ADDR", num_args = 1..)]
	pub listen_addr: Vec<Multiaddr>,
	#[arg(long, value_name = "PORT", conflicts_with_all = &[ "listen_addr" ])]
	pub port: Option<u16>,
	#[arg(long, alias = "no-private-ipv4", conflicts_with_all = &["allow_private_ip"])]
	pub no_private_ip: bool,
	#[arg(long, alias = "allow-private-ipv4", conflicts_with_all = &["no_private_ip"])]
	pub allow_private_ip: bool,
	#[arg(long, value_name = "COUNT", default_value_t = 8)]
	pub out_peers: u32,
	#[arg(long, value_name = "COUNT", default_value_t = 32)]
	pub in_peers: u32,
	#[arg(long, value_name = "COUNT", default_value_t = 100)]
	pub in_peers_light: u32,
	#[arg(long)]
	pub no_mdns: bool,
	#[arg(long, value_name = "COUNT", default_value_t = 5)]
	pub max_parallel_downloads: u32,
	#[allow(missing_docs)]
	#[clap(flatten)]
	pub node_key_params: NodeKeyParams,
	#[arg(long)]
	pub discover_local: bool,
	#[arg(long)]
	pub kademlia_disjoint_query_paths: bool,
	#[arg(long, default_value = "20")]
	pub kademlia_replication_factor: NonZeroUsize,
	#[arg(long)]
	pub ipfs_server: bool,
	#[arg(
		long,
		value_enum,
		value_name = "SYNC_MODE",
		default_value_t = SyncMode::Full,
		ignore_case = true,
		verbatim_doc_comment
	)]
	pub sync: SyncMode,
	#[arg(long, value_name = "COUNT", default_value_t = 64)]
	pub max_blocks_per_request: u32,
}
impl NetworkParams {
	pub fn network_config(
		&self,
		chain_spec: &Box<dyn ChainSpec>,
		is_dev: bool,
		is_validator: bool,
		net_config_path: Option<PathBuf>,
		client_id: &str,
		node_name: &str,
		node_key: NodeKeyConfig,
		default_listen_port: u16,
	) -> NetworkConfiguration {
		let port = self.port.unwrap_or(default_listen_port);
		let listen_addresses = if self.listen_addr.is_empty() {
			if is_validator || is_dev {
				vec![
					Multiaddr::empty()
						.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
						.with(Protocol::Tcp(port)),
					Multiaddr::empty()
						.with(Protocol::Ip4([0, 0, 0, 0].into()))
						.with(Protocol::Tcp(port)),
				]
			} else {
				vec![
					Multiaddr::empty()
						.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
						.with(Protocol::Tcp(port))
						.with(Protocol::Ws(Cow::Borrowed("/"))),
					Multiaddr::empty()
						.with(Protocol::Ip4([0, 0, 0, 0].into()))
						.with(Protocol::Tcp(port))
						.with(Protocol::Ws(Cow::Borrowed("/"))),
				]
			}
		} else {
			self.listen_addr.clone()
		};
		let public_addresses = self.public_addr.clone();
		let mut boot_nodes = chain_spec.boot_nodes().to_vec();
		boot_nodes.extend(self.bootnodes.clone());
		let chain_type = chain_spec.chain_type();
		let allow_non_globals_in_dht =
			self.discover_local ||
				is_dev || matches!(chain_type, ChainType::Local | ChainType::Development);
		let allow_private_ip = match (self.allow_private_ip, self.no_private_ip) {
			(true, true) => unreachable!("`*_private_ip` flags are mutually exclusive; qed"),
			(true, false) => true,
			(false, true) => false,
			(false, false) =>
				is_dev || matches!(chain_type, ChainType::Local | ChainType::Development),
		};
		NetworkConfiguration {
			boot_nodes,
			net_config_path,
			default_peers_set: SetConfig {
				in_peers: self.in_peers + self.in_peers_light,
				out_peers: self.out_peers,
				reserved_nodes: self.reserved_nodes.clone(),
				non_reserved_mode: if self.reserved_only {
					NonReservedPeerMode::Deny
				} else {
					NonReservedPeerMode::Accept
				},
			},
			default_peers_set_num_full: self.in_peers + self.out_peers,
			listen_addresses,
			public_addresses,
			node_key,
			node_name: node_name.to_string(),
			client_version: client_id.to_string(),
			transport: TransportConfig::Normal {
				enable_mdns: !is_dev && !self.no_mdns,
				allow_private_ip,
			},
			max_parallel_downloads: self.max_parallel_downloads,
			max_blocks_per_request: self.max_blocks_per_request,
			enable_dht_random_walk: !self.reserved_only,
			allow_non_globals_in_dht,
			kademlia_disjoint_query_paths: self.kademlia_disjoint_query_paths,
			kademlia_replication_factor: self.kademlia_replication_factor,
			yamux_window_size: None,
			ipfs_server: self.ipfs_server,
			sync_mode: self.sync.into(),
		}
	}
}
#[cfg(test)]
mod tests {
	use super::*;
	use clap::Parser;
	#[derive(Parser)]
	struct Cli {
		#[clap(flatten)]
		network_params: NetworkParams,
	}
	#[test]
	fn reserved_nodes_multiple_values_and_occurrences() {
		let params = Cli::try_parse_from([
			"",
			"--reserved-nodes",
			"/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
			"/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
			"--reserved-nodes",
			"/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
		])
		.expect("Parses network params");
		let expected = vec![
			MultiaddrWithPeerId::try_from(
				"/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
					.to_string(),
			)
			.unwrap(),
			MultiaddrWithPeerId::try_from(
				"/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
					.to_string(),
			)
			.unwrap(),
			MultiaddrWithPeerId::try_from(
				"/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
					.to_string(),
			)
			.unwrap(),
		];
		assert_eq!(expected, params.network_params.reserved_nodes);
	}
	#[test]
	fn sync_ingores_case() {
		let params = Cli::try_parse_from(["", "--sync", "wArP"]).expect("Parses network params");
		assert_eq!(SyncMode::Warp, params.network_params.sync);
	}
}