// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::sync::Arc;

use strum_macros::EnumIter;
use const_format::formatcp;
use nonempty::NonEmpty;

use super::super::{CertInfo, router::RouterEntry, privacy::PrivacyConfig};

use crate::protocol::discovery::GameId;
use crate::utils::command::{Args, Command as GenericCommand, CommandVariant as GenericCommandVariant, HELP_FOR_HELP};

pub enum Command {
	Help(Option<CommandVariant>),
	Exit,
	Clear,
	Add(RouterEntry, Option<PrivacyConfig>),
	Remove(Box<str>),
	Reload(Box<str>),
	Rename {
		id: GameId,
		new_id: GameId,
	},
	Privacy(PrivacyCommand),
	Status(Option<Box<str>>),
	Cert(CertInfo),
	Info,
	Message(Box<str>, Option<NonEmpty<Box<str>>>),
}

pub enum PrivacyCommand {
	PrintDefault,
	SetDefault(PrivacyConfig),
	SetGameServer(Box<str>, PrivacyConfig),
}

impl GenericCommand for Command {}

#[derive(Clone, Copy, EnumIter)]
pub enum CommandVariant { Help, Exit, Clear, Add, Remove, Reload, Rename, Privacy, Status, Cert, Info, Message }

impl GenericCommandVariant for CommandVariant {
	type Command = Command;

	fn try_from(cmd: &str) -> Option<CommandVariant> {
		match cmd {
			"help" => Some(CommandVariant::Help),
			"exit" => Some(CommandVariant::Exit),
			"clear" => Some(CommandVariant::Clear),
			"add" => Some(CommandVariant::Add),
			"remove" => Some(CommandVariant::Remove),
			"reload" => Some(CommandVariant::Reload),
			"rename" => Some(CommandVariant::Rename),
			"privacy" => Some(CommandVariant::Privacy),
			"status" => Some(CommandVariant::Status),
			"cert" => Some(CommandVariant::Cert),
			"info" => Some(CommandVariant::Info),
			"message" => Some(CommandVariant::Message),
			_ => None,
		}
	}

	fn help(self) -> &'static str {
		use crate::utils::INDENTATION as I;

		match self {
			CommandVariant::Help => formatcp!("{HELP_FOR_HELP}\n"),
			CommandVariant::Exit => "exit - exits the server\n",
			CommandVariant::Clear => "clear - clears the screen (you can also do this with Ctrl+L)\n",
			CommandVariant::Add => formatcp!("add - adds a new game server\n{I}add <ID>:<PATH>: Loads a game server from PATH with optional ID. If ID isn't given, \"default\" is used, which routes everything else to it.\n{I}add <ID>:<PATH> <PRIVACY> Adds a game server with given privacy config.\n"),
			CommandVariant::Remove => formatcp!("remove - removes an existing game server\n{I}remove <ID>: Removes the game server with id of ID.\n"),
			CommandVariant::Reload => formatcp!("reload - reloads the configuration of an existing game server\n{I}reload <ID>: Creates a new game server using the configuration file from the server with ID. Then replaces that server with the newly loaded server on success, preserving all routing information.\n"),
			CommandVariant::Rename => formatcp!("rename - change a game server's id\n{I}rename <ID> <NEW_ID>: Renames the game server ID to NEW_ID. ID must be an existing game server and NEW_ID must not already be used.\n"),
			CommandVariant::Privacy => formatcp!("privacy - change server privacy config\n{I}privacy: Prints default privacy config for newly created servers.\n{I}privacy <PRIVACY>: Sets default privacy config for newly created servers.\n{I}privacy <ID> <PRIVACY>: Changes the privacy config of an existing game server.\n"),
			CommandVariant::Status => formatcp!("status - prints information about running game servers\n{I}status: Prints the status of all game servers, published servers and publications accepted.\n{I}status <ID>: Prints information about a server with the id of ID.\n"),
			CommandVariant::Cert => formatcp!("cert - updates certificate information\n{I}cert <CERT_PATH> <KEY_PATH>: Updates the TLS certificate used by the server, taking paths to files in the PEM format.\n"),
			CommandVariant::Info => "info - prints the publicly available build information about the server\n",
			CommandVariant::Message => formatcp!("message - sends a message to users in the game\n{I}message <MESSAGE>: Sends a message to all game servers.\n{I}message <IDS> <MESSAGE>: Sends a message to the servers with game ids as provided in the previous arguments.\n"),
		}
	}

	fn build(self, args: Args) -> Result<Command, String> {
		Ok(match self {
			CommandVariant::Help => Command::Help(args.help_command::<CommandVariant>()?),
			CommandVariant::Exit => args.require_none(Command::Exit)?,
			CommandVariant::Clear => args.require_none(Command::Clear)?,
			CommandVariant::Add => {
				args.check_at_most_n_args(2)?;
				let game = &args.require_at_least_n_args::<1>()?[0];
				let privacy = args.get(1).map(|arg| PrivacyConfig::try_from_str(arg)).transpose().map_err(|err| format!("invalid privacy config: {err}"))?;
				Command::Add(RouterEntry::new(game)?, privacy)
			},
			CommandVariant::Remove => Command::Remove(args.require_n_args::<1>()?[0].clone().into_boxed_str()),
			CommandVariant::Reload => Command::Reload(args.require_n_args::<1>()?[0].clone().into_boxed_str()),
			CommandVariant::Rename => {
				let [id, new_id] = args.require_n_args::<2>()?;
				Command::Rename { id: GameId::try_from(Arc::from(id.as_str()))?, new_id: GameId::try_from(Arc::from(new_id.as_str()))? }
			},
			CommandVariant::Privacy => {
				fn get_privacy(privacy: &str) -> Result<PrivacyConfig, String> {
					PrivacyConfig::try_from_str(privacy).map_err(|err| format!("invalid privacy config: {err}"))
				}

				args.check_at_most_n_args(2)?;

				Command::Privacy(match (args.first(), args.get(1)) {
					(None, _) => PrivacyCommand::PrintDefault,
					(Some(privacy), None) => PrivacyCommand::SetDefault(get_privacy(privacy)?),
					(Some(id), Some(privacy)) => PrivacyCommand::SetGameServer(id.clone().into_boxed_str(), get_privacy(privacy)?),
				})
			},
			CommandVariant::Status => {
				args.check_at_most_n_args(1)?;
				Command::Status(args.first().map(|id| id.clone().into_boxed_str()))
			},
			CommandVariant::Cert => {
				let [cert_path, key_path] = args.require_n_args::<2>()?;
				Command::Cert(CertInfo { cert_path: cert_path.clone().into_boxed_str(), key_path: key_path.clone().into_boxed_str() })
			},
			CommandVariant::Info => args.require_none(Command::Info)?,
			CommandVariant::Message => {
				let Some(message) = args.last() else {
					return Err(String::from("expected at least one argument, got none"));
				};

				let ids = args.iter().take(args.len() - 1).cloned().map(String::into_boxed_str).collect();
				Command::Message(message.clone().into_boxed_str(), NonEmpty::from_vec(ids))
			},
		})
	}
}

#[cfg(test)]
mod tests {
	use strum::IntoEnumIterator;

	use super::*;

	#[test]
	fn all_help_text_ends_in_newline() {
		for cmd in CommandVariant::iter() {
			assert!(cmd.help().ends_with('\n'));
		}
	}
}
