// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::{time::Instant, fmt::Write};

use egui::{Ui, Color32, FontId, FontFamily, Frame, Margin, RichText};
use quinn::Endpoint;

use super::common::ConnectionStarter;

use crate::net::{Address, serp::request::{Request, PlayRequest, VersionType}, server::PORT};
use crate::protocol::discovery::DiscoveryServer;
use crate::app::gui::style::{UiExt, LABEL_SIZE};
use crate::app::config::Config;

trait DiscoveryEntry {
	#[must_use]
	fn add_to_ui(&self, ui: &mut Ui, host: &str, can_join: bool, time: f32, play_button_text: &str) -> bool;
}

const SMALL_COLOUR: Color32 = Color32::from_gray(0xaf);
const FADE_IN_TIME: f32 = 0.125;

impl DiscoveryEntry for DiscoveryServer {
	fn add_to_ui(&self, ui: &mut Ui, host: &str, can_join: bool, time: f32, play_button_text: &str) -> bool {
		// Provides a message if the LAN server is unsupported
		let unsupported_message = if self.protocol_type != PlayRequest::PROTOCOL_TYPE {
			Some(format!("Game server is unsupported. Check that this game is running the same version as the server. It is possible that the server is running a different fork. Technical details: client protocol type = {}, server protocol type = {}.", PlayRequest::PROTOCOL_TYPE, self.protocol_type))
		} else if self.protocol_version != PlayRequest::PROTOCOL_VERSION {
			let mut msg = String::new();

			match Request::protocol_version_type(self.protocol_version) {
				VersionType::Release => {
					match Request::protocol_version_type(PlayRequest::PROTOCOL_VERSION) {
						VersionType::Release => {
							let (older_or_newer, older, newer) = if self.protocol_version < PlayRequest::PROTOCOL_VERSION {
								("an old", "server", "client")
							} else {
								("a new", "client", "server")
							};
							let _ = write!(msg, "Game server is running {older_or_newer}er version of the game which isn't supported. To play the game, update the {older} to match the {newer}'s version or revert the {newer} to the {older}'s version.");
						},
						VersionType::Development => _ = write!(msg, "Game is running a development version which the server doesn't support."),
					}
				},
				VersionType::Development => _ = write!(msg, "Game server is running a development version of the game which isn't supported."),
			}

			let _ = write!(msg, " Technical details: server version = {}, client version = {}.", self.protocol_version, PlayRequest::PROTOCOL_VERSION);
			Some(msg)
		} else {
			None
		};

		let background = if unsupported_message.is_some() { Color32::from_rgb(0x3f, 0x27, 0x27) } else { Color32::from_gray(0x27) };
		let supported = unsupported_message.is_none();
		let max_opacity = if supported { 1.0 } else { 0.5 };
		ui.set_opacity((time / FADE_IN_TIME).min(1.0) * max_opacity);

		let mut start = false;
		let response = Frame::none().fill(background).inner_margin(Margin::same(8.0)).show(ui, |ui| {
			name_and_desc(ui, self.name.get(), self.desc.get(), false);

			let mut msg = format!("{}, at {host}", self.format_players());

			/*
			 * To avoid including too much information in the UI, omit the game id
			 * and port if the default settings are used.
			 */
			if self.game_id.get() != "default" {
				let _ = write!(msg, " with game id \"{}\"", self.game_id);
			}
			if self.port != PORT {
				let _ = write!(msg, " on port {}", self.port);
			}
			msg.push('.');

			let size = ui.spacing().button_padding.y * 2.0 + LABEL_SIZE;
			ui.label(RichText::new(msg).color(SMALL_COLOUR).font(FontId::new(size, FontFamily::Proportional)));

			ui.add_space(8.0);
			start |= ui.b2_enabled(play_button_text, supported && can_join).clicked();
		}).response;

		if let Some(msg) = unsupported_message {
			response.on_hover_ui_at_pointer(|ui| _ = ui.label(msg));
		}

		ui.set_opacity(1.0);
		start
	}
}

pub fn name_and_desc(ui: &mut Ui, mut name: &str, desc: &str, top_padding: bool) {
	if name.trim().is_empty() {
		name = "Unnamed Server";
	}
	let builder = ui.h2(name).selectable();
	if top_padding { builder.no_bottom_padding() } else { builder.no_padding() }.add();

	if !desc.trim().is_empty() {
		ui.add_space(2.0);
		ui.h3(desc).no_padding().selectable().add();
	}

	ui.add_space(12.0);
}

#[allow(clippy::too_many_arguments)]
pub fn add_entry(ui: &mut Ui, endpoint: Result<Endpoint, String>, config: &Config, starter: Option<ConnectionStarter>, server: &DiscoveryServer, host: &str, init_time: Instant) {
	let clicked = server.add_to_ui(ui, host, starter.is_some(), init_time.elapsed().as_secs_f32(), config.gui.play_button_text());
	if clicked && let Some(starter) = starter {
		let addr = Address { host: Box::from(host), port: server.port };
		let request = PlayRequest::new(server.game_id.clone(), config.gui.spectate, config.get_style());
		starter.connect(endpoint, addr, request);
	}
}
