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

use strum::IntoEnumIterator;

use super::{
	Server, Client, SpectatingMode, SANITY_CHECK_PERIOD, CHAT_MAX_BURST, CHAT_RATE_LIMIT, MoveChooser,
	scoring::{ScoringEvent, Winners},
	command::{Command, CommandVariant},
	joined::{Joined, JoinInfo},
	win_messages::{WinMessages, PlayerWinMessages, TeamWinMessages},
};

use crate::server::common::changes::ServerChanges;
use crate::protocol::message::{Message, WorldMessage, MiscMessage, GameStateTransition, text_io::{self, TextIoString, ClientTextInput, ChatType, ServerTextOutput}, stream::MsError};
use crate::world::{DELTA_TIME, update::WorldUpdate, event::WorldEvent, player::{PlayerId, HumanStyle}};
use crate::utils::{ToStr, maths::Circle, command::{Command as _, CommandVariant as _}};

impl Server {
	/**
	 * Returns Ok(()) if everything's fine, and Err(()) if the server should
	 * close.
	 */
	pub fn update(&mut self, mut dt: f32, mut messages: Vec<Message>) -> Result<(), ()> {
		/*
		 * Need at least one step to prevent a player joining being added to
		 * `joined` but no world updates occurring for them to be added to the
		 * world. Then the `InitWorld` message is sent to the client without
		 * containing the player, which is a violation of the protocol and so the
		 * client leaves the game.
		 */
		let steps = self.clock.advance(dt);
		if steps == 0 { return Ok(()); }

		/*
		 * Updates delta time to prevent time running slower (for some delta times
		 * being completely ignored) for chat rate limiting and sending out sanity
		 * checks.
		 */
		dt = steps as f32 * DELTA_TIME;

		let prev_spectator_count = self.spectator_count();

		let mut joined = self.accept_new_clients()?;
		let mut events = self.receive_client_messages(&mut messages, &mut joined, dt);
		self.remove_clients();
		self.balance_queues(dt);

		for i in 0..steps {
			self.update_world(&mut messages, mem::take(&mut events), &joined, i == 0);

			if let Some(timer) = &mut self.timer {
				timer.update(DELTA_TIME);
			}
		}

		self.after_update(&messages, &joined, prev_spectator_count, self.spectator_count());
		if let Some(msg) = self.get_sanity_check(dt) {
			messages.push(msg);
		}

		if self.sync_timer {
			messages.push(Message::World(WorldMessage::Timer(self.timer.clone())));
			self.sync_timer = false;
		}

		self.send_messages(messages);
		Ok(())
	}

	fn spectator_count(&self) -> usize {
		self.clients.values().filter(|client| client.spectating.is_some()).count()
	}

	fn accept_new_clients(&mut self) -> Result<Joined, ()> {
		let mut joined = Joined::default();

		loop {
			match self.connection_listener.get() {
				Ok(Some((incoming, request))) => {
					if self.clients.len() < self.max_clients {
						let spectate =
							if request.spectate { Some(SpectatingMode::Manual) }
							else if self.can_join() { None }
							else { Some(SpectatingMode::Automatic) };

						let stream = incoming.accept();

						let id = self.unused_player_id();

						log::info!("player (id = {id}) joined, spectating = {spectate:?}, name = \"{}\", source = {}", request.name, stream.source());

						let mut join_info = JoinInfo { style: HumanStyle { name: request.name, colour: request.colour }, team: None, config: None };
						if let Some(team_id) = self.world.next_team() {
							join_info.update_team(team_id, &self.world);
						}
						let client = Client::new(stream, spectate, join_info);

						if spectate.is_some() {
							joined.add_client_spectating(id);
						} else {
							joined.add_client_playing(id, client.join_info.clone().into());
						}

						self.clients.insert(id, client);
					} else {
						log::info!("player rejected, source = {}", incoming.source());
						incoming.reject(format!("too many clients, limit is {}", self.max_clients));
					}
				},
				Ok(None) => break,
				Err(()) => return Err(()),
			}
		}

		Ok(joined)
	}

	#[allow(clippy::cognitive_complexity)] // Yes, my code is bad. You don't need to tell me that.
	fn receive_client_messages(&mut self, messages: &mut Vec<Message>, joined: &mut Joined, dt: f32) -> Vec<WorldEvent> {
		// The borrow checker is fun
		let mut style_changes = BTreeMap::new();
		let mut team_changes = BTreeMap::new();
		let mut team_chats = Vec::new();
		let can_join = self.can_join();

		for (&id, client) in self.clients.iter_mut().filter(|(_, client)| !client.to_remove) {
			client.allowed_chats = (client.allowed_chats + CHAT_RATE_LIMIT * dt).min(CHAT_MAX_BURST);

			client.to_remove |= loop {
				match client.stream.receive() {
					Ok(Some(Message::PlayerUpdate(update, steps))) => client.updates.push(update, steps),
					Ok(Some(Message::PlayerBulkUpdate(bulk_update))) => client.updates.bulk_update(bulk_update),
					Ok(Some(Message::ClientTextInput(ClientTextInput::Chat(requested_type, msg)))) => {
						if client.allowed_chats >= 1.0 {
							let actual_type = if client.join_info.team.is_none() { ChatType::Global } else { requested_type };

							log::info!("player (id = {id}) sent chat message (requested {}, destination {}): {}", requested_type.to_str(), actual_type.to_str(), msg.get());

							let msg = Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::Chat(actual_type, client.join_info.style.name.clone(), client.join_info.style.colour, msg)));
							match actual_type {
								ChatType::Team => team_chats.push((msg, client.join_info.team)),
								ChatType::Global => messages.push(msg),
							}
							client.allowed_chats -= 1.0;
						} else {
							client.per_player_messages.push((messages.len(), Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::Server(text_io::from_const!("Rate limit reached, slow down."))))));
						}
					},
					Ok(Some(Message::ClientTextInput(ClientTextInput::Command(cmd)))) => {
						let response = match Command::build::<CommandVariant>(&cmd) {
							Ok(Some(Command::Help(None))) => Some(TextIoString::new_truncate(CommandVariant::iter().map(CommandVariant::help).collect::<Vec<_>>().join("\n"))),
							Ok(Some(Command::Help(Some(cmd)))) => Some(TextIoString::new_truncate(String::from(cmd.help()))),
							Ok(Some(Command::Spectate)) => {
								let msg = Some(match client.spectating {
									Some(SpectatingMode::Manual) => text_io::from_const!("Already spectating."),
									Some(SpectatingMode::Automatic) => text_io::from_const!("Already spectating, and will continue to spectate when the game ends."),
									None => text_io::from_const!("Now spectating."),
								});
								client.spectating = Some(SpectatingMode::Manual);
								msg
							},
							Ok(Some(Command::Play)) => {
								Some(if let Some(mode) = client.spectating {
									match mode {
										_ if can_join => {
											client.spectating = None;
											joined.add_player(id, client.join_info.clone().into());
											text_io::from_const!("Now playing.")
										},
										SpectatingMode::Manual => {
											client.spectating = Some(SpectatingMode::Automatic);
											text_io::from_const!("Will play once the game ends (joining mid-game has been disabled).")
										},
										SpectatingMode::Automatic => text_io::from_const!("Already chose to leave spectator mode once the game ends (joining mid-game has been disabled)."),
									}
								} else {
									text_io::from_const!("Already playing.")
								})
							},
							Ok(Some(Command::Ready)) => {
								if self.lobby.active() {
									if client.spectating.is_some() {
										Some(text_io::from_const!("Cannot vote ready while spectating."))
									} else {
										let success = self.lobby.add_ready(id);
										if success {
											None
										} else {
											Some(text_io::from_const!("Already voted."))
										}
									}
								} else {
									Some(text_io::from_const!("Game has already started."))
								}
							},
							Ok(Some(Command::Underpower(cmd))) => Some(self.event_chooser.underpower_command(id, cmd)),
							Ok(Some(Command::Name(name))) => {
								client.join_info.style.name = name.clone();
								style_changes.entry(id).or_insert((None, None)).0 = Some(name);
								Some(text_io::from_const!("Updated name."))
							},
							Ok(Some(Command::Colour(colour))) => {
								Some(if self.world.has_teams() {
									text_io::from_const!("Cannot change colour when teams are enabled.")
								} else {
									client.join_info.style.colour = colour;
									style_changes.entry(id).or_insert((None, None)).1 = Some(colour);
									text_io::from_const!("Updated colour.")
								})
							},
							Ok(Some(Command::Team(team))) => {
								Some(if !self.world.has_teams() {
									text_io::from_const!("Teams are not enabled.")
								} else if self.lobby.active() {
									if let Some(&team_id) = self.team_info.map.get(team.as_ref()) {
										client.join_info.update_team(team_id, &self.world);
										style_changes.entry(id).or_insert((None, None)).1 = Some(client.join_info.style.colour);
										team_changes.insert(id, team_id);
										text_io::from_const!("Updated team.")
									} else {
										TextIoString::new_truncate(String::from("error: team: unknown team"))
									}
								} else {
									text_io::from_const!("Cannot change team mid-game.")
								})
							},
							Ok(Some(Command::Teams)) => Some(self.team_info.teams_command_output(id, client.join_info.team, &self.world)),
							Ok(None) => None,
							Err(err) => Some(TextIoString::new_truncate(format!("error: {err}"))),
						};

						let before = messages.len();
						client.per_player_messages.push((before, Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::Command(cmd)))));
						if let Some(response) = response {
							client.per_player_messages.push((before, Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::CommandResponse(response)))));
						}
					},
					Ok(Some(Message::ClientTextInput(ClientTextInput::Extension(_)))) => (),
					Ok(Some(_)) => { // Violation of the protocol so removed
						log::warn!("player (id = {id}) violated the protocol, sending an invalid message");
						break true;
					}
					Ok(None) => break false,
					Err(err) => {
						if let MsError::Other(err) = err {
							log::warn!("player (id = {id}) disconnected with an error: {err}");
						}
						break true;
					}
				}
			}
		}

		/*
		 * Not doing this directly to prevent theoretical (but impractical) DoS
		 * attack from sending a WorldUpdate larger than client limits.
		 */
		let mut events = Vec::with_capacity(style_changes.len());
		for (id, (name, colour)) in style_changes {
			if let Some(name) = name {
				log::info!("player (id = {id}) changed name to \"{}\"", &*name);
				events.push(WorldEvent::PlayerName(id, name));
			}
			if let Some(colour) = colour {
				events.push(WorldEvent::PlayerColour(id, colour));
			}
		}

		for (id, team) in team_changes {
			events.push(WorldEvent::PlayerTeam(id, Some(team)));
		}

		for (msg, dest_team) in team_chats {
			if let Some(team) = dest_team {
				for client in self.clients.values_mut() {
					if client.join_info.team == Some(team) {
						client.per_player_messages.push((messages.len(), msg.clone()));
					}
				}
			} else {
				log::warn!("team chat sent from player with no team (this shouldn't happen)");
			}
		}

		events
	}

	fn balance_queues(&mut self, dt: f32) {
		for client in self.clients.values_mut() {
			client.updates.balance_queue(dt);
		}
	}

	fn remove_clients(&mut self) {
		self.clients.retain(|&id, client| {
			if client.to_remove {
				log::info!("player (id = {id}) left");
			}
			!client.to_remove
		});
	}

	fn update_world(&mut self, messages: &mut Vec<Message>, mut arg_events: Vec<WorldEvent>, joined: &Joined, first: bool) {
		// Creating the player update map and all acknowledgements
		let (mut player_updates, mut acks) = (BTreeMap::new(), BTreeMap::new());

		for (&id, client) in &mut self.clients {
			let (update, ack) = client.updates.pop();
			if client.spectating.is_none() {
				player_updates.insert(id, update);
			}
			acks.insert(id, ack);
		}

		// Constructing the pre_events
		let mut changes = ServerChanges::default();
		let move_chooser: &dyn MoveChooser = if self.lobby.active() { self.lobby.get_move_chooser() } else { &self.playing_move_chooser };
		let mut pre_events = self.event_chooser.get_pre_events(&self.world, move_chooser, first.then_some(joined));
		pre_events.append(&mut arg_events);
		self.world.apply_events(&pre_events, &mut changes);

		// Updating the world
		self.world.update(&player_updates, &mut changes);

		// Updating the scoring
		self.scoring.event(ScoringEvent::Time(DELTA_TIME));
		for kill in &changes.kills {
			if let Some(killer) = kill.killer {
				self.scoring.event(ScoringEvent::Kill(killer, self.world.get_player_team(killer)));
			}
			self.scoring.event(ScoringEvent::Death(kill.killed, self.world.get_player_team(kill.killed)));
		}

		for (&id, player) in self.world.get_players() {
			self.scoring.event(ScoringEvent::Pos(id, player.get_team(), player.get_pos(), DELTA_TIME));
		}

		// Constructing the post_events
		let (mut post_events, spectator_changes) = self.get_post_events(messages, &changes);

		// Adds and removes players if necessary
		let mut player_change_events = Vec::new();
		for (id, new_spectating) in spectator_changes {
			let Some(client) = self.clients.get_mut(&id) else { continue; };

			if client.spectating.is_some() != new_spectating.is_some() {
				if new_spectating.is_some() {
					player_change_events.push(WorldEvent::RemovePlayer(id));
				} else {
					let move_chooser: &dyn MoveChooser = if self.lobby.active() { self.lobby.get_move_chooser() } else { &self.playing_move_chooser };
					self.event_chooser.add_player(id, client.join_info.clone().into(), &self.world, move_chooser, &mut player_change_events);
				}
			}

			client.spectating = new_spectating;
		}
		self.world.apply_events(&player_change_events, &mut changes);
		self.prev_changes = changes;
		post_events.append(&mut player_change_events);

		// Constructing the message for this update
		for (id, ack) in acks {
			if !joined.is_new_client(id) {
				let update = Message::World(WorldMessage::WorldUpdate(WorldUpdate {
					pre_events: pre_events.clone(), players: player_updates.clone(), post_events: post_events.clone(),
				}, ack));

				self.clients.get_mut(&id).unwrap().per_player_messages.push((messages.len(), update));
			}
		}
	}

	fn get_post_events(&mut self, messages: &mut Vec<Message>, changes: &ServerChanges) -> (Vec<WorldEvent>, BTreeMap<PlayerId, Option<SpectatingMode>>) {
		let mut spectator_changes = BTreeMap::new();

		if self.lobby.active() {
			let just_started = self.lobby.just_started();
			for msg in self.lobby.update(DELTA_TIME, &self.world, &self.clients) {
				messages.push(Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::Server(msg))));
			}

			// Resends the timer as after updating it is no longer paused
			if just_started {
				let timer = self.lobby.get_timer();
				self.set_timer(timer);
			}

			if self.world.human_player_count() < self.lobby.get_min_players() {
				self.reset_lobby(true);
			} else if !self.lobby.active() {
				self.transition_playing();
				messages.push(Message::World(WorldMessage::GameStateTransition(GameStateTransition::Start)));
				return (self.reset(), spectator_changes);
			}

			// Scoring is ignored in the lobby
			let (world_events, _scoring_events) = self.event_chooser.get_and_apply_post_events(&mut self.world, self.lobby.get_move_chooser(), changes);
			(world_events, spectator_changes)
		} else {
			let (mut world_events, scoring_events) = self.event_chooser.get_and_apply_post_events(&mut self.world, &self.playing_move_chooser, changes);

			for event in scoring_events {
				self.scoring.event(event);
			}

			let res = self.scoring.update(&self.world, self.timer.as_ref().map(|timer| timer.displayed_seconds(timer.raw_seconds(), 0.0)));
			if let Some(eliminated) = res.eliminated {
				for id in eliminated {
					spectator_changes.insert(id, Some(SpectatingMode::Automatic));
				}
			}

			let reset = if let Some(winners) = res.winners {
				self.get_win_messages(winners, messages);
				true
			} else {
				self.world.human_player_count() == 0 && self.lobby.reset_if_no_players()
			};

			if reset {
				self.transition_lobby(&mut spectator_changes);
				world_events.append(&mut self.reset());
			}

			(world_events, spectator_changes)
		}
	}

	fn transition_playing(&mut self) {
		self.scoring.start();
		self.set_timer(self.playing_timer.clone());
	}

	fn reset_lobby(&mut self, only_reset_time: bool) {
		self.lobby.reset(self.world.get_blocks(), only_reset_time);
		self.set_timer(self.lobby.get_timer());
	}

	fn transition_lobby(&mut self, spectator_changes: &mut BTreeMap<PlayerId, Option<SpectatingMode>>) {
		self.reset_lobby(false);

		for (&id, client) in &self.clients {
			if client.spectating != Some(SpectatingMode::Manual) {
				spectator_changes.insert(id, None);
			}
		}

		if self.lobby.active() {
			self.scoring.clear();
		} else { // If the lobby is disabled, instead go to the playing state
			self.transition_playing();
		}
	}

	fn get_win_messages(&mut self, winners: Winners, messages: &[Message]) {
		let per_player_messages = match winners {
			Winners::Players(winners) => PlayerWinMessages::new(winners, self).get_messages(&self.clients, messages.len()),
			Winners::Teams(winners) => TeamWinMessages::new(winners, self).get_messages(&self.clients, messages.len()),
		};

		for (id, index, messages) in per_player_messages {
			if let Some(client) = self.clients.get_mut(&id) {
				for msg in messages {
					client.per_player_messages.push((index, msg));
				}
			}
		}
	}

	fn after_update(&mut self, messages: &[Message], joined: &Joined, prev_spectator_count: usize, spectator_count: usize) {
		let dscore_msg = self.scoring.delta_scores_message();

		for (&id, client) in &mut self.clients {
			let before = messages.len();
			if joined.is_new_client(id) {
				// Adds the initial messages if the player just joined
				client.set_init_per_player_messages([
					Message::World(WorldMessage::InitWorld {
						player_id: id,
						world: Box::new(self.world.clone()),
						spectator_count,
						extension: Box::new([]),
					}),
					Message::World(WorldMessage::Timer(self.timer.clone())),
					self.scoring.init_scores_message(),
				].into_iter());

				if let Some(msg) = &self.instructions {
					client.per_player_messages.push((before, Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::Server(msg.clone())))));
				}
			} else if let Some(msg) = &dscore_msg {
				client.per_player_messages.push((before, msg.clone()));
			}

			if joined.is_new_client(id) || prev_spectator_count != spectator_count {
				client.per_player_messages.push((messages.len(), Message::Misc(MiscMessage::SpectatorCount(spectator_count))));
			}
		}
	}

	fn get_sanity_check(&mut self, dt: f32) -> Option<Message> {
		self.next_sanity_check -= dt;
		(self.next_sanity_check <= 0.0).then(|| {
			self.next_sanity_check += SANITY_CHECK_PERIOD;
			Message::World(WorldMessage::SanityCheck(Box::new(self.world.get_dynamic_data().clone())))
		})
	}

	fn send_messages(&mut self, messages: Vec<Message>) {
		for (&id, client) in self.clients.iter_mut().filter(|(_, client)| !client.to_remove) {
			if let Err(err) = client.send_messages(&messages) {
				client.to_remove = true;
				if let MsError::Other(err) = err {
					log::warn!("player (id = {id}) disconnected with an error: {err}");
				}
			}
		}
	}
}
