// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
#[cfg(test)]
mod tests;

use serde::{Serialize, Deserialize};

use super::{CLIENT_MAGIC_NUMBER, Reader, response::RawFailureResponse, super::{Error, SIZE_LIMIT, super::serp, vu30::{Vu30, ExtendVu30, ReadVu30, ReadError}}};

use crate::world::{colour::Colour, player::PlayerName};
#[cfg(feature = "client")] use crate::world::player::HumanStyle;
use crate::net::{server::publish::Key, utils::hostname::Hostname};
use crate::protocol::discovery::GameId;
use crate::utils::or::Or;

/**
 * Represents a SERP request, as well as a subrequest (because right now only
 * one subrequest is being sent out).
 */
#[derive(Debug, PartialEq, Eq)]
pub enum Request {
	Play(PlayRequest),
	Info,
	Discover,
	Publish(PublishRequest),
}

pub enum VersionType {
	Release,
	Development,
}

impl Request {
	pub fn protocol_version_type(version: Vu30) -> VersionType {
		if version.get() >= versions::VERSION_FIRST_DEV { VersionType::Development }
		else { VersionType::Release }
	}

	/**
	 * Serialises the entire request, returning the length of the request
	 * payload.
	 */
	pub(super) fn serialise(&self) -> Result<(Vec<u8>, usize), Error> {
		let subrequest = self.serialise_subrequest()?;
		let mut data = Vec::new();
		data.extend_from_slice(CLIENT_MAGIC_NUMBER);
		data.extend_bytes(&subrequest)?;
		Ok((data, subrequest.len()))
	}

	fn serialise_subrequest(&self) -> Result<Vec<u8>, Error> {
		let mut data = Vec::new();
		let (p_type, p_version) = self.subprotocol_info();
		data.extend_vu30(p_type);
		data.extend_vu30(p_version);
		let payload = self.serialise_subrequest_payload()?;
		data.extend_bytes(&payload)?;
		Ok(data)
	}

	/**
	 * Returns (type, version) for the request's subprotocol.
	 */
	fn subprotocol_info(&self) -> (Vu30, Vu30) {
		match self {
			Request::Play(_) => (types::PLAY, versions::PLAY),
			Request::Info => (types::INFO, versions::INFO),
			Request::Discover => (types::DISCOVER, versions::DISCOVER),
			Request::Publish(_) => (types::PUBLISH, versions::PUBLISH),
		}
	}

	pub(super) fn subprotocol_version(&self) -> Vu30 {
		self.subprotocol_info().1
	}

	fn serialise_subrequest_payload(&self) -> Result<Vec<u8>, Error> {
		match self {
			Request::Play(request) => serp::serialise(request),
			Request::Info | Request::Discover => Ok(vec![]), // No payload
			Request::Publish(request) => serp::serialise(request),
		}
	}
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PlayRequest {
	pub game_id: GameId,
	pub spectate: bool,
	pub name: PlayerName,
	pub colour: Colour,
	extension: Box<[u8]>,
}

impl PlayRequest {
	pub const PROTOCOL_TYPE: Vu30 = types::PLAY;
	pub const PROTOCOL_VERSION: Vu30 = versions::PLAY;

	#[cfg(feature = "client")]
	pub fn new(game_id: GameId, spectate: bool, style: HumanStyle) -> PlayRequest {
		PlayRequest { game_id, spectate, name: style.name, colour: style.colour, extension: Box::from([]) }
	}
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PublishRequest {
	Init(u16, Option<Hostname>, Key),
	Subscribe(Key),
}

pub(super) struct RawSubrequest {
	subprotocol_type: Vu30,
	subprotocol_version: Vu30,
	payload: Vec<u8>,
}

pub(super) enum SubrequestResult {
	/// The subprotocol type isn't supported.
	UnsupportedProtocol,

	/// The subprotocol version isn't supported. Includes the supported version.
	UnsupportedVersion(Vu30),

	/// The protocol is supported. Includes the result of deserialising the
	/// payload into a request.
	Supported(Result<Request, Error>),
}

impl RawSubrequest {
	pub(super) async fn read_all(reader: &mut impl Reader) -> Result<Vec<RawSubrequest>, Or<Error, RawFailureResponse>> {
		let mut magic_number = [0; CLIENT_MAGIC_NUMBER.len()];
		reader.read_exact(&mut magic_number).await.map_err(Or::A)?;
		if magic_number != CLIENT_MAGIC_NUMBER {
			return Err(Or::A(Error::Invalid(format!("invalid magic number, got {magic_number:?}"))));
		}

		let subrequest_data = reader.read_bytes().await.map_err(|(err, read_err)| Or::Both(err, match read_err {
			ReadError::Io | ReadError::Utf8(_) /* Shouldn't happen */ => RawFailureResponse::Invalid(String::from("failed reading request")),
			ReadError::TooLong { len: _, size_limit } => RawFailureResponse::TooLong(Vu30::try_from_usize(size_limit).unwrap_or(Vu30::MAX), String::new()),
		}))?;

		let mut reader = subrequest_data.as_slice();
		let mut subrequests = Vec::new();

		while !reader.is_empty() {
			let err = |()| Or::B(RawFailureResponse::Invalid(String::from("cannot read subrequest info")));
			let (subprotocol_type, subprotocol_version, payload_len) = (reader.read_vu30().map_err(err)?, reader.read_vu30().map_err(err)?, reader.read_vu30().map_err(err)?.get() as usize);

			if let Some((payload, remaining)) = reader.split_at_checked(payload_len) {
				subrequests.push(RawSubrequest { subprotocol_type, subprotocol_version, payload: payload.to_vec() });
				reader = remaining;
			} else {
				return Err(Or::B(RawFailureResponse::Invalid(String::from("subrequest payload too long"))));
			}
		}

		Ok(subrequests)
	}

	pub(super) fn deserialise(&self) -> SubrequestResult {
		match (self.subprotocol_type, self.subprotocol_version) {
			(types::PLAY, versions::PLAY) => SubrequestResult::Supported(serp::deserialise::<PlayRequest>(&self.payload, SIZE_LIMIT).map(Request::Play)),
			(types::INFO, versions::INFO) | (types::DISCOVER, versions::DISCOVER) => {
				SubrequestResult::Supported(match self.subprotocol_type {
					_ if !self.payload.is_empty() => Err(Error::Deserialisation(String::from("expected empty subrequest payload"))),
					types::INFO => Ok(Request::Info),
					_ => Ok(Request::Discover),
				})
			},
			(types::PUBLISH, versions::PUBLISH) => SubrequestResult::Supported(serp::deserialise::<PublishRequest>(&self.payload, SIZE_LIMIT).map(Request::Publish)),

			(types::PLAY, _) => SubrequestResult::UnsupportedVersion(versions::PLAY),
			(types::INFO, _) => SubrequestResult::UnsupportedVersion(versions::INFO),
			(types::DISCOVER, _) => SubrequestResult::UnsupportedVersion(versions::DISCOVER),
			(types::PUBLISH, _) => SubrequestResult::UnsupportedVersion(versions::PUBLISH),

			_ => SubrequestResult::UnsupportedProtocol,
		}
	}
}

mod types {
	use super::Vu30;

	pub const PLAY: Vu30 = Vu30::from_u8(0);
	pub const INFO: Vu30 = Vu30::from_u8(1);
	pub const DISCOVER: Vu30 = Vu30::from_u8(2);
	pub const PUBLISH: Vu30 = Vu30::from_u8(3);
}

mod versions {
	use super::Vu30;

	pub const VERSION_FIRST_DEV: u32 = 0x20000000;

	pub const PLAY: Vu30 = Vu30::try_from_u32(5).unwrap();
	//pub const PLAY: Vu30 = Vu30::try_from_u32(VERSION_FIRST_DEV + 65).unwrap(); // Use this for the next debug version

	pub const INFO: Vu30 = Vu30::ZERO;

	pub const DISCOVER: Vu30 = Vu30::ZERO;
	//pub const DISCOVER: Vu30 = Vu30::try_from_u32(VERSION_FIRST_DEV + 5).unwrap(); // Use this for the next development version

	pub const PUBLISH: Vu30 = Vu30::ZERO;
	//pub const PUBLISH: Vu30 = Vu30::try_from_u32(VERSION_FIRST_DEV + 8).unwrap(); // Use this for the next development version
}
