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

use ipnetwork::{
	IpNetwork,
	Ipv4Network,
	Ipv6Network
};
use netlink_packet_route::{
	AddressFamily,
	address::AddressAttribute,
	link::LinkAttribute,
	link::State
};
use netlink_packet_route::route::{
	RouteAddress,
	RouteAttribute,
	RouteMessage,
	RouteHeader,
	RouteScope,
	RouteType
};

use crate::config::DEFAULT_PORT;
use crate::net::address::InterfaceAddress;


#[derive(Debug, Default)]
pub(crate) struct Devices {
	pub inner: Vec<Device>
}

#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Device {
	index: u32,
	pub name: String,
	addresses: Vec<(IpAddr, Vec<IpNetwork>)>,
	state: State
}


#[derive(Debug)]
struct Route {
	if_index: u32,
	preferred_source: Option<IpAddr>,
	destination: IpNetwork,
	//gateway: Option<IpAddr>,
	//scope: RtScope,
	kind: RouteType
}

trait RouteSet {
	fn push_route(&mut self, route: IpNetwork);
}
impl RouteSet for Vec<IpNetwork> {
	fn push_route(&mut self, route: IpNetwork) {
		if !self.contains(&route) {
			self.push(route)
		}
	}
}
impl RouteSet for (IpAddr, Vec<IpNetwork>) {
	fn push_route(&mut self, route: IpNetwork) {
		self.1.push_route(route)
	}
}

impl Device {
	pub fn init(index: u32, name: String) -> Self {
		let addresses = Vec::new();
		//let state = State::Down;
		let state = State::Unknown;

		Self{ index, name, addresses, state }
	}

	fn unwrap_address(&self, address: IpAddr, routes: &[IpNetwork])
		-> InterfaceAddress
	{
		match address {
			IpAddr::V4(ip_address) => {
				let routes = routes.iter().filter_map(|n| match n {
					&IpNetwork::V4(route) => Some(route),
					IpNetwork::V6(route) => {
						error!(
							device=?self,
							%ip_address,
							%route,
							"IP version mismatch in assigned routes"
						);
						None
					}
				}).collect();
				InterfaceAddress::V4UdpChaCha20 {
					if_name: Some(self.name.clone()),
					addr: SocketAddrV4::new(ip_address, DEFAULT_PORT),
					routes
				}
			},
			IpAddr::V6(ip_address) => {
				let routes = routes.iter().filter_map(|n| match n {
					IpNetwork::V4(route) => {
						error!(
							device=?self,
							%ip_address,
							%route,
							"IP version mismatch in assigned routes"
						);
						None
					},
					&IpNetwork::V6(route) => Some(route)
				}).collect();
				InterfaceAddress::V6UdpChaCha20 {
					if_name: Some(self.name.clone()),
					addr: SocketAddrV6::new(
						ip_address,
						DEFAULT_PORT,
						0,
						self.index
					),
					routes
				}
			}
		}
	}

	pub fn make_addresses(&self) -> Vec<InterfaceAddress> {
		self.addresses.iter()
			.map(|(ip, routes)| self.unwrap_address(*ip, routes))
			.collect()
	}

	pub fn absorb_link_nla(&mut self, nla: LinkAttribute) {
		match nla {
			LinkAttribute::IfName(n) => self.name = n,
			LinkAttribute::OperState(s) => self.state = s,
			//LinkAttribute::Info(_) => trace!("ignoring LinkAttribute::Info"),
			//other => debug!(?other, "ignoring LinkAttribute")
			_ => {}
		}
	}

	pub fn absorb_addr_nla(&mut self, nla: AddressAttribute) {
		match nla {
			AddressAttribute::Address(a) => self.add_address(a),
			AddressAttribute::Label(n) => self.name = n,
			//other => debug!(?other, "ignoring AddressAttribute")
			_ => {}
		}
	}

	pub fn add_address(&mut self, address: IpAddr) {
		if !self.addresses.iter().any(|(a, _routes)| a == &address) {
			self.addresses.push((address, Vec::new()));
		}
	}
	pub fn delete_address(&mut self, address: IpAddr) {
		self.addresses.retain(|(a, _routes)| a != &address);
	}

	pub fn add_route(&mut self, address: IpAddr, route: IpNetwork) {
		let entry = self.addresses
			.iter_mut()
			.find(|(a, _routes)| a == &address);
		match entry {
			Some((_addr, routes)) => if !routes.contains(&route) {
				routes.push(route);
			},
			None => {
				self.addresses.push((address, vec![route]));
			}
		}
	}

	fn delete_route(&mut self, route: Route) {
		let mut count = 0;
		for (_, rts) in self.addresses.iter_mut() {
			if let Some(i) = rts.iter().position(|&r| r == route.destination) {
				rts.swap_remove(i);
				count += 1;
			}
		}
		match count {
			0 => debug!(?route, ?self, "no such route to delete"),
			1 => trace!(?route, ?self, "route removed once"),
			count => info!(?route, ?self, count, "route removed")
		}
	}

	fn process_route(&mut self, route: Route) {
		let count = if let Some(src_addr) = route.preferred_source {
			self.add_route(src_addr, route.destination);
			1
		}
		else if let IpAddr::V6(dest_ip) = route.destination.ip() {
			// Doesn't seem to be a preferred source for IPv6 routes
			// workaround: try to match routes & v6 addresses by scope
			let mut v6_addresses = self.addresses
				.iter_mut()
				.filter_map(|(addr, routes)| match *addr {
					IpAddr::V6(addr) => Some((addr, routes)),
					_ => None
				});
			let first_two = [v6_addresses.next(), v6_addresses.next()];
			if let [Some((_addr, routes)), None] = first_two {
				// there's only one v6 address on this device, so assume the v6 route belongs to it
				routes.push_route(route.destination);
				1
			}
			else if let [Some(a), Some(b)] = first_two {
				let mut count = 0;
				// more than one v6 address: put the iterator back together
				for (addr, routes) in [a, b].into_iter().chain(v6_addresses) {

					let addr_route_match = (
							// if the route includes the address, and isn't the default route, assume it can be used from this address
							!dest_ip.is_unspecified()
							&& route.destination.contains(IpAddr::V6(addr))
						)
						|| (
							// assume global routes work with global addresses
							addr.is_global()
							&& (dest_ip.is_global() || dest_ip.is_unspecified())
						)
						|| (
							// assume link-local routes & addresses belong together
							dest_ip.is_unicast_link_local()
							&& addr.is_unicast_link_local()
						)
						|| (
							// assume multicast routes work from all addresses
							matches!(route.kind, RouteType::Multicast)
						);

					//TODO:
					// Consider special treatment for routes with a gateway (NAT?)
					// Detecting & using NAT64 could be useful
					// NAT66 seems too uncommon

					// RtScope seems to be Universe for all V6 routes, so probably not useful
					// RouteType::Local and RouteType::Unicast maybe useful however

					if addr_route_match {
						routes.push_route(route.destination);
						count += 1;
						continue;
					}
				}
				count
			}
			else {0}

		}
		else if let IpAddr::V4(_dest_ip) = route.destination.ip() {
			//TODO: Consider matching for v4 addresses as well
			self.addresses
				.iter_mut()
				.filter_map(|(addr, routes)| match *addr {
					IpAddr::V4(addr) => Some((addr, routes)),
					_ => None
				})
				.map(|(_, r)| r.push_route(route.destination))
				.count()
		}
		else {unreachable!()};
		match count {
			0 if self.addresses.is_empty() => trace!(
				?route,
				?self,
				"no addresses to assign route to"
			),
			0 => warn!(?route, ?self, "unable to assign route to an address"),
			1 => trace!(?route, ?self, "route assigned once"),
			count => info!(?route, ?self, count, "route assigned")
		}
	}
}

impl Devices {
	pub fn new() -> Self { Self {inner: Vec::new()} }

	pub fn get_or_insert(&mut self, idx: u32) -> &mut Device {
		let pos = self.inner
			.iter()
			.position(|d| d.index == idx);
		let pos = match pos {
			Some(p) => p,
			None => {
				let last = self.inner.len();
				self.inner.push(Device::init(idx, String::new()));
				last
			}
		};
		self.inner.get_mut(pos).unwrap()
	}
	pub fn delete(&mut self, idx: u32) {
		match self.inner.iter().position(|d| d.index == idx) {
			Some(i) => {
				let dev = self.inner.swap_remove(i);
				trace!(?dev, "removed device");
			},
			None => warn!(idx, "tried to delete unknown device")
		}
	}

	pub fn add_route(&mut self, msg: RouteMessage) {
		if let Some(route) = Self::extract_route(msg) {
			let dev = self.get_or_insert(route.if_index);
			dev.process_route(route);
		}
	}
	pub fn delete_route(&mut self, msg: RouteMessage) {
		if let Some(route) = Self::extract_route(msg) {
			let dev = self.get_or_insert(route.if_index);
			dev.delete_route(route);
		}
	}

	fn extract_route(msg: RouteMessage) -> Option<Route> {
		let mut if_idx = None;
		let mut src = None;
		let mut dest = None;
		//let mut gateway = None;

		for attr in msg.attributes {
			match attr {
				//RouteAttribute::Gateway(g) => gateway = Some(g),
				RouteAttribute::PrefSource(s) => src = Some(s),
				RouteAttribute::Destination(s) => dest = Some(s),
				RouteAttribute::Oif(i) => if_idx = Some(i),
				//attr => debug!(?attr, "ignoring route attribute")
				_ => {}
			}
		}
		let source = match src {
			Some(RouteAddress::Inet(src)) => Some(IpAddr::V4(src)),
			Some(RouteAddress::Inet6(src)) => Some(IpAddr::V6(src)),
			Some(invalid) => {
				warn!(?invalid, "received route with non-IP source");
				return None;
			},
			None => None
		};
		let network = match dest {
			Some(RouteAddress::Inet(dest)) => {
				let prefix = msg.header.destination_prefix_length;
				IpNetwork::new(dest.into(), prefix).ok()?
			}
			Some(RouteAddress::Inet6(dest)) => {
				let prefix = msg.header.destination_prefix_length;
				IpNetwork::new(dest.into(), prefix).ok()?
			}
			Some(invalid) => {
				warn!(?invalid, "received route with non-IP destination");
				return None;
			},
			None => Self::default_route(&msg.header)
		};

		Some(Route {
			if_index: if_idx?,
			preferred_source: source,
			destination: network,
			// gateway,
			// scope: msg.header.scope.into(),
			kind: msg.header.kind
		})
	}

	fn default_route(header: &RouteHeader) -> IpNetwork {
		//TODO: is there any reason to check for this?
		let is_default = header.scope == RouteScope::Universe
			&& header.destination_prefix_length == 0;
		if !is_default {
			error!(?header, "invalid non-default RouteMessage without dest");
		}
		match header.address_family {
			AddressFamily::Inet => Ipv4Network::new(Ipv4Addr::UNSPECIFIED, 0)
				.map(IpNetwork::V4)
				.unwrap(),
			AddressFamily::Inet6 => Ipv6Network::new(Ipv6Addr::UNSPECIFIED, 0)
				.map(IpNetwork::V6)
				.unwrap(),
			_ => unreachable!("invalid non-IP address family received")
		}
	}
}