// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::fmt::{Display, Formatter, Result as FmtResult};

use crate::net::utils::IpCategory;
use crate::utils::ToStr;

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PrivacyLevel {
	/// Nobody is allowed.
	None,

	/// Only connections from the same host are allowed.
	Loopback,

	/// Only connections from the local network are allowed.
	Local,

	/// Everyone is allowed.
	Global,
}

impl ToStr for PrivacyLevel {
	fn to_str(self) -> &'static str {
		match self {
			PrivacyLevel::None => "none",
			PrivacyLevel::Loopback => "loopback",
			PrivacyLevel::Local => "local",
			PrivacyLevel::Global => "global",
		}
	}
}

impl PrivacyLevel {
	fn try_from(s: &str) -> Result<PrivacyLevel, String> {
		match s {
			"none" => Ok(PrivacyLevel::None),
			"loopback" => Ok(PrivacyLevel::Loopback),
			"local" => Ok(PrivacyLevel::Local),
			"global" => Ok(PrivacyLevel::Global),
			_ => Err(format!("expected \"none\", \"loopback\", \"local\" or \"global\", got \"{s}\"")),
		}
	}

	fn allowed(self, category: IpCategory) -> bool {
		match self {
			PrivacyLevel::None => false,
			PrivacyLevel::Loopback => {
				match category {
					IpCategory::Loopback => true,
					IpCategory::Local | IpCategory::Global => false,
				}
			},
			PrivacyLevel::Local => {
				match category {
					IpCategory::Loopback | IpCategory::Local => true,
					IpCategory::Global => false,
				}
			},
			PrivacyLevel::Global => true,
		}
	}
}

// INVARIANT: `discover` ≤ `join`
#[derive(Clone, Copy)]
pub struct PrivacyConfig {
	discover: PrivacyLevel,
	join: PrivacyLevel,
}

impl PrivacyConfig {
	pub fn try_from_str(s: &str) -> Result<PrivacyConfig, String> {
		if let Some((discover, join)) = s.split_once('/') {
			let (discover, join) = (PrivacyConfig::get_level(discover, "discoverability")?, PrivacyConfig::get_level(join, "joinability")?);
			PrivacyConfig::try_from_levels(discover, join)
		} else {
			let level = PrivacyLevel::try_from(s)?;
			Ok(PrivacyConfig::from_level(level))
		}
	}

	pub fn from_level(level: PrivacyLevel) -> PrivacyConfig {
		PrivacyConfig { discover: level, join: level } // Invariant satisfied as they equal
	}

	fn try_from_levels(discover: PrivacyLevel, join: PrivacyLevel) -> Result<PrivacyConfig, String> {
		if discover <= join { Ok(PrivacyConfig { discover, join }) } // Invariant checked before creation
		else { Err(String::from("cannot have discoverability stricter than joinability")) }
	}

	fn get_level(s: &str, val: &str) -> Result<PrivacyLevel, String> {
		PrivacyLevel::try_from(s).map_err(|err| format!("{val}: {err}"))
	}

	pub fn can_discover(self, category: IpCategory) -> bool {
		self.discover.allowed(category)
	}

	pub fn can_join(self, category: IpCategory) -> bool {
		self.join.allowed(category)
	}
}

impl Display for PrivacyConfig {
	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
		if self.discover == self.join {
			write!(fmt, "{}", self.discover.to_str())
		} else {
			write!(fmt, "{}/{}", self.discover.to_str(), self.join.to_str())
		}
	}
}
