Documentation
use std::net::{
	Ipv4Addr,
	Ipv6Addr,
	SocketAddr,
	SocketAddrV4,
	SocketAddrV6
};

use ipnetwork::Ipv4Network;

use crate::util::fmt_bytes;
use crate::net::address::InterfaceAddress;
use crate::config::Config;
use crate::control::KeyArg;
use crate::peer::{
	Address,
	NodeID
};



#[test]
fn node_id() {

	println!("testing known good id strings");

	let good_ids: [(&str, [u8; 6]); 10] = [
		("nbYYd1-k", [0x9D, 0xB6, 0x18, 0x77, 0x5F, 0xA4]),
		("V6X_GkqJ", [0x57, 0xA5, 0xFF, 0x1A, 0x4A, 0x89]),
		("pfL3udnz", [0xA5, 0xF2, 0xF7, 0xB9, 0xD9, 0xF3]),
		("e1n7zlYm", [0x7B, 0x59, 0xFB, 0xCE, 0x56, 0x26]),
		("rTsxHkTp", [0xAD, 0x3B, 0x31, 0x1E, 0x44, 0xE9]),
		("0xATfaUf", [0xD3, 0x10, 0x13, 0x7D, 0xA5, 0x1F]),
		("hcSDERrj", [0x85, 0xC4, 0x83, 0x11, 0x1A, 0xE3]),
		("las0IFEY", [0x95, 0xAB, 0x34, 0x20, 0x51, 0x18]),
		("q2kBjiUF", [0xAB, 0x69, 0x01, 0x8E, 0x25, 0x05]),
		("t94Blkn8", [0xB7, 0xDE, 0x01, 0x96, 0x49, 0xFC])
	];

	for (id_str, id_bytes) in good_ids {
		let id = id_str.parse::<NodeID>().unwrap();
		assert_eq!(
			id.get(),
			id_bytes,
			"str: {}, expected: {}, parsed: {}",
			id_str,
			fmt_bytes(&id_bytes),
			fmt_bytes(&id.get())
		);
	}

	let bad_ids = [
		("", "empty"),
		("nbYYd1", "corrupted"),
		("lasFEY", "corrupted"),
		("KsYLF8=", "corrupted"),
		("t94Blkk=", "too short"),
		("81PfIeMFLswhhk7mWS262pU2moc36g3NA5z6CKsYLF8=", "too long"),
		("V6X/GkqJ", "wrong character set"),
		("nbYYd1+k", "wrong character set"),
		("========", "invalid base64"),
		("        ", "invalid base64"),
		("++++++++", "invalid base64"),
		("↑↑↓↓←→", "invalid base64")
	];

	println!("testing bad id strings (they should fail to parse)");

	for (id_str, description) in bad_ids {
		print!("bad id str: \"{id_str}\" ({description})\t");
		let err_msg = id_str.parse::<NodeID>().unwrap_err();
		println!("parse error: {err_msg}");
	}
}


#[test]
fn encryption_key() {

	println!("testing known good key strings");

	let good_keys: [(&str, u128, u128); 5] = [
		(	"9gU5dcNPXL2oWl/JiTMW4F9w+VUZduPKeWXxEUg5MW4=",
			0xF6053975C34F5CBDA85A5FC9893316E0,
			0x5F70F9551976E3CA7965F1114839316E
		),
		(	"XZkl6H5tBXuVcYCiUm5wFfJr8LOf4c+ISd/hg9wU4gU=",
			0x5D9925E87E6D057B957180A2526E7015,
			0xF26BF0B39FE1CF8849DFE183DC14E205
		),
		(	"bpoQ1NuWaoaZXxfc+jrdnpuYc+slFH13eIPhzBRII5w=",
			0x6E9A10D4DB966A86995F17DCFA3ADD9E,
			0x9B9873EB25147D777883E1CC1448239C
		),
		(	"NUFHzgEUQg2917u+FhmgtTi6nXL1Hi1vDybnbpOPHl4=",
			0x354147CE0114420DBDD7BBBE1619A0B5,
			0x38BA9D72F51E2D6F0F26E76E938F1E5E
		),
		(	"K1Tg4+2kHEHMt3XpUyAG63Vz8/da/KqybaRF3mes6Z8=",
			0x2B54E0E3EDA41C41CCB775E9532006EB,
			0x7573F3F75AFCAAB26DA445DE67ACE99F
		)
	];


	for (key_str, key_half1, key_half2) in good_keys {
		let mut key_bytes = [0u8; 32];
		key_bytes[..16].copy_from_slice(&key_half1.to_be_bytes());
		key_bytes[16..].copy_from_slice(&key_half2.to_be_bytes());

		let key = key_str.parse::<KeyArg>().unwrap();

		assert_eq!(
			key.clone().get(),
			key_bytes,
			"\n\tstr: {},\n\texpected: {},\n\treceived: {}",
			key_str,
			fmt_bytes(&key_bytes),
			fmt_bytes(&key.get())
		);

	}

	let bad_keys = [
		("", "empty"),
		("KsYLF8=", "corrupted"),
		("pfL3udnz", "too short"),
		("1PfIeMFLswhhk7mWS262pU2moc36g3NA5z6CKsYLF8=", "corrupted"),
		("81PfIeMFLswhhk7mWS262pU2mo36g3NA5z6CKsYLF8=", "corrupted"),
		("e1IJF24IqcxGoZ5TIDME10vqHKYg13w7AL1U33E6/w==", "too short"),
		("e1IJF24IqcxGoZ5TIDME10vqHKYg13w7AL1U33E6/yoq", "too long"),
		("NUFHzgEUQg2917u-FhmgtTi6nXL1Hi1vDybnbpOPHl4=", "wrong character set"),
		("========", "invalid base64"),
		("        ", "invalid base64"),
		("________", "invalid base64"),
		("↑↑↓↓←→", "invalid base64")
	];

	println!("testing bad key strings (they should fail to parse)");

	for (key_str, description) in bad_keys {
		print!("bad key str: \"{key_str}\" ({description})\t");
		let err_msg = key_str.parse::<KeyArg>().unwrap_err();
		println!("parse error: {err_msg}");
	}
}

#[test]
fn config_file() {

	println!("generating default config, serializing and then deserializing");

	let cfg = Config::default();
	let cfg_string = cfg.to_pretty_string();
	let parsed_cfg = toml::from_str::<Config>(&cfg_string).unwrap();

	assert_eq!(parsed_cfg, cfg);

}

fn known_good_addresses() -> [(&'static str, SocketAddr); 6] {
	[
		(
			"127.0.0.1:0",
			SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0).into()
		),
		(
			"[::1]:0",
			SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0).into()
		),
		(
			"127.0.0.1:9999",
			SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999).into()
		),
		(
			"127.0.0.1:9999",
			SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999).into()
		),
		(
			"[::1]:10000",
			SocketAddrV6::new(Ipv6Addr::LOCALHOST, 10000, 0, 0).into()
		),
		(
			"[::]:1",
			SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 1, 0, 0).into()
		)
	]
}

fn known_bad_addresses() -> [(&'static str, &'static str); 8] {
	[
		("127.0.0.1", "missing port"),
		("[::1]", "missing port"),
		("::1", "missing port"),
		("::1:9999", "missing port"),
		("::", "missing port"),
		("127.0.0.1:99999", "bogus port"),
		("127.0.0.0.1:9", "bogus ip"),
		("256.0.0.1:9", "bogus ip")
	]
}

#[test]
fn address() {
	println!("testing known good addr strings");

	let test_cases = known_good_addresses()
		.map(|(addr_str, socket_addr)| (addr_str, match socket_addr {
			SocketAddr::V4(addr) => Address::V4UdpChaCha20(addr),
			SocketAddr::V6(addr) => Address::V6UdpChaCha20(addr)
		}));

	for (addr_str, if_addr) in test_cases {
		println!("str:      {addr_str:?}");
		println!("expected: {if_addr}");
		let parsed = addr_str.parse::<Address>().unwrap();
		println!("received: {parsed}");
		assert_eq!(parsed, if_addr);
	}

	println!("testing bad addr strings (they should fail to parse)");

	for (bad_str, description) in known_bad_addresses() {
		print!("bad addr str: \"{bad_str}\" ({description})\t");
		let err_msg = bad_str.parse::<Address>().unwrap_err();
		println!("parse error: {err_msg:?}");
	}
}

#[test]
fn interface_address() {
	use InterfaceAddress::V4UdpChaCha20 as V4UC;
	use InterfaceAddress::V6UdpChaCha20 as V6UC;

	let loopback_network = Ipv4Network::new(
		Ipv4Addr::new(127, 0, 0, 0),
		8
	).unwrap();
	let another_network = Ipv4Network::new(
		Ipv4Addr::new(10, 0, 0, 0),
		24
	).unwrap();
	let both_networks = || vec![loopback_network, another_network];

	println!("testing known good iface addr strings");

	//TODO: parse these as having global route??
	let without_routes = known_good_addresses()
		.map(|(addr_str, socket_addr)| (addr_str, match socket_addr {
			SocketAddr::V4(addr) => V4UC{addr, if_name: None, routes: vec![]},
			SocketAddr::V6(addr) => V6UC{addr, if_name: None, routes: vec![]}
		}));

	//TODO: add tests for parsing v6 address & routes
	let with_routes = [
		("127.0.0.1:9999 /8", V4UC {
			addr: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999),
			if_name: None,
			routes: vec![loopback_network]
		}),
		("127.0.0.1:9999 127.0.0.0/8", V4UC {
			addr: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999),
			if_name: None,
			routes: vec![loopback_network]
		}),
		("127.0.0.1:9999 127.0.0.1/8", V4UC {
			addr: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999),
			if_name: None,
			routes: vec![loopback_network]
		}),
		("127.0.0.1:9999 127.0.255.255/8", V4UC {
			addr: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999),
			if_name: None,
			routes: vec![loopback_network]
		}),
		("127.0.0.1:9999 10.0.0.0/24", V4UC {
			addr: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999),
			if_name: None,
			routes: vec![another_network]
		}),
		("10.0.0.33:9999 /24", V4UC {
			addr: SocketAddrV4::new(Ipv4Addr::new(10, 0, 0, 33), 9999),
			if_name: None,
			routes: vec![another_network]
		}),
		("127.0.0.1:9999 /8 10.0.0.0/24", V4UC {
			addr: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999),
			if_name: None,
			routes: both_networks()
		}),
		("127.0.0.1:9999 127.0.0.0/8 10.0.0.0/24", V4UC {
			addr: SocketAddrV4::new(Ipv4Addr::LOCALHOST, 9999),
			if_name: None,
			routes: both_networks()
		})
	];

	let test_cases = without_routes.into_iter().chain(with_routes);

	for (addr_str, if_addr) in test_cases {
		println!("str:      {addr_str:?}");
		println!("expected: {if_addr}");
		let parsed = addr_str.parse::<InterfaceAddress>().unwrap();
		println!("received: {parsed}");
		assert_eq!(parsed, if_addr);
	}

	let with_routes = [
		("127.0.0.1:9 /33", "bogus prefix"),
		("127.0.0.1:9 /0 /0", "double prefix"),
		("127.0.0.1:9 ::1/0", "ip version mismatch"),
		("127.0.0.1:9 /0 ::1/0", "ip version mismatch")
	];

	let test_cases = known_bad_addresses().into_iter().chain(with_routes);

	println!("testing bad iface addr strings (they should fail to parse)");

	for (bad_str, description) in test_cases {
		print!("bad iface_addr str: \"{bad_str}\" ({description})\t");
		let err_msg = bad_str.parse::<InterfaceAddress>().unwrap_err();
		println!("parse error: {err_msg:?}");
	}
}