// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::{collections::{BTreeMap, btree_map::Entry}, num::NonZeroUsize, sync::{Arc, atomic::{AtomicUsize, Ordering}}, fmt::Write, path::Path};

use tokio::sync::{mpsc::{self, Sender, error::TrySendError}, Notify};
use nonempty::NonEmpty;

use super::{client_count_notify::ClientCountNotify, privacy::PrivacyConfig};

use crate::net::{serp::request::PlayRequest, utils::IpCategory};
use crate::protocol::{discovery::{DiscoveryServer, GameId}, message::text_io::{TextIoString, TextIoStr, MAX_LEN}};

use crate::server::multiplayer::{Server, ServerMessage, config::{Config, DiscoveryConfig}, connection_listener::{ConnectionListener, DynIncomingConnection}};
use crate::utils::INDENTATION;
#[cfg(feature = "client")] use crate::utils::max_len::MaxLenStr;

pub type StreamSender = Sender<(Box<DynIncomingConnection>, PlayRequest)>;

pub struct GameServer {
	stream_sender: StreamSender,
	message_sender: Sender<ServerMessage>,
	path: Arc<Path>,
	discovery: DiscoveryConfig,
	privacy: PrivacyConfig,
	player_count: Arc<AtomicUsize>,
	spectator_count: Arc<AtomicUsize>,
	max_clients: Option<NonZeroUsize>,
}

impl GameServer {
	pub fn try_new(config: Config, path: Arc<Path>, privacy: PrivacyConfig, sanity_checks: bool, router_notify: Arc<Notify>) -> Result<GameServer, String> {
		let max_clients = config.max_clients;
		let discovery = config.discovery.clone();

		let (stream_sender, stream_receiver) = mpsc::channel(256);

		let connection_listener = ConnectionListener::new(stream_receiver);
		let client_count_notify = ClientCountNotify::new(router_notify);
		let server = Server::try_new(connection_listener, config, path.to_path_buf(), sanity_checks)?;

		let (message_sender, message_receiver) = mpsc::channel(256);

		let player_count = client_count_notify.player_count();
		let spectator_count = client_count_notify.spectator_count();
		tokio::spawn(server.run(message_receiver, client_count_notify));

		Ok(GameServer {
			stream_sender,
			message_sender,
			path,
			discovery,
			privacy,
			player_count,
			spectator_count,
			max_clients,
		})
	}

	#[cfg(feature = "client")]
	pub fn get_stream_sender(&self) -> StreamSender {
		self.stream_sender.clone()
	}

	fn send_admin_message(&self, msg: TextIoString) -> Result<(), String> {
		match self.message_sender.try_send(ServerMessage::AdminMessage(msg)) {
			/*
			 * If the server closed (I don't think it's possible for this to happen
			 * while in the router, don't bother reporting the error to the user as
			 * it's not important. This is because an error is worth reporting if
			 * users who should see a message don't get the message, which isn't
			 * the case here as the server's closed.
			 */
			Ok(()) | Err(TrySendError::Closed(_)) => Ok(()),
			Err(TrySendError::Full(_)) => Err(String::from("failed sending admin message to at least one server: sender is full")),
		}
	}
}

pub struct RouterEntry {
	pub path: Arc<Path>,
	pub game_id: GameId,
}

impl RouterEntry {
	/**
	 * Parses a string consisting of "id:path". If no colon is found, the id of
	 * "default" is used.
	 */
	pub fn new(game_server: &str) -> Result<RouterEntry, String> {
		let (game_id, path) = game_server.split_once(':').unwrap_or(("default", game_server));
		let game_id = GameId::try_from(Arc::from(game_id)).map_err(|err| format!("invalid game id: {err}"))?;
		Ok(RouterEntry { path: Arc::from(Path::new(path)), game_id })
	}
}

pub enum RouteError {
	NotFound,
	AccessDenied,
}

pub struct Router {
	servers: BTreeMap<GameId, GameServer>,
	port: u16,
	notify: Arc<Notify>,
}

impl Router {
	pub fn new(port: u16) -> Router {
		Router { servers: BTreeMap::new(), port, notify: Arc::new(Notify::new()) }
	}

	#[cfg(feature = "client")]
	pub fn with_single_server(server: GameServer, port: u16, notify: Arc<Notify>) -> Router {
		let mut servers = BTreeMap::new();
		let game_id = const { MaxLenStr::new("default").unwrap() }.shared();
		servers.insert(game_id, server);
		Router { servers, port, notify }
	}

	pub fn get_notify(&self) -> Arc<Notify> {
		Arc::clone(&self.notify)
	}

	fn notify(&self) {
		self.notify.notify_waiters();
	}

	pub fn clear(&mut self) {
		self.servers.clear();
		self.notify();
	}

	pub(super) fn discovery_servers(&self, ip_category: IpCategory) -> impl Iterator<Item = DiscoveryServer> + use<'_> {
		self.servers
			.iter()
			.filter(move |(_game_id, server)| server.privacy.can_discover(ip_category))
			.map(|(game_id, server)| DiscoveryServer {
				protocol_type: PlayRequest::PROTOCOL_TYPE,
				protocol_version: PlayRequest::PROTOCOL_VERSION,
				port: self.port,
				player_count: server.player_count.load(Ordering::Acquire),
				spectator_count: server.spectator_count.load(Ordering::Acquire),
				max_clients: server.max_clients,
				game_id: game_id.clone(),
				name: server.discovery.name.clone(),
				desc: server.discovery.desc.clone(),
			})
	}

	/**
	 * Adds a new game server to the router. Keeps it unmodified and in a
	 * consistent state on error.
	 */
	pub fn add_game_server(&mut self, game_id: GameId, server: GameServer) -> Result<(), String> {
		match self.servers.entry(game_id) {
			Entry::Occupied(e) => Err(format!("game server with id \"{}\" already exists", e.key())),
			Entry::Vacant(e) => {
				e.insert(server);
				self.notify();
				Ok(())
			},
		}
	}

	pub(super) fn remove_game_server(&mut self, game_id: &str) -> Result<(), String> {
		if self.servers.remove(game_id).is_some() {
			self.notify();
			Ok(())
		} else {
			Err(format!("game server with id \"{game_id}\" not found"))
		}
	}

	pub(super) fn get_server_info(&self, game_id: &str) -> Option<(Arc<Path>, PrivacyConfig)> {
		let server = self.servers.get(game_id)?;
		Some((Arc::clone(&server.path), server.privacy))
	}

	pub(super) fn replace_game_server(&mut self, game_id: &str, server: GameServer) -> Result<(), String> {
		/*
		 * Checking for errors and not unwrapping in this method as the read lock
		 * is released after first getting the router entry, so it's possible that
		 * somewhere else in the code modifies the server config. I don't want to
		 * hold that lock while loading a server, as that could take a while
		 * (I/O).
		 *
		 * Right now in the current configuration I don't think this can change as
		 * the router is only modified through the REPL, but that could change.
		 */
		let (game_id, old_server) = self.servers.remove_entry(game_id).ok_or_else(|| String::from("cannot find old server (possible race condition?)"))?;

		if old_server.path != server.path {
			self.notify();
			return Err(String::from("paths of old and new server don't match (possible race condition?)"));
		}

		self.servers.insert(game_id, server);
		self.notify();
		Ok(())
	}

	pub(super) fn route(&self, game_id: &str, ip_category: IpCategory) -> Result<StreamSender, RouteError> {
		if let Some(server) = self.servers.get(game_id).or_else(|| self.servers.get("default")) {
			if server.privacy.can_join(ip_category) { Ok(server.stream_sender.clone()) }
			else { Err(RouteError::AccessDenied) }
		} else {
			Err(RouteError::NotFound)
		}
	}

	pub(super) fn change_route(&mut self, id: GameId, new_id: GameId) -> Result<(), String> {
		if id == new_id {
			return Err(String::from("equal game ids given"));
		}

		let Some(server) = self.servers.remove(&id) else {
			return Err(format!("game id \"{id}\" does not exist"));
		};

		match self.servers.entry(new_id.clone()) {
			Entry::Occupied(_) => {
				let not_found = self.servers.insert(id, server).is_none();
				debug_assert!(not_found);
				Err(format!("new game id \"{new_id}\" already exists"))
			},
			Entry::Vacant(e) => {
				e.insert(server);
				self.notify();
				Ok(())
			},
		}
	}

	pub(super) fn change_privacy(&mut self, id: &str, privacy: PrivacyConfig) -> Result<(), String> {
		if let Some(server) = self.servers.get_mut(id) {
			server.privacy = privacy;
			self.notify();
			Ok(())
		} else {
			Err(format!("game id \"{id}\" does not exist"))
		}
	}

	pub(super) fn send_admin_message(&self, msg: &str, game_ids: Option<NonEmpty<Box<str>>>) -> Result<(), String> {
		let Some(msg) = TextIoStr::new(msg) else {
			return Err(format!("admin message too long, expected at most {MAX_LEN} bytes, got {}", msg.len()));
		};

		let mut ret = Ok(()); // If there's an error, don't stop sending to all other servers

		if let Some(ids) = game_ids { // Sends the message to particular servers
			let mut servers = BTreeMap::new();
			for id in ids {
				match servers.entry(id) {
					Entry::Occupied(e) => return Err(format!("duplicate game id \"{}\"", e.key())),
					Entry::Vacant(e) => {
						if let Some(server) = self.servers.get(e.key().as_ref()) {
							e.insert(server);
						} else {
							return Err(format!("game id \"{}\" does not exist", e.key()));
						}
					},
				}
			}

			for server in servers.into_values() {
				if let Err(err) = server.send_admin_message(msg.owned()) {
					ret = Err(err);
				}
			}
		} else { // Sends the message to all servers
			for server in self.servers.values() {
				if let Err(err) = server.send_admin_message(msg.owned()) {
					ret = Err(err);
				}
			}
		}

		ret
	}

	pub(super) fn get_status(&self, status: &mut String) {
		if !self.servers.is_empty() {
			let _ = writeln!(status, "Showing status of all game servers:");
			for (i, (game_id, server)) in self.servers.iter().enumerate() {
				if i > 0 {
					let _ = writeln!(status);
				}
				Router::write_game_id(status, game_id);
				Router::write_server_status(status, server);
			}
		} else {
			let _ = writeln!(status, "No game servers are running");
		}
	}

	pub(super) fn get_game_server_status(&self, game_id: &str) -> String {
		let mut status = String::new();
		if let Some(server) = self.servers.get(game_id) {
			let _ = writeln!(status, "Showing status of game server \"{game_id}\":");
			Router::write_server_status(&mut status, server);
		} else {
			let _ = writeln!(status, "No game server with id \"{game_id}\" exists");
		}
		status
	}

	fn write_game_id(status: &mut String, game_id: &str) {
		let _ = writeln!(status, "\"{game_id}\":");
	}

	fn write_server_status(status: &mut String, server: &GameServer) {
		let _ = writeln!(status, "{INDENTATION}path = {}", server.path.display());
		let players = DiscoveryServer::format_players_with(server.player_count.load(Ordering::Acquire), server.spectator_count.load(Ordering::Acquire), server.max_clients);
		let _ = writeln!(status, "{INDENTATION}{players}\n{INDENTATION}privacy = {}", server.privacy);
	}
}
