// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::{str::FromStr, path::Path, net::{IpAddr, SocketAddr}, sync::Arc};

use tokio::{task, sync::Notify};

use super::{Server, ServerArgs, ServerCliArgs, Repl, router::{Router, RouterEntry, GameServer}, privacy::PrivacyConfig};

use crate::net::{lan_discovery::server::LanDiscoveryServer, utils::udp};
use crate::server::common::config;
use crate::utils::ctrl_c::CtrlCHandler;

#[tokio::main]
pub async fn run(args: ServerCliArgs) -> Result<(), String> {
	let default_privacy = PrivacyConfig::try_from_str(&args.privacy).map_err(|err| format!("invalid default privacy config: {err}"))?;

	let addr = SocketAddr::new(IpAddr::from_str(&args.addr).map_err(|err| format!("invalid address: {err}"))?, args.port);

	let mut router = Router::new(addr.port());
	let router_notify = router.get_notify();
	for game in &args.game_servers {
		let entry = RouterEntry::new(game).map_err(|err| format!("invalid router entry: {err}"))?;
		let config = config::load_async(Path::new(entry.path.as_ref())).await.map_err(|err| format!("failed loading config: {err}"))?;
		let server = GameServer::try_new(config, entry.path, default_privacy, args.sanity_checks, Arc::clone(&router_notify)).map_err(|err| format!("failed creating game server: {err}"))?;
		router.add_game_server(entry.game_id, server).map_err(|err| format!("failed adding game server: {err}"))?;
	}

	tokio::spawn(async { CtrlCHandler::new().listen().await; });

	let server_args = ServerArgs::try_from_command_line(&args).map_err(|err| format!("cannot create server args: {err}"))?;
	let socket = udp::new(addr).map_err(|err| format!("failed binding socket on {addr}: {err}"))?;
	let server = Server::try_new(socket, router, server_args).await.map_err(|err| format!("failed creating server: {err}"))?;

	log::info!("Starting the server!");
	let task = {
		let server = Arc::clone(&server);
		tokio::spawn(server.run())
	};

	if args.lan_discovery.into() {
		tokio::spawn(async move {
			match LanDiscoveryServer::try_new(addr) {
				Ok(lan_discovery) => {
					log::info!("Created LAN discovery server");
					lan_discovery.loop_forever(Arc::new(Notify::new())).await;
				},
				Err(err) => log::warn!("failed creating LAN discovery: {err}"),
			}
		});
	}

	/*
	 * Need to run the REPL on the main thread as for whatever reason, `linefeed`
	 * doesn't work as well when on another thread. In particular, when pressing
	 * Ctrl+C on a line, the line doesn't get immediately get cancelled.
	 */
	let repl = Repl::new(Arc::clone(&server), default_privacy, args.sanity_checks, args.exit_on_eof.into(), router_notify);
	task::block_in_place(move || repl.run());

	let _ = task.await;
	Ok(())
}
