// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
pub mod command;

use std::{sync::Arc, io::{self, Write, Error}, path::Path, fmt::Write as _, ops::ControlFlow, time::Instant};

use tokio::{runtime::Handle, sync::Notify};
use quinn::ServerConfig;
use linefeed::{Interface, ReadResult, DefaultTerminal};
use strum::IntoEnumIterator;

use command::{Command, CommandVariant, PrivacyCommand};

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

use crate::server::{multiplayer::config::Config, common::config};
use crate::utils::command::{Command as _, CommandVariant as _};

pub struct Repl {
	server: Arc<Server>,
	handle: Handle,
	default_privacy: PrivacyConfig,
	sanity_checks: bool,
	exit_on_eof: bool,
	router_notify: Arc<Notify>,
}

enum ReplExit {
	Eof,
	ExitCommand,
}

impl Repl {
	pub fn new(server: Arc<Server>, default_privacy: PrivacyConfig, sanity_checks: bool, exit_on_eof: bool, router_notify: Arc<Notify>) -> Repl {
		Repl { server, handle: Handle::current(), default_privacy, sanity_checks, exit_on_eof, router_notify }
	}

	pub fn run(mut self) {
		let start = Instant::now();
		let (res, using_raw) = match Self::get_repl_interface() {
			Ok(interface) => {
				match self.run_linefeed(interface) {
					Ok(res) => (Ok(res), false),
					Err(err) => {
						log::warn!("failed reading line (can happen if stdin isn't a terminal), falling back to raw console: {err}");
						(self.run_raw(), true)
					},
				}
			},
			Err(err) => {
				log::warn!("failed starting linefeed REPL (can happen if stdout isn't a terminal), falling back to raw console: {err}");
				(self.run_raw(), true)
			},
		};

		match res {
			Ok(ReplExit::Eof) if self.exit_on_eof => {
				if start.elapsed().as_secs() < 1 && using_raw { // Instructions if this appears to be accidental
					log::warn!("server shutting down immediately due to EOF received on server console");
					log::warn!("this is likely unintentional and possibly a result of having the stdin be /dev/null");
					log::warn!("to disable this behaviour, run the server with `--exit-on-eof disabled`");
				}
				self.server.close();
			},
			Ok(ReplExit::Eof) => (),
			Ok(ReplExit::ExitCommand) => self.server.close(),
			Err(err) => {
				log::warn!("failed reading line, closing console but keeping server alive: {err}");
				// Closing the server on error is unnecessary and might cause disruption
			},
		}
	}

	fn get_repl_interface() -> Result<Interface<DefaultTerminal>, Error> {
		let interface = Interface::new("spaceships")?;
		interface.set_prompt("> ")?;
		Ok(interface)
	}

	/**
	 * The preferred REPL interface using the `linefeed` crate that provides
	 * similar functionality to GNU readline, such as up arrow history and
	 * keyboard shortcuts.
	 */
	fn run_linefeed(&mut self, interface: Interface<DefaultTerminal>) -> Result<ReplExit, Error> {
		Ok(loop {
			match interface.read_line()? {
				ReadResult::Input(line) => {
					let res = self.command(&line);
					interface.add_history(line);
					if res.is_break() {
						break ReplExit::ExitCommand;
					}
				},
				ReadResult::Signal(_) => (),
				ReadResult::Eof => break ReplExit::Eof,
			}
		})
	}

	/**
	 * Fallback in case `linefeed` doesn't work, which happens if stdin isn't a
	 * terminal (see issue 19).
	 */
	fn run_raw(&mut self) -> Result<ReplExit, Error> {
		for line in io::stdin().lines() {
			if self.command(&line?).is_break() {
				return Ok(ReplExit::ExitCommand);
			}
		}
		Ok(ReplExit::Eof)
	}

	fn command(&mut self, line: &str) -> ControlFlow<()> {
		match Command::build::<CommandVariant>(line) {
			Ok(Some(Command::Help(None))) => {
				println!("Showing help of all commands:");
				for cmd in CommandVariant::iter() {
					print!("{}", cmd.help());
				}
			},
			Ok(Some(Command::Help(Some(cmd)))) => print!("{}", cmd.help()),
			Ok(Some(Command::Exit)) => return ControlFlow::Break(()),
			Ok(Some(Command::Clear)) => {
				/*
				 * Would like to use the `clear_screen` method that this
				 * crate has, but I can't seem to access the type that
				 * allows it.
				 */
				print!("\x1b[H\x1b[2J\x1b[3J"); // Output of `clear | xxd`, might not work on all platforms
				let _ = io::stdout().flush();
			},
			Ok(Some(Command::Add(entry, privacy))) => {
				if let Err(err) = self.add_game_server(entry, privacy) {
					println!("error: add: {err}");
				}
			},
			Ok(Some(Command::Remove(game_id))) => {
				let res = self.server.router.write().unwrap().remove_game_server(game_id.as_ref()); // Ensures that the lock is dropped as soon as possible
				if let Err(err) = res {
					println!("error: remove: cannot remove game server: {err}");
				}
			},
			Ok(Some(Command::Reload(game_id))) => {
				if let Err(err) = self.reload_game_server(&game_id) {
					println!("error: reload: {err}");
				}
			},
			Ok(Some(Command::Rename { id, new_id })) => {
				let res = self.server.router.write().unwrap().change_route(id, new_id);
				if let Err(err) = res {
					println!("error: rename: {err}");
				}
			},
			Ok(Some(Command::Privacy(PrivacyCommand::PrintDefault))) => println!("Default privacy = {}", self.default_privacy),
			Ok(Some(Command::Privacy(PrivacyCommand::SetDefault(privacy)))) => {
				self.default_privacy = privacy;
				println!("Updated default privacy config");
			},
			Ok(Some(Command::Privacy(PrivacyCommand::SetGameServer(game_id, privacy)))) => {
				let res = self.server.router.write().unwrap().change_privacy(&game_id, privacy);
				if let Err(err) = res {
					println!("error: privacy: {err}");
				} else {
					println!("Updated privacy config");
				}
			},
			Ok(Some(Command::Status(None))) => {
				/*
				 * Want to reduce lock contention with the router as much
				 * as possible, so not performing I/O or waiting to
				 * retrieve the publish status.
				 */
				let mut status = self.server.get_publish_status();
				let _ = writeln!(status);
				self.server.router.read().unwrap().get_status(&mut status);
				print!("{status}");
			},
			Ok(Some(Command::Status(Some(id)))) => {
				let status = self.server.router.read().unwrap().get_game_server_status(&id);
				print!("{status}");
			},
			Ok(Some(Command::Cert(info))) => {
				if let Err(err) = self.update_cert_info(info) {
					println!("error: cert: {err}");
				} else {
					println!("Successfully updated certificate information!");
				}
			},
			Ok(Some(Command::Info)) => self.server.info.print(),
			Ok(Some(Command::Message(msg, game_ids))) => {
				let res = self.server.router.read().unwrap().send_admin_message(msg.as_ref(), game_ids);
				if let Err(err) = res {
					println!("error: message: {err}");
				}
			},
			Ok(None) => (),
			Err(err) => println!("error: {err}"), // Not using stderr as that's for logs, and if redirecting logs to a file, those will be ignored
		}
		ControlFlow::Continue(())
	}

	fn create_game_server(&self, path: Arc<Path>, privacy: Option<PrivacyConfig>) -> Result<GameServer, String> {
		let config = config::load_sync::<Config>(&path).map_err(|err| format!("failed loading config: {err}"))?;
		GameServer::try_new(config, path, privacy.unwrap_or(self.default_privacy), self.sanity_checks, Arc::clone(&self.router_notify)).map_err(|err| format!("failed creating game server: {err}"))
	}

	fn add_game_server(&self, entry: RouterEntry, privacy: Option<PrivacyConfig>) -> Result<(), String> {
		let server = self.create_game_server(entry.path, privacy)?;
		self.server.router.write().unwrap().add_game_server(entry.game_id, server).map_err(|err| format!("failed adding game server: {err}"))
	}

	fn reload_game_server(&self, game_id: &str) -> Result<(), String> {
		let (path, privacy) = self.server.router.read().unwrap().get_server_info(game_id.as_ref()).ok_or_else(|| format!("game id \"{game_id}\" does not exist"))?;
		let server = self.create_game_server(path, Some(privacy))?;
		self.server.router.write().unwrap().replace_game_server(game_id, server)
	}

	fn update_cert_info(&self, info: CertInfo) -> Result<(), String> {
		let (certs, key) = self.handle.block_on(Server::load_cert_info(info)).map_err(|err| format!("failed loading certificate info: {err}"))?;
		let config = ServerConfig::with_single_cert(certs, key).map_err(|err| format!("cannot create server config: {err}"))?;
		self.server.endpoint.set_server_config(Some(config));
		Ok(())
	}
}
