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

use tokio::time::{self, Duration};
use quinn::Endpoint;

use crate::net::{Address, serp::request::PlayRequest, utils::{quic::{self, IdleConfig}, security::Security}, server::StreamSender};
use crate::protocol::message::{
	Message, WorldMessage, MiscMessage, LevelMessage, StoryText,
	text_io::ServerTextOutput, scores::Scores, timer::Timer,
	stream::{local::{LocalMessageStream, SingleplayerMessageStream}, networked::ClientMessageStream, DynMs, MsError},
};
use crate::world::{World, WorldDynamic, MAX_STEPS, MAX_PLAYERS, MAX_TEAMS, player::{PlayerId, HumanStyle}, update::WorldUpdate};
use crate::server::level::config::Config;
use crate::app::playing::view::Sfx;

const MAX_SERVER_TEXT_OUTPUT: usize = 4096;
const MAX_SOUNDS: usize = 16;

pub struct Client {
	stream: MessageStream,
	updates: Vec<(WorldUpdate, u64)>,
	text_output: Vec<ServerTextOutput>,
	sounds: Vec<Sfx>,
	scores: Scores,
	timer: Option<(Timer, f64)>,
	sanity_check_data: Option<WorldDynamic>,
	spectator_count: usize,
	level: Option<LevelInfo>,
}

enum MessageStream {
	Singleplayer(Box<SingleplayerMessageStream>), // Boxed because of clippy warning with large enum variant size difference
	Other(Box<DynMs>),
}

pub struct LevelInfo {
	index: Option<usize>,
	init_world: World,
	init_lives: u32,
	lives: u32,
	init_must_kill: usize,
	must_kill: usize,
	story_text: Vec<StoryText>,
	has_reset: bool,
	has_completed: bool,
}

/**
 * An enum categorising types of errors methods of the Client return.
 */
pub enum ClientError {
	Io(MsError), // I/O errors in the message stream
	Protocol(String), // Protocol violations
	Limit(String), // When a limit to prevent application layer denial of service attacks is violated
}

pub type ClientRes = (PlayerId, Client, World);

impl Client {
	pub fn story_mode(config: Arc<Config>, config_path: PathBuf, style: HumanStyle, level_index: Option<usize>, sanity_checks: bool, spectate: bool) -> Result<ClientRes, String> {
		let (init_msg, stream) = SingleplayerMessageStream::try_new(config, config_path, style, sanity_checks, spectate)?;
		Client::try_new(init_msg, MessageStream::Singleplayer(Box::new(stream)), level_index)
	}

	pub async fn multiplayer(endpoint: Result<Endpoint, String>, addr: Address, request: PlayRequest) -> Result<ClientRes, String> {
		time::timeout(Duration::from_secs(10), async {
			let (connection, security) = quic::connect(endpoint?, addr.host.as_ref(), addr.port, IdleConfig::Default).await?;
			let (stream, init_msg) = ClientMessageStream::try_new(connection, request, security).await?;
			Client::try_new(init_msg, MessageStream::Other(Box::new(stream)), None)
		}).await.unwrap_or_else(|_| Err(String::from("connection timed out")))
	}

	pub async fn local_gui(request: PlayRequest, stream_sender: StreamSender) -> Result<ClientRes, String> {
		let (stream_a, mut stream_b) = LocalMessageStream::new_pair();
		stream_sender.send((Box::new(stream_a), request)).await.map_err(|err| format!("failed sending to local server: {err}"))?;
		let init_msg = stream_b.receive_async().await.ok_or_else(|| String::from("failed receiving initial message"))?;
		Client::try_new(init_msg, MessageStream::Other(Box::new(stream_b)), None)
	}

	fn try_new(init_msg: Message, stream: MessageStream, level_index: Option<usize>) -> Result<ClientRes, String> {
		// Receives the response to connecting
		let (player_id, world, spectator_count, level) = match init_msg {
			Message::World(WorldMessage::InitWorld { player_id, world, spectator_count, extension: _ }) => (player_id, world, spectator_count, None),
			Message::Level(LevelMessage::Init { player_id, world, lives, must_kill, story_text }) => {
				let level = LevelInfo {
					index: level_index,
					init_world: *world.clone(),
					init_lives: lives,
					lives,
					init_must_kill: must_kill,
					must_kill,
					story_text,
					has_reset: false,
					has_completed: false,
				};
				(player_id, world, 0, Some(level))
			},
			_ => return Err(String::from("expected InitWorld message (or Init for level servers), got something else")),
		};

		world.validate_init()?;

		let client = Client {
			stream,
			updates: Vec::new(),
			text_output: Vec::new(),
			sounds: Vec::new(),
			scores: Scores::default(),
			timer: None,
			sanity_check_data: None,
			spectator_count,
			level,
		};

		Ok((player_id, client, *world))
	}

	pub fn get_scores(&self) -> &Scores { &self.scores }
	pub fn get_spectator_count(&self) -> usize { self.spectator_count }
	pub fn get_ping(&self) -> Duration { self.stream.as_ref().ping() }
	pub fn get_security(&self) -> Security { self.stream.as_ref().security() }

	pub fn get_time(&mut self, shift: f32) -> Option<String> {
		self.timer.as_mut().map(|(timer, max)| {
			let displayed = timer.displayed_seconds(timer.raw_seconds(), shift as f64);
			*max = timer.most_ahead(displayed, *max);
			timer.format_string(*max)
		})
	}

	pub fn is_networked(&self) -> bool {
		self.stream.as_ref().is_networked()
	}

	pub fn is_level(&self) -> bool {
		self.level.is_some()
	}

	pub fn get_level(&self) -> Option<&LevelInfo> {
		self.level.as_ref()
	}

	pub fn get_level_mut(&mut self) -> Option<&mut LevelInfo> {
		self.level.as_mut()
	}

	pub fn send(&mut self, msg: Message) -> Result<(), ClientError> {
		self.stream.as_mut().send(msg).map_err(ClientError::Io)
	}

	pub fn flush(&mut self) -> Result<(), ClientError> {
		self.stream.as_mut().flush().map_err(ClientError::Io)
	}

	pub fn process_messages(&mut self, dt: f32) -> Result<(), ClientError> {
		if let MessageStream::Singleplayer(stream) = &mut self.stream {
			stream.set_dt(dt);
		}

		loop {
			match self.stream.as_mut().receive() {
				Ok(Some(Message::World(WorldMessage::WorldUpdate(update, ack)))) => {
					self.updates.push((update, ack));

					if self.updates.len() > MAX_STEPS { // Client has been inactive for a while
						return Err(ClientError::Limit(String::from("too many steps accumulated")));
					}

					self.discard_sanity_check_data();
				},
				Ok(Some(Message::World(WorldMessage::InitScores(scores)))) => {
					self.scores = scores;
					self.validate_scores()?;
				},
				Ok(Some(Message::World(WorldMessage::DScores(delta)))) => {
					delta.apply(&mut self.scores);
					self.validate_scores()?;
				},
				Ok(Some(Message::World(WorldMessage::Timer(timer)))) => self.timer = timer.map(|t| { let s = t.raw_seconds(); (t, s) }),
				Ok(Some(Message::World(WorldMessage::SanityCheck(data)))) => self.sanity_check_data = Some(*data),
				Ok(Some(Message::World(WorldMessage::GameStateTransition(trans)))) => {
					if self.sounds.len() < MAX_SOUNDS {
						self.sounds.push(Sfx::GameStateTransition(trans));
					}
				},
				Ok(Some(Message::Misc(MiscMessage::ServerTextOutput(msg)))) => {
					self.text_output.push(msg);

					if self.text_output.len() > MAX_SERVER_TEXT_OUTPUT {
						return Err(ClientError::Limit(String::from("too much server text output accumulated")));
					}
				},
				Ok(Some(Message::Misc(MiscMessage::SpectatorCount(count)))) => self.spectator_count = count,
				Ok(Some(Message::World(WorldMessage::Extension(_)) | Message::Misc(MiscMessage::Extension(_)))) => (),
				Ok(Some(Message::Level(msg))) => {
					let Some(level) = &mut self.level else {
						return Err(ClientError::Protocol(String::from("server somehow sent level message despite not being level")));
					};

					match msg {
						LevelMessage::Completion => level.has_completed = true,
						LevelMessage::Reset => {
							level.has_reset = true;
							level.lives = level.init_lives;
							level.must_kill = level.init_must_kill;
							self.updates.clear();
							self.discard_sanity_check_data();
						},
						LevelMessage::Lives(lives) => level.lives = lives,
						LevelMessage::MustKill(must_kill) => level.must_kill = must_kill,
						LevelMessage::Init { .. } => return Err(ClientError::Protocol(String::from("sending an invalid level message"))),
					}
				},
				Ok(Some(_)) => return Err(ClientError::Protocol(String::from("sending an invalid message"))),
				Ok(None) => return Ok(()),
				Err(err) => return Err(ClientError::Io(err)),
			}
		}
	}

	fn validate_scores(&self) -> Result<(), ClientError> {
		if self.scores.get_player_scores().len() <= MAX_PLAYERS && self.scores.get_team_scores().len() <= MAX_TEAMS {
			Ok(())
		} else {
			Err(ClientError::Limit(String::from("too many scores")))
		}
	}

	fn discard_sanity_check_data(&mut self) {
		if self.sanity_check_data.take().is_some() {
			log::debug!("ignoring sanity check, received another world event"); // The world sent by the server is outdated, so this must be ignored
		}
	}

	pub fn update_timer(&mut self, dt: f32) {
		if let Some((timer, _)) = &mut self.timer {
			timer.update(dt);
		}
	}

	pub fn take_text_output(&mut self) -> Vec<ServerTextOutput> { mem::take(&mut self.text_output) }
	pub fn take_world_updates(&mut self) -> Vec<(WorldUpdate, u64)> { mem::take(&mut self.updates) }
	pub fn take_sounds(&mut self) -> Vec<Sfx> { mem::take(&mut self.sounds) }
	pub fn take_sanity_check_data(&mut self) -> Option<WorldDynamic> { self.sanity_check_data.take() }
}

impl MessageStream {
	fn as_ref(&self) -> &DynMs {
		match self {
			MessageStream::Singleplayer(stream) => stream.as_ref(),
			MessageStream::Other(stream) => stream.as_ref(),
		}
	}

	fn as_mut(&mut self) -> &mut DynMs {
		match self {
			MessageStream::Singleplayer(stream) => stream.as_mut(),
			MessageStream::Other(stream) => stream.as_mut(),
		}
	}
}

impl LevelInfo {
	pub fn get_index(&self) -> Option<usize> { self.index }
	pub fn get_init_lives(&self) -> u32 { self.init_lives }
	pub fn get_lives(&self) -> u32 { self.lives }
	pub fn get_must_kill(&self) -> usize { self.must_kill }
	pub fn get_init_must_kill(&self) -> usize { self.init_must_kill }
	pub fn take_story_text(&mut self) -> Vec<StoryText> { mem::take(&mut self.story_text) }
	pub fn take_has_reset(&mut self) -> Option<World> { mem::take(&mut self.has_reset).then(|| self.init_world.clone()) }
	pub fn take_has_completed(&mut self) -> bool { mem::take(&mut self.has_completed) }
}
