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

use std::fmt::{Display, Formatter, Result as FmtResult};

use super::{Reader, SERVER_MAGIC_NUMBER, request::{Request, VersionType}, super::{Error, vu30::{Vu30, ExtendVu30}}};

#[derive(Debug, Clone)]
pub enum FailureResponse {
	Understood(String),
	Error(String),
	Invalid(String),
	BadPayload(String),
	TooLong(Vu30, String),
	UnsupportedProtocol(String),
	UnsupportedVersion { supported_version: Vu30, msg: String },
	Unknown, // For an unknown SERP status received
}

impl Display for FailureResponse {
	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
		// The error message can be empty, in that case exclude the ": "
		let fmt_optional_msg = |msg: &str, fmt: &mut Formatter<'_>| {
			if msg.is_empty() { Ok(()) }
			else { write!(fmt, ": {msg}") }
		};

		let fmt_str_msg = |s: &str, msg: &str, fmt: &mut Formatter<'_>| {
			write!(fmt, "{s}")?;
			fmt_optional_msg(msg, fmt)
		};

		match self {
			FailureResponse::Understood(msg) => fmt_str_msg("understod request but failed", msg, fmt),
			FailureResponse::Error(msg) => fmt_str_msg("error", msg, fmt),
			FailureResponse::Invalid(msg) => fmt_str_msg("server failed processing request", msg, fmt), // The request might not actually be invalid, the server can just lie
			FailureResponse::BadPayload(msg) => fmt_str_msg("bad request payload", msg, fmt),
			FailureResponse::TooLong(size_limit, msg) => {
				write!(fmt, "message was too long, server says it violated size limit of {size_limit}")?;
				fmt_optional_msg(msg, fmt)
			},
			FailureResponse::UnsupportedProtocol(msg) => fmt_str_msg("unsupported request", msg, fmt),
			FailureResponse::UnsupportedVersion { supported_version, msg } => {
				write!(fmt, "unsupported version, server supports version {supported_version}")?;

				match Request::protocol_version_type(*supported_version) {
					VersionType::Release => (),
					VersionType::Development => write!(fmt, " (which is a development version)")?,
				}

				fmt_optional_msg(msg, fmt)
			},
			FailureResponse::Unknown => write!(fmt, "unknown"),
		}
	}
}

/**
 * Includes additional information about the subrequest index in the response.
 */
#[derive(Debug, PartialEq, Eq)]
pub(super) enum RawResponse {
	Success(Vu30),
	Failure(RawFailureResponse),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum RawFailureResponse {
	Understood(Vu30, String),
	Error(String),
	Invalid(String),
	BadPayload(Vu30, String),
	TooLong(Vu30, String),
	UnsupportedProtocol(String),
	UnsupportedVersion { index: Vu30, supported_version: Vu30, msg: String },
}

impl RawResponse {
	/**
	 * Only way this can fail is if a string's length is larger than the maximum
	 * vu30.
	 */
	pub fn serialise(&self) -> Result<Vec<u8>, Error> {
		let data = SERVER_MAGIC_NUMBER.to_vec();
		Ok(match self {
			RawResponse::Success(index) => data.vu30(statuses::SUCCESS).vu30(*index),
			RawResponse::Failure(RawFailureResponse::Understood(index, msg)) => data.vu30(statuses::UNDERSTOOD).vu30(*index).string(msg)?,
			RawResponse::Failure(RawFailureResponse::Error(msg)) => data.vu30(statuses::ERROR).string(msg)?,
			RawResponse::Failure(RawFailureResponse::Invalid(msg)) => data.vu30(statuses::INVALID).string(msg)?,
			RawResponse::Failure(RawFailureResponse::BadPayload(index, msg)) => data.vu30(statuses::BAD_PAYLOAD).vu30(*index).string(msg)?,
			RawResponse::Failure(RawFailureResponse::TooLong(size_limit, msg)) => data.vu30(statuses::TOO_LONG).vu30(*size_limit).string(msg)?,
			RawResponse::Failure(RawFailureResponse::UnsupportedProtocol(msg)) => data.vu30(statuses::UNSUPPORTED_PROTOCOL).string(msg)?,
			RawResponse::Failure(RawFailureResponse::UnsupportedVersion { index, supported_version, msg }) => data.vu30(statuses::UNSUPPORTED_VERSION).vu30(*index).vu30(*supported_version).string(msg)?,
		})
	}

	pub async fn read(reader: &mut impl Reader) -> Result<RawResponse, Error> {
		let mut magic_number = [0; SERVER_MAGIC_NUMBER.len()];
		reader.read_exact(&mut magic_number).await?;
		if magic_number != SERVER_MAGIC_NUMBER {
			return Err(Error::Invalid(format!("invalid magic number, got {magic_number:?}")));
		}

		let status = reader.read_vu30().await?;
		Ok(match status {
			statuses::SUCCESS => RawResponse::Success(reader.read_vu30().await?),
			statuses::UNDERSTOOD => RawResponse::Failure(RawFailureResponse::Understood(reader.read_vu30().await?, reader.read_string().await?)),
			statuses::ERROR => RawResponse::Failure(RawFailureResponse::Error(reader.read_string().await?)),
			statuses::INVALID => RawResponse::Failure(RawFailureResponse::Invalid(reader.read_string().await?)),
			statuses::BAD_PAYLOAD => RawResponse::Failure(RawFailureResponse::BadPayload(reader.read_vu30().await?, reader.read_string().await?)),
			statuses::TOO_LONG => RawResponse::Failure(RawFailureResponse::TooLong(reader.read_vu30().await?, reader.read_string().await?)),
			statuses::UNSUPPORTED_PROTOCOL => RawResponse::Failure(RawFailureResponse::UnsupportedProtocol(reader.read_string().await?)),
			statuses::UNSUPPORTED_VERSION => RawResponse::Failure(RawFailureResponse::UnsupportedVersion {
				index: reader.read_vu30().await?, supported_version: reader.read_vu30().await?, msg: reader.read_string().await?,
			}),
			_ => return Err(Error::Response(FailureResponse::Unknown)),
		})
	}
}

impl RawFailureResponse {
	pub(super) fn process(self) -> FailureResponse {
		match self {
			RawFailureResponse::Understood(_index, msg) => FailureResponse::Understood(msg),
			RawFailureResponse::Error(msg) => FailureResponse::Error(msg),
			RawFailureResponse::Invalid(msg) => FailureResponse::Invalid(msg),
			RawFailureResponse::BadPayload(_index, msg) => FailureResponse::BadPayload(msg),
			RawFailureResponse::TooLong(size_limit, msg) => FailureResponse::TooLong(size_limit, msg),
			RawFailureResponse::UnsupportedProtocol(msg) => FailureResponse::UnsupportedProtocol(msg),
			RawFailureResponse::UnsupportedVersion { index: _, supported_version, msg } => FailureResponse::UnsupportedVersion { supported_version, msg },
		}
	}
}

mod statuses {
	use super::Vu30;

	pub const SUCCESS: Vu30 = Vu30::from_u8(0);
	pub const UNDERSTOOD: Vu30 = Vu30::from_u8(1);
	pub const ERROR: Vu30 = Vu30::from_u8(2);
	pub const INVALID: Vu30 = Vu30::from_u8(3);
	pub const BAD_PAYLOAD: Vu30 = Vu30::from_u8(4);
	pub const TOO_LONG: Vu30 = Vu30::from_u8(5);
	pub const UNSUPPORTED_PROTOCOL: Vu30 = Vu30::from_u8(6);
	pub const UNSUPPORTED_VERSION: Vu30 = Vu30::from_u8(7);
}
