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

use super::{Server, Client, PlayerId, TeamId, Message, MiscMessage, ServerTextOutput, TextIoString};

use crate::world::team::Team;
use crate::protocol::message::{WorldMessage, GameStateTransition};
use crate::utils::string_list;

pub trait WinMessages<I> where I: Copy + Ord {
	fn get_messages(self, clients: &BTreeMap<PlayerId, Client>, messages_len: usize) -> Vec<(PlayerId, usize, [Message; 2])>
	where
		Self: Sized,
	{
		// NOTE: If bot players get added to multiplayer, it's worth updating the win messages to avoid congratulating these bots
		let neutral_msg = self.get_neutral_message();

		clients.iter().map(|(&id, client)| {
			let (msg, trans) = if let Some(msg) = self.get_win_message(id, client.spectating.is_some()) {
				(msg, GameStateTransition::Win)
			} else {
				(neutral_msg.clone(), GameStateTransition::End)
			};

			(id, messages_len, [
				Message::Misc(MiscMessage::ServerTextOutput(ServerTextOutput::Server(TextIoString::new_truncate(msg)))),
				Message::World(WorldMessage::GameStateTransition(trans)),
			])
		}).collect()
	}

	fn get_neutral_message(&self) -> String {
		let winners = self.get_winners();
		match winners.len() {
			0 => Self::no_winners(),
			1 => Self::one_winner(self.id_to_str(*winners.first().unwrap())),
			_ => Self::multiple_winners(winners.iter().map(|&id| self.id_to_str(id)).collect()),
		}
	}

	fn get_win_message(&self, client_id: PlayerId, spectating: bool) -> Option<String> {
		if self.everyone_won() {
			Some(Self::all_winners(spectating))
		} else if self.won(client_id) {
			let other_winners = self.other_winners(client_id);
			Some(match other_winners.len() {
				0 => Self::you_won(),
				1 => Self::you_and_one_other_won(self.id_to_str(*other_winners.first().unwrap())),
				_ => Self::you_and_many_others_won(other_winners.into_iter().map(|id| self.id_to_str(id)).collect()),
			})
		} else {
			None
		}
	}

	fn get_winners(&self) -> &BTreeSet<I>;
	fn everyone_won(&self) -> bool;
	fn won(&self, client_id: PlayerId) -> bool;
	fn other_winners(&self, client_id: PlayerId) -> BTreeSet<I>;
	fn id_to_str(&self, id: I) -> &str;

	// Win messages
	fn no_winners() -> String;
	fn one_winner(winner: &str) -> String;
	fn multiple_winners(winners: Vec<&str>) -> String;

	fn all_winners(spectating: bool) -> String;

	fn you_won() -> String;
	fn you_and_one_other_won(other: &str) -> String;
	fn you_and_many_others_won(others: Vec<&str>) -> String;
}

pub struct PlayerWinMessages<'a> {
	winners: BTreeSet<PlayerId>,
	server: &'a Server,
}

impl PlayerWinMessages<'_> {
	pub fn new(winners: BTreeSet<PlayerId>, server: &Server) -> PlayerWinMessages<'_> {
		PlayerWinMessages { winners, server }
	}
}

impl WinMessages<PlayerId> for PlayerWinMessages<'_> {
	fn get_winners(&self) -> &BTreeSet<PlayerId> {
		&self.winners
	}

	fn everyone_won(&self) -> bool {
		self.winners.len() >= self.server.world.all_player_count()
	}

	fn won(&self, client_id: PlayerId) -> bool {
		self.winners.contains(&client_id)
	}

	fn other_winners(&self, client_id: PlayerId) -> BTreeSet<PlayerId> {
		let mut other_winners = self.winners.clone();
		other_winners.remove(&client_id);
		other_winners
	}

	fn id_to_str(&self, id: PlayerId) -> &str {
		Client::get_name(&self.server.clients, id)
	}

	fn no_winners() -> String {
		String::from("Nobody won the game!")
	}

	fn one_winner(winner: &str) -> String {
		format!("{winner} won the game!")
	}

	fn multiple_winners(winners: Vec<&str>) -> String {
		let mut msg = String::new();
		add_list(&mut msg, &winners);
		msg += " won the game!";
		msg
	}

	fn all_winners(spectating: bool) -> String {
		String::from(
			if spectating { "Everyone won!" }
			else { "Congratulations! Everyone won!" }
		)
	}

	fn you_won() -> String {
		String::from("Congratulations! You won!")
	}

	fn you_and_one_other_won(other: &str) -> String {
		format!("Congratulations! You and {other} won!")
	}

	fn you_and_many_others_won(others: Vec<&str>) -> String {
		let mut msg = String::from("Congratulations! You and players ");
		add_list(&mut msg, &others);
		msg += " won!";
		msg
	}
}

pub struct TeamWinMessages<'a> {
	winners: BTreeSet<TeamId>,
	server: &'a Server,
}

impl TeamWinMessages<'_> {
	pub fn new(winners: BTreeSet<TeamId>, server: &Server) -> TeamWinMessages<'_> {
		TeamWinMessages { winners, server }
	}

	fn client_to_team_id(&self, id: PlayerId) -> Option<TeamId> {
		self.server.clients.get(&id).and_then(|client| client.join_info.team)
	}
}

impl WinMessages<TeamId> for TeamWinMessages<'_> {
	fn get_winners(&self) -> &BTreeSet<TeamId> {
		&self.winners
	}

	fn everyone_won(&self) -> bool {
		self.winners.len() >= self.server.world.team_count()
	}

	fn won(&self, client_id: PlayerId) -> bool {
		self.client_to_team_id(client_id).is_some_and(|team_id| self.winners.contains(&team_id))
	}

	fn other_winners(&self, client_id: PlayerId) -> BTreeSet<TeamId> {
		let mut other_winners = self.winners.clone();
		if let Some(id) = self.client_to_team_id(client_id) {
			other_winners.remove(&id);
		}
		other_winners
	}

	fn id_to_str(&self, id: TeamId) -> &str {
		self.server.world.get_team(id).map_or("unknown", Team::get_name)
	}

	fn no_winners() -> String {
		String::from("No team won the game!")
	}

	fn one_winner(winner: &str) -> String {
		format!("Team {winner} won the game!")
	}

	fn multiple_winners(winners: Vec<&str>) -> String {
		let mut msg = String::from("Teams ");
		add_list(&mut msg, &winners);
		msg += " won the game!";
		msg
	}

	fn all_winners(spectating: bool) -> String {
		String::from(
			if spectating { "All teams won!" }
			else { "Congratulations! All teams won!" }
		)
	}

	fn you_won() -> String {
		String::from("Congratulations! Your team won!")
	}

	fn you_and_one_other_won(other: &str) -> String {
		format!("Congratulations! Your team and team {other} won!")
	}

	fn you_and_many_others_won(others: Vec<&str>) -> String {
		let mut msg = String::from("Congratulations! Your team and teams ");
		add_list(&mut msg, &others);
		msg += " won!";
		msg
	}
}

fn add_list(s: &mut String, list: &[&str]) {
	string_list::create(s, list, "and");
}
