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

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

use super::{Server, ServerArgs, PORT, router::{Router, GameServer, StreamSender}, privacy::{PrivacyConfig, PrivacyLevel}};

use crate::net::{utils::udp, lan_discovery::server::LanDiscoveryServer};
use crate::server::multiplayer::config::{Config, DiscoveryConfig};

pub struct GuiServerHandle {
	pub stream_sender: StreamSender,
	pub discovery: DiscoveryConfig,
	pub port: u16,
	pub lan_discovery: bool,
	pub hosts: Vec<Box<str>>,
	server: Arc<Server>,
	close_lan_discovery: Arc<Notify>,
}

impl Drop for GuiServerHandle {
	fn drop(&mut self) {
		self.close_lan_discovery.notify_waiters();
		self.server.close();
	}
}

pub async fn run(config: Config, config_path: Arc<Path>) -> Result<(GuiServerHandle, JoinHandle<()>), String> {
	let discovery = config.discovery.clone();

	let socket = {
		let ip_addr = IpAddr::V6(Ipv6Addr::UNSPECIFIED);
		udp::new(SocketAddr::new(ip_addr, PORT)).or_else(|_| udp::new(SocketAddr::new(ip_addr, 0))).map_err(|err| format!("failed binding socket: {err}"))?
	};

	let addr = socket.local_addr().map_err(|err| format!("failed getting local address: {err}"))?;
	let port = addr.port();

	let router_notify = Arc::new(Notify::new());
	let server = GameServer::try_new(config, config_path, PrivacyConfig::from_level(PrivacyLevel::Local), false, Arc::clone(&router_notify))?;
	let stream_sender = server.get_stream_sender();
	let router = Router::with_single_server(server, port, router_notify);

	let close_lan_discovery = Arc::new(Notify::new());
	let server = Server::try_new(socket, router, ServerArgs::new_gui_server()).await.map_err(|err| format!("failed creating server: {err}"))?;
	let hosts = server.get_locally_connectable_hosts().map(|addr| addr.to_string().into_boxed_str()).collect();

	let server_task = {
		let server = Arc::clone(&server);
		tokio::spawn(server.run())
	};

	let lan_discovery = if let Ok(lan_discovery) = LanDiscoveryServer::try_new(addr) {
		tokio::spawn(lan_discovery.loop_forever(Arc::clone(&close_lan_discovery)));
		true
	} else { false };

	Ok((GuiServerHandle { stream_sender, discovery, port, lan_discovery, hosts, server, close_lan_discovery }, server_task))
}
