use ruma::{
	api::client::{
		backup::{
			add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, create_backup_version,
			delete_backup_keys, delete_backup_keys_for_room, delete_backup_keys_for_session, delete_backup_version,
			get_backup_info, get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
			get_latest_backup_info, update_backup_version,
		},
		error::ErrorKind,
	},
	UInt,
};

use crate::{services, Error, Result, Ruma};

/// # `POST /_matrix/client/r0/room_keys/version`
///
/// Creates a new backup.
pub(crate) async fn create_backup_version_route(
	body: Ruma<create_backup_version::v3::Request>,
) -> Result<create_backup_version::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");
	let version = services()
		.key_backups
		.create_backup(sender_user, &body.algorithm)?;

	Ok(create_backup_version::v3::Response {
		version,
	})
}

/// # `PUT /_matrix/client/r0/room_keys/version/{version}`
///
/// Update information about an existing backup. Only `auth_data` can be
/// modified.
pub(crate) async fn update_backup_version_route(
	body: Ruma<update_backup_version::v3::Request>,
) -> Result<update_backup_version::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");
	services()
		.key_backups
		.update_backup(sender_user, &body.version, &body.algorithm)?;

	Ok(update_backup_version::v3::Response {})
}

/// # `GET /_matrix/client/r0/room_keys/version`
///
/// Get information about the latest backup version.
pub(crate) async fn get_latest_backup_info_route(
	body: Ruma<get_latest_backup_info::v3::Request>,
) -> Result<get_latest_backup_info::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	let (version, algorithm) = services()
		.key_backups
		.get_latest_backup(sender_user)?
		.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;

	Ok(get_latest_backup_info::v3::Response {
		algorithm,
		count: (UInt::try_from(services().key_backups.count_keys(sender_user, &version)?)
			.expect("user backup keys count should not be that high")),
		etag: services().key_backups.get_etag(sender_user, &version)?,
		version,
	})
}

/// # `GET /_matrix/client/v3/room_keys/version/{version}`
///
/// Get information about an existing backup.
pub(crate) async fn get_backup_info_route(
	body: Ruma<get_backup_info::v3::Request>,
) -> Result<get_backup_info::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");
	let algorithm = services()
		.key_backups
		.get_backup(sender_user, &body.version)?
		.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;

	Ok(get_backup_info::v3::Response {
		algorithm,
		count: (UInt::try_from(
			services()
				.key_backups
				.count_keys(sender_user, &body.version)?,
		)
		.expect("user backup keys count should not be that high")),
		etag: services()
			.key_backups
			.get_etag(sender_user, &body.version)?,
		version: body.version.clone(),
	})
}

/// # `DELETE /_matrix/client/r0/room_keys/version/{version}`
///
/// Delete an existing key backup.
///
/// - Deletes both information about the backup, as well as all key data related
///   to the backup
pub(crate) async fn delete_backup_version_route(
	body: Ruma<delete_backup_version::v3::Request>,
) -> Result<delete_backup_version::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	services()
		.key_backups
		.delete_backup(sender_user, &body.version)?;

	Ok(delete_backup_version::v3::Response {})
}

/// # `PUT /_matrix/client/r0/room_keys/keys`
///
/// Add the received backup keys to the database.
///
/// - Only manipulating the most recently created version of the backup is
///   allowed
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
pub(crate) async fn add_backup_keys_route(
	body: Ruma<add_backup_keys::v3::Request>,
) -> Result<add_backup_keys::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	if Some(&body.version)
		!= services()
			.key_backups
			.get_latest_backup_version(sender_user)?
			.as_ref()
	{
		return Err(Error::BadRequest(
			ErrorKind::InvalidParam,
			"You may only manipulate the most recently created version of the backup.",
		));
	}

	for (room_id, room) in &body.rooms {
		for (session_id, key_data) in &room.sessions {
			services()
				.key_backups
				.add_key(sender_user, &body.version, room_id, session_id, key_data)?;
		}
	}

	Ok(add_backup_keys::v3::Response {
		count: (UInt::try_from(
			services()
				.key_backups
				.count_keys(sender_user, &body.version)?,
		)
		.expect("user backup keys count should not be that high")),
		etag: services()
			.key_backups
			.get_etag(sender_user, &body.version)?,
	})
}

/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}`
///
/// Add the received backup keys to the database.
///
/// - Only manipulating the most recently created version of the backup is
///   allowed
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
pub(crate) async fn add_backup_keys_for_room_route(
	body: Ruma<add_backup_keys_for_room::v3::Request>,
) -> Result<add_backup_keys_for_room::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	if Some(&body.version)
		!= services()
			.key_backups
			.get_latest_backup_version(sender_user)?
			.as_ref()
	{
		return Err(Error::BadRequest(
			ErrorKind::InvalidParam,
			"You may only manipulate the most recently created version of the backup.",
		));
	}

	for (session_id, key_data) in &body.sessions {
		services()
			.key_backups
			.add_key(sender_user, &body.version, &body.room_id, session_id, key_data)?;
	}

	Ok(add_backup_keys_for_room::v3::Response {
		count: (UInt::try_from(
			services()
				.key_backups
				.count_keys(sender_user, &body.version)?,
		)
		.expect("user backup keys count should not be that high")),
		etag: services()
			.key_backups
			.get_etag(sender_user, &body.version)?,
	})
}

/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
///
/// Add the received backup key to the database.
///
/// - Only manipulating the most recently created version of the backup is
///   allowed
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
pub(crate) async fn add_backup_keys_for_session_route(
	body: Ruma<add_backup_keys_for_session::v3::Request>,
) -> Result<add_backup_keys_for_session::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	if Some(&body.version)
		!= services()
			.key_backups
			.get_latest_backup_version(sender_user)?
			.as_ref()
	{
		return Err(Error::BadRequest(
			ErrorKind::InvalidParam,
			"You may only manipulate the most recently created version of the backup.",
		));
	}

	services()
		.key_backups
		.add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)?;

	Ok(add_backup_keys_for_session::v3::Response {
		count: (UInt::try_from(
			services()
				.key_backups
				.count_keys(sender_user, &body.version)?,
		)
		.expect("user backup keys count should not be that high")),
		etag: services()
			.key_backups
			.get_etag(sender_user, &body.version)?,
	})
}

/// # `GET /_matrix/client/r0/room_keys/keys`
///
/// Retrieves all keys from the backup.
pub(crate) async fn get_backup_keys_route(
	body: Ruma<get_backup_keys::v3::Request>,
) -> Result<get_backup_keys::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	let rooms = services().key_backups.get_all(sender_user, &body.version)?;

	Ok(get_backup_keys::v3::Response {
		rooms,
	})
}

/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}`
///
/// Retrieves all keys from the backup for a given room.
pub(crate) async fn get_backup_keys_for_room_route(
	body: Ruma<get_backup_keys_for_room::v3::Request>,
) -> Result<get_backup_keys_for_room::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	let sessions = services()
		.key_backups
		.get_room(sender_user, &body.version, &body.room_id)?;

	Ok(get_backup_keys_for_room::v3::Response {
		sessions,
	})
}

/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
///
/// Retrieves a key from the backup.
pub(crate) async fn get_backup_keys_for_session_route(
	body: Ruma<get_backup_keys_for_session::v3::Request>,
) -> Result<get_backup_keys_for_session::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	let key_data = services()
		.key_backups
		.get_session(sender_user, &body.version, &body.room_id, &body.session_id)?
		.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Backup key not found for this user's session."))?;

	Ok(get_backup_keys_for_session::v3::Response {
		key_data,
	})
}

/// # `DELETE /_matrix/client/r0/room_keys/keys`
///
/// Delete the keys from the backup.
pub(crate) async fn delete_backup_keys_route(
	body: Ruma<delete_backup_keys::v3::Request>,
) -> Result<delete_backup_keys::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	services()
		.key_backups
		.delete_all_keys(sender_user, &body.version)?;

	Ok(delete_backup_keys::v3::Response {
		count: (UInt::try_from(
			services()
				.key_backups
				.count_keys(sender_user, &body.version)?,
		)
		.expect("user backup keys count should not be that high")),
		etag: services()
			.key_backups
			.get_etag(sender_user, &body.version)?,
	})
}

/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}`
///
/// Delete the keys from the backup for a given room.
pub(crate) async fn delete_backup_keys_for_room_route(
	body: Ruma<delete_backup_keys_for_room::v3::Request>,
) -> Result<delete_backup_keys_for_room::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	services()
		.key_backups
		.delete_room_keys(sender_user, &body.version, &body.room_id)?;

	Ok(delete_backup_keys_for_room::v3::Response {
		count: (UInt::try_from(
			services()
				.key_backups
				.count_keys(sender_user, &body.version)?,
		)
		.expect("user backup keys count should not be that high")),
		etag: services()
			.key_backups
			.get_etag(sender_user, &body.version)?,
	})
}

/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
///
/// Delete a key from the backup.
pub(crate) async fn delete_backup_keys_for_session_route(
	body: Ruma<delete_backup_keys_for_session::v3::Request>,
) -> Result<delete_backup_keys_for_session::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	services()
		.key_backups
		.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)?;

	Ok(delete_backup_keys_for_session::v3::Response {
		count: (UInt::try_from(
			services()
				.key_backups
				.count_keys(sender_user, &body.version)?,
		)
		.expect("user backup keys count should not be that high")),
		etag: services()
			.key_backups
			.get_etag(sender_user, &body.version)?,
	})
}