use anyhow::{Result, bail};
use vbare::OwnedVersionedData;

use crate::generated::{v1, v2, v3, v4};

pub enum ToClient {
	V1(v1::ToClient),
	V2(v2::ToClient),
	V3(v3::ToClient),
	V4(v4::ToClient),
}

impl OwnedVersionedData for ToClient {
	type Latest = v4::ToClient;

	fn wrap_latest(latest: Self::Latest) -> Self {
		Self::V4(latest)
	}

	fn unwrap_latest(self) -> Result<Self::Latest> {
		match self {
			Self::V4(data) => Ok(data),
			_ => bail!("version not latest"),
		}
	}

	fn deserialize_version(payload: &[u8], version: u16) -> Result<Self> {
		match version {
			1 => Ok(Self::V1(serde_bare::from_slice(payload)?)),
			2 => Ok(Self::V2(serde_bare::from_slice(payload)?)),
			3 => Ok(Self::V3(serde_bare::from_slice(payload)?)),
			4 => Ok(Self::V4(serde_bare::from_slice(payload)?)),
			_ => bail!("invalid client protocol version: {version}"),
		}
	}

	fn serialize_version(self, version: u16) -> Result<Vec<u8>> {
		match (self, version) {
			(Self::V1(data), 1) => serde_bare::to_vec(&data).map_err(Into::into),
			(Self::V2(data), 2) => serde_bare::to_vec(&data).map_err(Into::into),
			(Self::V3(data), 3) => serde_bare::to_vec(&data).map_err(Into::into),
			(Self::V4(data), 4) => serde_bare::to_vec(&data).map_err(Into::into),
			(_, version) => bail!("unexpected client protocol version: {version}"),
		}
	}

	fn deserialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
		vec![Self::v1_to_v2, Self::v2_to_v3, Self::v3_to_v4]
	}

	fn serialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
		vec![Self::v4_to_v3, Self::v3_to_v2, Self::v2_to_v1]
	}
}

impl ToClient {
	fn v1_to_v2(self) -> Result<Self> {
		let Self::V1(data) = self else {
			bail!("expected client protocol v1 ToClient")
		};

		let body = match data.body {
			v1::ToClientBody::Init(init) => v2::ToClientBody::Init(v2::Init {
				actor_id: init.actor_id,
				connection_id: init.connection_id,
			}),
			v1::ToClientBody::Error(error) => v2::ToClientBody::Error(v2::Error {
				group: error.group,
				code: error.code,
				message: error.message,
				metadata: error.metadata,
				action_id: error.action_id,
			}),
			v1::ToClientBody::ActionResponse(response) => {
				v2::ToClientBody::ActionResponse(v2::ActionResponse {
					id: response.id,
					output: response.output,
				})
			}
			v1::ToClientBody::Event(event) => v2::ToClientBody::Event(v2::Event {
				name: event.name,
				args: event.args,
			}),
		};

		Ok(Self::V2(v2::ToClient { body }))
	}

	fn v2_to_v3(self) -> Result<Self> {
		let Self::V2(data) = self else {
			bail!("expected client protocol v2 ToClient")
		};
		Ok(Self::V3(data.into()))
	}

	fn v3_to_v4(self) -> Result<Self> {
		let Self::V3(data) = self else {
			bail!("expected client protocol v3 ToClient")
		};

		let body = match data.body {
			v3::ToClientBody::Init(init) => v4::ToClientBody::Init(v4::Init {
				actor_id: init.actor_id,
				connection_id: init.connection_id,
			}),
			v3::ToClientBody::Error(error) => v4::ToClientBody::Error(v4::Error {
				group: error.group,
				code: error.code,
				message: error.message,
				metadata: error.metadata,
				action_id: error.action_id,
				actor: None,
			}),
			v3::ToClientBody::ActionResponse(response) => {
				v4::ToClientBody::ActionResponse(v4::ActionResponse {
					id: response.id,
					output: response.output,
				})
			}
			v3::ToClientBody::Event(event) => v4::ToClientBody::Event(v4::Event {
				name: event.name,
				args: event.args,
			}),
		};

		Ok(Self::V4(v4::ToClient { body }))
	}

	fn v4_to_v3(self) -> Result<Self> {
		let Self::V4(data) = self else {
			bail!("expected client protocol v4 ToClient")
		};

		let body = match data.body {
			v4::ToClientBody::Init(init) => v3::ToClientBody::Init(v3::Init {
				actor_id: init.actor_id,
				connection_id: init.connection_id,
			}),
			v4::ToClientBody::Error(error) => v3::ToClientBody::Error(v3::Error {
				group: error.group,
				code: error.code,
				message: error.message,
				metadata: error.metadata,
				action_id: error.action_id,
			}),
			v4::ToClientBody::ActionResponse(response) => {
				v3::ToClientBody::ActionResponse(v3::ActionResponse {
					id: response.id,
					output: response.output,
				})
			}
			v4::ToClientBody::Event(event) => v3::ToClientBody::Event(v3::Event {
				name: event.name,
				args: event.args,
			}),
		};

		Ok(Self::V3(v3::ToClient { body }))
	}

	fn v3_to_v2(self) -> Result<Self> {
		let Self::V3(data) = self else {
			bail!("expected client protocol v3 ToClient")
		};
		Ok(Self::V2(data.into()))
	}

	fn v2_to_v1(self) -> Result<Self> {
		let Self::V2(data) = self else {
			bail!("expected client protocol v2 ToClient")
		};

		let body = match data.body {
			v2::ToClientBody::Init(init) => v1::ToClientBody::Init(v1::Init {
				actor_id: init.actor_id,
				connection_id: init.connection_id,
				connection_token: String::new(),
			}),
			v2::ToClientBody::Error(error) => v1::ToClientBody::Error(v1::Error {
				group: error.group,
				code: error.code,
				message: error.message,
				metadata: error.metadata,
				action_id: error.action_id,
			}),
			v2::ToClientBody::ActionResponse(response) => {
				v1::ToClientBody::ActionResponse(v1::ActionResponse {
					id: response.id,
					output: response.output,
				})
			}
			v2::ToClientBody::Event(event) => v1::ToClientBody::Event(v1::Event {
				name: event.name,
				args: event.args,
			}),
		};

		Ok(Self::V1(v1::ToClient { body }))
	}
}

macro_rules! impl_same_fields_pair {
	($left:ident, $right:ident, $ty:ident { $($field:ident),+ $(,)? }) => {
		impl From<$left::$ty> for $right::$ty {
			fn from(value: $left::$ty) -> Self {
				Self {
					$($field: value.$field),+
				}
			}
		}

		impl From<$right::$ty> for $left::$ty {
			fn from(value: $right::$ty) -> Self {
				Self {
					$($field: value.$field),+
				}
			}
		}
	};
}

macro_rules! impl_to_server_pair {
	($left:ident, $right:ident) => {
		impl From<$left::ToServerBody> for $right::ToServerBody {
			fn from(value: $left::ToServerBody) -> Self {
				match value {
					$left::ToServerBody::ActionRequest(request) => {
						Self::ActionRequest(request.into())
					}
					$left::ToServerBody::SubscriptionRequest(request) => {
						Self::SubscriptionRequest(request.into())
					}
				}
			}
		}

		impl From<$right::ToServerBody> for $left::ToServerBody {
			fn from(value: $right::ToServerBody) -> Self {
				match value {
					$right::ToServerBody::ActionRequest(request) => {
						Self::ActionRequest(request.into())
					}
					$right::ToServerBody::SubscriptionRequest(request) => {
						Self::SubscriptionRequest(request.into())
					}
				}
			}
		}

		impl From<$left::ToServer> for $right::ToServer {
			fn from(value: $left::ToServer) -> Self {
				Self {
					body: value.body.into(),
				}
			}
		}

		impl From<$right::ToServer> for $left::ToServer {
			fn from(value: $right::ToServer) -> Self {
				Self {
					body: value.body.into(),
				}
			}
		}
	};
}

macro_rules! impl_common_pair {
	($left:ident, $right:ident) => {
		impl_same_fields_pair!($left, $right, ActionRequest { id, name, args });
		impl_same_fields_pair!(
			$left,
			$right,
			SubscriptionRequest {
				event_name,
				subscribe,
			}
		);
		impl_to_server_pair!($left, $right);
		impl_same_fields_pair!($left, $right, HttpActionRequest { args });
		impl_same_fields_pair!($left, $right, HttpActionResponse { output });
		impl_same_fields_pair!($left, $right, HttpResolveResponse { actor_id });
	};
}

macro_rules! impl_to_client_v2_v3_pair {
	() => {
		impl_same_fields_pair!(
			v2,
			v3,
			Init {
				actor_id,
				connection_id,
			}
		);
		impl_same_fields_pair!(
			v2,
			v3,
			Error {
				group,
				code,
				message,
				metadata,
				action_id,
			}
		);
		impl_same_fields_pair!(v2, v3, ActionResponse { id, output });
		impl_same_fields_pair!(v2, v3, Event { name, args });

		impl From<v2::ToClientBody> for v3::ToClientBody {
			fn from(value: v2::ToClientBody) -> Self {
				match value {
					v2::ToClientBody::Init(init) => Self::Init(init.into()),
					v2::ToClientBody::Error(error) => Self::Error(error.into()),
					v2::ToClientBody::ActionResponse(response) => {
						Self::ActionResponse(response.into())
					}
					v2::ToClientBody::Event(event) => Self::Event(event.into()),
				}
			}
		}

		impl From<v3::ToClientBody> for v2::ToClientBody {
			fn from(value: v3::ToClientBody) -> Self {
				match value {
					v3::ToClientBody::Init(init) => Self::Init(init.into()),
					v3::ToClientBody::Error(error) => Self::Error(error.into()),
					v3::ToClientBody::ActionResponse(response) => {
						Self::ActionResponse(response.into())
					}
					v3::ToClientBody::Event(event) => Self::Event(event.into()),
				}
			}
		}

		impl From<v2::ToClient> for v3::ToClient {
			fn from(value: v2::ToClient) -> Self {
				Self {
					body: value.body.into(),
				}
			}
		}

		impl From<v3::ToClient> for v2::ToClient {
			fn from(value: v3::ToClient) -> Self {
				Self {
					body: value.body.into(),
				}
			}
		}
	};
}

impl_common_pair!(v1, v2);
impl_common_pair!(v2, v3);
impl_common_pair!(v3, v4);
impl_to_client_v2_v3_pair!();
impl_same_fields_pair!(
	v1,
	v2,
	HttpResponseError {
		group,
		code,
		message,
		metadata,
	}
);
impl_same_fields_pair!(
	v2,
	v3,
	HttpResponseError {
		group,
		code,
		message,
		metadata,
	}
);
impl_same_fields_pair!(
	v3,
	v4,
	HttpQueueSendRequest {
		body,
		name,
		wait,
		timeout,
	}
);
impl_same_fields_pair!(v3, v4, HttpQueueSendResponse { status, response });

macro_rules! impl_versioned_manual {
	($name:ident, $latest_ty:path, $v1_ty:path, $v2_ty:path, $v3_ty:path, $v4_ty:path) => {
		pub enum $name {
			V1($v1_ty),
			V2($v2_ty),
			V3($v3_ty),
			V4($v4_ty),
		}

		impl OwnedVersionedData for $name {
			type Latest = $latest_ty;

			fn wrap_latest(latest: Self::Latest) -> Self {
				Self::V4(latest)
			}

			fn unwrap_latest(self) -> Result<Self::Latest> {
				match self {
					Self::V4(data) => Ok(data),
					_ => bail!("version not latest"),
				}
			}

			fn deserialize_version(payload: &[u8], version: u16) -> Result<Self> {
				match version {
					1 => Ok(Self::V1(serde_bare::from_slice(payload)?)),
					2 => Ok(Self::V2(serde_bare::from_slice(payload)?)),
					3 => Ok(Self::V3(serde_bare::from_slice(payload)?)),
					4 => Ok(Self::V4(serde_bare::from_slice(payload)?)),
					_ => bail!(
						"invalid client protocol version for {}: {version}",
						stringify!($name)
					),
				}
			}

			fn serialize_version(self, version: u16) -> Result<Vec<u8>> {
				match (self, version) {
					(Self::V1(data), 1) => serde_bare::to_vec(&data).map_err(Into::into),
					(Self::V2(data), 2) => serde_bare::to_vec(&data).map_err(Into::into),
					(Self::V3(data), 3) => serde_bare::to_vec(&data).map_err(Into::into),
					(Self::V4(data), 4) => serde_bare::to_vec(&data).map_err(Into::into),
					(_, version) => bail!(
						"unexpected client protocol version for {}: {version}",
						stringify!($name)
					),
				}
			}

			fn deserialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
				vec![Self::v1_to_v2, Self::v2_to_v3, Self::v3_to_v4]
			}

			fn serialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
				vec![Self::v4_to_v3, Self::v3_to_v2, Self::v2_to_v1]
			}
		}

		impl $name {
			fn v1_to_v2(self) -> Result<Self> {
				let Self::V1(data) = self else {
					bail!("expected client protocol v1 {}", stringify!($name))
				};
				Ok(Self::V2(data.into()))
			}

			fn v2_to_v3(self) -> Result<Self> {
				let Self::V2(data) = self else {
					bail!("expected client protocol v2 {}", stringify!($name))
				};
				Ok(Self::V3(data.into()))
			}

			fn v3_to_v4(self) -> Result<Self> {
				let Self::V3(data) = self else {
					bail!("expected client protocol v3 {}", stringify!($name))
				};
				Ok(Self::V4(data.into()))
			}

			fn v4_to_v3(self) -> Result<Self> {
				let Self::V4(data) = self else {
					bail!("expected client protocol v4 {}", stringify!($name))
				};
				Ok(Self::V3(data.into()))
			}

			fn v3_to_v2(self) -> Result<Self> {
				let Self::V3(data) = self else {
					bail!("expected client protocol v3 {}", stringify!($name))
				};
				Ok(Self::V2(data.into()))
			}

			fn v2_to_v1(self) -> Result<Self> {
				let Self::V2(data) = self else {
					bail!("expected client protocol v2 {}", stringify!($name))
				};
				Ok(Self::V1(data.into()))
			}
		}
	};
}

macro_rules! impl_versioned_v3_only {
	($name:ident, $latest_ty:path) => {
		pub enum $name {
			V3(v3::$name),
			V4($latest_ty),
		}

		impl OwnedVersionedData for $name {
			type Latest = $latest_ty;

			fn wrap_latest(latest: Self::Latest) -> Self {
				Self::V4(latest)
			}

			fn unwrap_latest(self) -> Result<Self::Latest> {
				match self {
					Self::V4(data) => Ok(data),
					_ => bail!("version not latest"),
				}
			}

			fn deserialize_version(payload: &[u8], version: u16) -> Result<Self> {
				match version {
					3 => Ok(Self::V3(serde_bare::from_slice(payload)?)),
					4 => Ok(Self::V4(serde_bare::from_slice(payload)?)),
					_ => bail!(
						"{} only exists in client protocol v3, got {version}",
						stringify!($name)
					),
				}
			}

			fn serialize_version(self, version: u16) -> Result<Vec<u8>> {
				match (self, version) {
					(Self::V3(data), 3) => serde_bare::to_vec(&data).map_err(Into::into),
					(Self::V4(data), 4) => serde_bare::to_vec(&data).map_err(Into::into),
					(_, version) => bail!(
						"{} only exists in client protocol v3, got {version}",
						stringify!($name)
					),
				}
			}

			fn deserialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
				vec![Ok, Ok, Self::v3_to_v4]
			}

			fn serialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
				vec![Self::v4_to_v3, Ok, Ok]
			}
		}

		impl $name {
			fn v3_to_v4(self) -> Result<Self> {
				let Self::V3(data) = self else {
					bail!("expected client protocol v3 {}", stringify!($name))
				};
				Ok(Self::V4(data.into()))
			}

			fn v4_to_v3(self) -> Result<Self> {
				let Self::V4(data) = self else {
					bail!("expected client protocol v4 {}", stringify!($name))
				};
				Ok(Self::V3(data.into()))
			}
		}
	};
}

impl_versioned_manual!(
	ToServer,
	v4::ToServer,
	v1::ToServer,
	v2::ToServer,
	v3::ToServer,
	v4::ToServer
);
impl_versioned_manual!(
	HttpActionRequest,
	v4::HttpActionRequest,
	v1::HttpActionRequest,
	v2::HttpActionRequest,
	v3::HttpActionRequest,
	v4::HttpActionRequest
);
impl_versioned_manual!(
	HttpActionResponse,
	v4::HttpActionResponse,
	v1::HttpActionResponse,
	v2::HttpActionResponse,
	v3::HttpActionResponse,
	v4::HttpActionResponse
);
impl_versioned_manual!(
	HttpResolveResponse,
	v4::HttpResolveResponse,
	v1::HttpResolveResponse,
	v2::HttpResolveResponse,
	v3::HttpResolveResponse,
	v4::HttpResolveResponse
);
impl_versioned_v3_only!(HttpQueueSendRequest, v4::HttpQueueSendRequest);
impl_versioned_v3_only!(HttpQueueSendResponse, v4::HttpQueueSendResponse);

pub enum HttpResponseError {
	V1(v1::HttpResponseError),
	V2(v2::HttpResponseError),
	V3(v3::HttpResponseError),
	V4(v4::HttpResponseError),
}

impl OwnedVersionedData for HttpResponseError {
	type Latest = v4::HttpResponseError;

	fn wrap_latest(latest: Self::Latest) -> Self {
		Self::V4(latest)
	}

	fn unwrap_latest(self) -> Result<Self::Latest> {
		match self {
			Self::V4(data) => Ok(data),
			_ => bail!("version not latest"),
		}
	}

	fn deserialize_version(payload: &[u8], version: u16) -> Result<Self> {
		match version {
			1 => Ok(Self::V1(serde_bare::from_slice(payload)?)),
			2 => Ok(Self::V2(serde_bare::from_slice(payload)?)),
			3 => Ok(Self::V3(serde_bare::from_slice(payload)?)),
			4 => Ok(Self::V4(serde_bare::from_slice(payload)?)),
			_ => bail!("invalid client protocol version for HttpResponseError: {version}"),
		}
	}

	fn serialize_version(self, version: u16) -> Result<Vec<u8>> {
		match (self, version) {
			(Self::V1(data), 1) => serde_bare::to_vec(&data).map_err(Into::into),
			(Self::V2(data), 2) => serde_bare::to_vec(&data).map_err(Into::into),
			(Self::V3(data), 3) => serde_bare::to_vec(&data).map_err(Into::into),
			(Self::V4(data), 4) => serde_bare::to_vec(&data).map_err(Into::into),
			(_, version) => {
				bail!("unexpected client protocol version for HttpResponseError: {version}")
			}
		}
	}

	fn deserialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
		vec![Self::v1_to_v2, Self::v2_to_v3, Self::v3_to_v4]
	}

	fn serialize_converters() -> Vec<impl Fn(Self) -> Result<Self>> {
		vec![Self::v4_to_v3, Self::v3_to_v2, Self::v2_to_v1]
	}
}

impl HttpResponseError {
	fn v1_to_v2(self) -> Result<Self> {
		let Self::V1(data) = self else {
			bail!("expected client protocol v1 HttpResponseError")
		};
		Ok(Self::V2(data.into()))
	}

	fn v2_to_v3(self) -> Result<Self> {
		let Self::V2(data) = self else {
			bail!("expected client protocol v2 HttpResponseError")
		};
		Ok(Self::V3(data.into()))
	}

	fn v3_to_v4(self) -> Result<Self> {
		let Self::V3(data) = self else {
			bail!("expected client protocol v3 HttpResponseError")
		};
		Ok(Self::V4(v4::HttpResponseError {
			group: data.group,
			code: data.code,
			message: data.message,
			metadata: data.metadata,
			actor: None,
		}))
	}

	fn v4_to_v3(self) -> Result<Self> {
		let Self::V4(data) = self else {
			bail!("expected client protocol v4 HttpResponseError")
		};
		Ok(Self::V3(v3::HttpResponseError {
			group: data.group,
			code: data.code,
			message: data.message,
			metadata: data.metadata,
		}))
	}

	fn v3_to_v2(self) -> Result<Self> {
		let Self::V3(data) = self else {
			bail!("expected client protocol v3 HttpResponseError")
		};
		Ok(Self::V2(data.into()))
	}

	fn v2_to_v1(self) -> Result<Self> {
		let Self::V2(data) = self else {
			bail!("expected client protocol v2 HttpResponseError")
		};
		Ok(Self::V1(data.into()))
	}
}
