use std::{cmp::max, collections::BTreeMap};

use axum::extract::State;
use conduit::{debug_info, debug_warn, err};
use ruma::{
	api::client::{
		error::ErrorKind,
		room::{self, aliases, create_room, get_room_event, upgrade_room},
	},
	events::{
		room::{
			canonical_alias::RoomCanonicalAliasEventContent,
			create::RoomCreateEventContent,
			guest_access::{GuestAccess, RoomGuestAccessEventContent},
			history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
			join_rules::{JoinRule, RoomJoinRulesEventContent},
			member::{MembershipState, RoomMemberEventContent},
			name::RoomNameEventContent,
			power_levels::RoomPowerLevelsEventContent,
			tombstone::RoomTombstoneEventContent,
			topic::RoomTopicEventContent,
		},
		StateEventType, TimelineEventType,
	},
	int,
	serde::{JsonObject, Raw},
	CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId,
};
use serde_json::{json, value::to_raw_value};
use tracing::{error, info, warn};

use super::invite_helper;
use crate::{
	service::{appservice::RegistrationInfo, pdu::PduBuilder, Services},
	Error, Result, Ruma,
};

/// Recommended transferable state events list from the spec
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
	StateEventType::RoomServerAcl,
	StateEventType::RoomEncryption,
	StateEventType::RoomName,
	StateEventType::RoomAvatar,
	StateEventType::RoomTopic,
	StateEventType::RoomGuestAccess,
	StateEventType::RoomHistoryVisibility,
	StateEventType::RoomJoinRules,
	StateEventType::RoomPowerLevels,
];

/// # `POST /_matrix/client/v3/createRoom`
///
/// Creates a new room.
///
/// - Room ID is randomly generated
/// - Create alias if `room_alias_name` is set
/// - Send create event
/// - Join sender user
/// - Send power levels event
/// - Send canonical room alias
/// - Send join rules
/// - Send history visibility
/// - Send guest access
/// - Send events listed in initial state
/// - Send events implied by `name` and `topic`
/// - Send invite events
pub(crate) async fn create_room_route(
	State(services): State<crate::State>, body: Ruma<create_room::v3::Request>,
) -> Result<create_room::v3::Response> {
	use create_room::v3::RoomPreset;

	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	if !services.globals.allow_room_creation()
		&& body.appservice_info.is_none()
		&& !services.users.is_admin(sender_user)?
	{
		return Err(Error::BadRequest(ErrorKind::forbidden(), "Room creation has been disabled."));
	}

	let room_id: OwnedRoomId = if let Some(custom_room_id) = &body.room_id {
		custom_room_id_check(&services, custom_room_id)?
	} else {
		RoomId::new(&services.globals.config.server_name)
	};

	// check if room ID doesn't already exist instead of erroring on auth check
	if services.rooms.short.get_shortroomid(&room_id)?.is_some() {
		return Err(Error::BadRequest(
			ErrorKind::RoomInUse,
			"Room with that custom room ID already exists",
		));
	}

	let _short_id = services.rooms.short.get_or_create_shortroomid(&room_id)?;
	let state_lock = services.rooms.state.mutex.lock(&room_id).await;

	let alias: Option<OwnedRoomAliasId> = if let Some(alias) = &body.room_alias_name {
		Some(room_alias_check(&services, alias, &body.appservice_info).await?)
	} else {
		None
	};

	let room_version = match body.room_version.clone() {
		Some(room_version) => {
			if services
				.globals
				.supported_room_versions()
				.contains(&room_version)
			{
				room_version
			} else {
				return Err(Error::BadRequest(
					ErrorKind::UnsupportedRoomVersion,
					"This server does not support that room version.",
				));
			}
		},
		None => services.globals.default_room_version(),
	};

	let content = match &body.creation_content {
		Some(content) => {
			use RoomVersionId::*;

			let mut content = content
				.deserialize_as::<CanonicalJsonObject>()
				.map_err(|e| {
					error!("Failed to deserialise content as canonical JSON: {}", e);
					Error::bad_database("Failed to deserialise content as canonical JSON.")
				})?;
			match room_version {
				V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
					content.insert(
						"creator".into(),
						json!(&sender_user).try_into().map_err(|e| {
							info!("Invalid creation content: {e}");
							Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
						})?,
					);
				},
				_ => {
					// V11+ removed the "creator" key
				},
			}
			content.insert(
				"room_version".into(),
				json!(room_version.as_str())
					.try_into()
					.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?,
			);
			content
		},
		None => {
			use RoomVersionId::*;

			let content = match room_version {
				V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => RoomCreateEventContent::new_v1(sender_user.clone()),
				_ => RoomCreateEventContent::new_v11(),
			};
			let mut content = serde_json::from_str::<CanonicalJsonObject>(
				to_raw_value(&content)
					.expect("we just created this as content was None")
					.get(),
			)
			.unwrap();
			content.insert(
				"room_version".into(),
				json!(room_version.as_str())
					.try_into()
					.expect("we just created this as content was None"),
			);
			content
		},
	};

	// 1. The room create event
	services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomCreate,
				content: to_raw_value(&content).expect("event is valid, we just created it"),
				unsigned: None,
				state_key: Some(String::new()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&room_id,
			&state_lock,
		)
		.await?;

	// 2. Let the room creator join
	services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomMember,
				content: to_raw_value(&RoomMemberEventContent {
					membership: MembershipState::Join,
					displayname: services.users.displayname(sender_user)?,
					avatar_url: services.users.avatar_url(sender_user)?,
					is_direct: Some(body.is_direct),
					third_party_invite: None,
					blurhash: services.users.blurhash(sender_user)?,
					reason: None,
					join_authorized_via_users_server: None,
				})
				.expect("event is valid, we just created it"),
				unsigned: None,
				state_key: Some(sender_user.to_string()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&room_id,
			&state_lock,
		)
		.await?;

	// 3. Power levels

	// Figure out preset. We need it for preset specific events
	let preset = body.preset.clone().unwrap_or(match &body.visibility {
		room::Visibility::Public => RoomPreset::PublicChat,
		_ => RoomPreset::PrivateChat, // Room visibility should not be custom
	});

	let mut users = BTreeMap::new();
	users.insert(sender_user.clone(), int!(100));

	if preset == RoomPreset::TrustedPrivateChat {
		for invite_ in &body.invite {
			users.insert(invite_.clone(), int!(100));
		}
	}

	let power_levels_content =
		default_power_levels_content(&body.power_level_content_override, &body.visibility, users)?;

	services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomPowerLevels,
				content: to_raw_value(&power_levels_content).expect("to_raw_value always works on serde_json::Value"),
				unsigned: None,
				state_key: Some(String::new()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&room_id,
			&state_lock,
		)
		.await?;

	// 4. Canonical room alias
	if let Some(room_alias_id) = &alias {
		services
			.rooms
			.timeline
			.build_and_append_pdu(
				PduBuilder {
					event_type: TimelineEventType::RoomCanonicalAlias,
					content: to_raw_value(&RoomCanonicalAliasEventContent {
						alias: Some(room_alias_id.to_owned()),
						alt_aliases: vec![],
					})
					.expect("We checked that alias earlier, it must be fine"),
					unsigned: None,
					state_key: Some(String::new()),
					redacts: None,
					timestamp: None,
				},
				sender_user,
				&room_id,
				&state_lock,
			)
			.await?;
	}

	// 5. Events set by preset

	// 5.1 Join Rules
	services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomJoinRules,
				content: to_raw_value(&RoomJoinRulesEventContent::new(match preset {
					RoomPreset::PublicChat => JoinRule::Public,
					// according to spec "invite" is the default
					_ => JoinRule::Invite,
				}))
				.expect("event is valid, we just created it"),
				unsigned: None,
				state_key: Some(String::new()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&room_id,
			&state_lock,
		)
		.await?;

	// 5.2 History Visibility
	services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomHistoryVisibility,
				content: to_raw_value(&RoomHistoryVisibilityEventContent::new(HistoryVisibility::Shared))
					.expect("event is valid, we just created it"),
				unsigned: None,
				state_key: Some(String::new()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&room_id,
			&state_lock,
		)
		.await?;

	// 5.3 Guest Access
	services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomGuestAccess,
				content: to_raw_value(&RoomGuestAccessEventContent::new(match preset {
					RoomPreset::PublicChat => GuestAccess::Forbidden,
					_ => GuestAccess::CanJoin,
				}))
				.expect("event is valid, we just created it"),
				unsigned: None,
				state_key: Some(String::new()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&room_id,
			&state_lock,
		)
		.await?;

	// 6. Events listed in initial_state
	for event in &body.initial_state {
		let mut pdu_builder = event.deserialize_as::<PduBuilder>().map_err(|e| {
			warn!("Invalid initial state event: {:?}", e);
			Error::BadRequest(ErrorKind::InvalidParam, "Invalid initial state event.")
		})?;

		debug_info!("Room creation initial state event: {event:?}");

		// client/appservice workaround: if a user sends an initial_state event with a
		// state event in there with the content of literally `{}` (not null or empty
		// string), let's just skip it over and warn.
		if pdu_builder.content.get().eq("{}") {
			info!("skipping empty initial state event with content of `{{}}`: {event:?}");
			debug_warn!("content: {}", pdu_builder.content.get());
			continue;
		}

		// Implicit state key defaults to ""
		pdu_builder.state_key.get_or_insert_with(String::new);

		// Silently skip encryption events if they are not allowed
		if pdu_builder.event_type == TimelineEventType::RoomEncryption && !services.globals.allow_encryption() {
			continue;
		}

		services
			.rooms
			.timeline
			.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
			.await?;
	}

	// 7. Events implied by name and topic
	if let Some(name) = &body.name {
		services
			.rooms
			.timeline
			.build_and_append_pdu(
				PduBuilder {
					event_type: TimelineEventType::RoomName,
					content: to_raw_value(&RoomNameEventContent::new(name.clone()))
						.expect("event is valid, we just created it"),
					unsigned: None,
					state_key: Some(String::new()),
					redacts: None,
					timestamp: None,
				},
				sender_user,
				&room_id,
				&state_lock,
			)
			.await?;
	}

	if let Some(topic) = &body.topic {
		services
			.rooms
			.timeline
			.build_and_append_pdu(
				PduBuilder {
					event_type: TimelineEventType::RoomTopic,
					content: to_raw_value(&RoomTopicEventContent {
						topic: topic.clone(),
					})
					.expect("event is valid, we just created it"),
					unsigned: None,
					state_key: Some(String::new()),
					redacts: None,
					timestamp: None,
				},
				sender_user,
				&room_id,
				&state_lock,
			)
			.await?;
	}

	// 8. Events implied by invite (and TODO: invite_3pid)
	drop(state_lock);
	for user_id in &body.invite {
		if let Err(e) = invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct).await {
			warn!(%e, "Failed to send invite");
		}
	}

	// Homeserver specific stuff
	if let Some(alias) = alias {
		services
			.rooms
			.alias
			.set_alias(&alias, &room_id, sender_user)?;
	}

	if body.visibility == room::Visibility::Public {
		services.rooms.directory.set_public(&room_id)?;
	}

	info!("{sender_user} created a room with room ID {room_id}");

	Ok(create_room::v3::Response::new(room_id))
}

/// # `GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}`
///
/// Gets a single event.
///
/// - You have to currently be joined to the room (TODO: Respect history
///   visibility)
pub(crate) async fn get_room_event_route(
	State(services): State<crate::State>, body: Ruma<get_room_event::v3::Request>,
) -> Result<get_room_event::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	let event = services
		.rooms
		.timeline
		.get_pdu(&body.event_id)?
		.ok_or_else(|| err!(Request(NotFound("Event {} not found.", &body.event_id))))?;

	if !services
		.rooms
		.state_accessor
		.user_can_see_event(sender_user, &event.room_id, &body.event_id)?
	{
		return Err(Error::BadRequest(
			ErrorKind::forbidden(),
			"You don't have permission to view this event.",
		));
	}

	let mut event = (*event).clone();
	event.add_age()?;

	Ok(get_room_event::v3::Response {
		event: event.to_room_event(),
	})
}

/// # `GET /_matrix/client/r0/rooms/{roomId}/aliases`
///
/// Lists all aliases of the room.
///
/// - Only users joined to the room are allowed to call this, or if
///   `history_visibility` is world readable in the room
pub(crate) async fn get_room_aliases_route(
	State(services): State<crate::State>, body: Ruma<aliases::v3::Request>,
) -> Result<aliases::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	if !services
		.rooms
		.state_accessor
		.user_can_see_state_events(sender_user, &body.room_id)?
	{
		return Err(Error::BadRequest(
			ErrorKind::forbidden(),
			"You don't have permission to view this room.",
		));
	}

	Ok(aliases::v3::Response {
		aliases: services
			.rooms
			.alias
			.local_aliases_for_room(&body.room_id)
			.filter_map(Result::ok)
			.collect(),
	})
}

/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
///
/// Upgrades the room.
///
/// - Creates a replacement room
/// - Sends a tombstone event into the current room
/// - Sender user joins the room
/// - Transfers some state events
/// - Moves local aliases
/// - Modifies old room power levels to prevent users from speaking
pub(crate) async fn upgrade_room_route(
	State(services): State<crate::State>, body: Ruma<upgrade_room::v3::Request>,
) -> Result<upgrade_room::v3::Response> {
	let sender_user = body.sender_user.as_ref().expect("user is authenticated");

	if !services
		.globals
		.supported_room_versions()
		.contains(&body.new_version)
	{
		return Err(Error::BadRequest(
			ErrorKind::UnsupportedRoomVersion,
			"This server does not support that room version.",
		));
	}

	// Create a replacement room
	let replacement_room = RoomId::new(services.globals.server_name());

	let _short_id = services
		.rooms
		.short
		.get_or_create_shortroomid(&replacement_room)?;

	let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;

	// Send a m.room.tombstone event to the old room to indicate that it is not
	// intended to be used any further Fail if the sender does not have the required
	// permissions
	let tombstone_event_id = services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomTombstone,
				content: to_raw_value(&RoomTombstoneEventContent {
					body: "This room has been replaced".to_owned(),
					replacement_room: replacement_room.clone(),
				})
				.expect("event is valid, we just created it"),
				unsigned: None,
				state_key: Some(String::new()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&body.room_id,
			&state_lock,
		)
		.await?;

	// Change lock to replacement room
	drop(state_lock);
	let state_lock = services.rooms.state.mutex.lock(&replacement_room).await;

	// Get the old room creation event
	let mut create_event_content = serde_json::from_str::<CanonicalJsonObject>(
		services
			.rooms
			.state_accessor
			.room_state_get(&body.room_id, &StateEventType::RoomCreate, "")?
			.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
			.content
			.get(),
	)
	.map_err(|_| Error::bad_database("Invalid room event in database."))?;

	// Use the m.room.tombstone event as the predecessor
	let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
		body.room_id.clone(),
		(*tombstone_event_id).to_owned(),
	));

	// Send a m.room.create event containing a predecessor field and the applicable
	// room_version
	{
		use RoomVersionId::*;
		match body.new_version {
			V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
				create_event_content.insert(
					"creator".into(),
					json!(&sender_user).try_into().map_err(|e| {
						info!("Error forming creation event: {e}");
						Error::BadRequest(ErrorKind::BadJson, "Error forming creation event")
					})?,
				);
			},
			_ => {
				// "creator" key no longer exists in V11+ rooms
				create_event_content.remove("creator");
			},
		}
	}

	create_event_content.insert(
		"room_version".into(),
		json!(&body.new_version)
			.try_into()
			.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
	);
	create_event_content.insert(
		"predecessor".into(),
		json!(predecessor)
			.try_into()
			.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?,
	);

	// Validate creation event content
	if serde_json::from_str::<CanonicalJsonObject>(
		to_raw_value(&create_event_content)
			.expect("Error forming creation event")
			.get(),
	)
	.is_err()
	{
		return Err(Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"));
	}

	services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomCreate,
				content: to_raw_value(&create_event_content).expect("event is valid, we just created it"),
				unsigned: None,
				state_key: Some(String::new()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&replacement_room,
			&state_lock,
		)
		.await?;

	// Join the new room
	services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomMember,
				content: to_raw_value(&RoomMemberEventContent {
					membership: MembershipState::Join,
					displayname: services.users.displayname(sender_user)?,
					avatar_url: services.users.avatar_url(sender_user)?,
					is_direct: None,
					third_party_invite: None,
					blurhash: services.users.blurhash(sender_user)?,
					reason: None,
					join_authorized_via_users_server: None,
				})
				.expect("event is valid, we just created it"),
				unsigned: None,
				state_key: Some(sender_user.to_string()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&replacement_room,
			&state_lock,
		)
		.await?;

	// Replicate transferable state events to the new room
	for event_type in TRANSFERABLE_STATE_EVENTS {
		let event_content = match services
			.rooms
			.state_accessor
			.room_state_get(&body.room_id, event_type, "")?
		{
			Some(v) => v.content.clone(),
			None => continue, // Skipping missing events.
		};

		services
			.rooms
			.timeline
			.build_and_append_pdu(
				PduBuilder {
					event_type: event_type.to_string().into(),
					content: event_content,
					unsigned: None,
					state_key: Some(String::new()),
					redacts: None,
					timestamp: None,
				},
				sender_user,
				&replacement_room,
				&state_lock,
			)
			.await?;
	}

	// Moves any local aliases to the new room
	for alias in services
		.rooms
		.alias
		.local_aliases_for_room(&body.room_id)
		.filter_map(Result::ok)
	{
		services
			.rooms
			.alias
			.remove_alias(&alias, sender_user)
			.await?;
		services
			.rooms
			.alias
			.set_alias(&alias, &replacement_room, sender_user)?;
	}

	// Get the old room power levels
	let mut power_levels_event_content: RoomPowerLevelsEventContent = serde_json::from_str(
		services
			.rooms
			.state_accessor
			.room_state_get(&body.room_id, &StateEventType::RoomPowerLevels, "")?
			.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
			.content
			.get(),
	)
	.map_err(|_| Error::bad_database("Invalid room event in database."))?;

	// Setting events_default and invite to the greater of 50 and users_default + 1
	let new_level = max(
		int!(50),
		power_levels_event_content
			.users_default
			.checked_add(int!(1))
			.ok_or_else(|| {
				Error::BadRequest(ErrorKind::BadJson, "users_default power levels event content is not valid")
			})?,
	);
	power_levels_event_content.events_default = new_level;
	power_levels_event_content.invite = new_level;

	// Modify the power levels in the old room to prevent sending of events and
	// inviting new users
	services
		.rooms
		.timeline
		.build_and_append_pdu(
			PduBuilder {
				event_type: TimelineEventType::RoomPowerLevels,
				content: to_raw_value(&power_levels_event_content).expect("event is valid, we just created it"),
				unsigned: None,
				state_key: Some(String::new()),
				redacts: None,
				timestamp: None,
			},
			sender_user,
			&body.room_id,
			&state_lock,
		)
		.await?;

	drop(state_lock);

	// Return the replacement room id
	Ok(upgrade_room::v3::Response {
		replacement_room,
	})
}

/// creates the power_levels_content for the PDU builder
fn default_power_levels_content(
	power_level_content_override: &Option<Raw<RoomPowerLevelsEventContent>>, visibility: &room::Visibility,
	users: BTreeMap<OwnedUserId, Int>,
) -> Result<serde_json::Value> {
	let mut power_levels_content = serde_json::to_value(RoomPowerLevelsEventContent {
		users,
		..Default::default()
	})
	.expect("event is valid, we just created it");

	// secure proper defaults of sensitive/dangerous permissions that moderators
	// (power level 50) should not have easy access to
	power_levels_content["events"]["m.room.power_levels"] = serde_json::to_value(100).expect("100 is valid Value");
	power_levels_content["events"]["m.room.server_acl"] = serde_json::to_value(100).expect("100 is valid Value");
	power_levels_content["events"]["m.room.tombstone"] = serde_json::to_value(100).expect("100 is valid Value");
	power_levels_content["events"]["m.room.encryption"] = serde_json::to_value(100).expect("100 is valid Value");
	power_levels_content["events"]["m.room.history_visibility"] =
		serde_json::to_value(100).expect("100 is valid Value");

	// synapse does this too. clients do not expose these permissions. it prevents
	// default users from calling public rooms, for obvious reasons.
	if *visibility == room::Visibility::Public {
		power_levels_content["events"]["m.call.invite"] = serde_json::to_value(50).expect("50 is valid Value");
		power_levels_content["events"]["org.matrix.msc3401.call"] =
			serde_json::to_value(50).expect("50 is valid Value");
		power_levels_content["events"]["org.matrix.msc3401.call.member"] =
			serde_json::to_value(50).expect("50 is valid Value");
	}

	if let Some(power_level_content_override) = power_level_content_override {
		let json: JsonObject = serde_json::from_str(power_level_content_override.json().get())
			.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override."))?;

		for (key, value) in json {
			power_levels_content[key] = value;
		}
	}

	Ok(power_levels_content)
}

/// if a room is being created with a room alias, run our checks
async fn room_alias_check(
	services: &Services, room_alias_name: &str, appservice_info: &Option<RegistrationInfo>,
) -> Result<OwnedRoomAliasId> {
	// Basic checks on the room alias validity
	if room_alias_name.contains(':') {
		return Err(Error::BadRequest(
			ErrorKind::InvalidParam,
			"Room alias contained `:` which is not allowed. Please note that this expects a localpart, not the full \
			 room alias.",
		));
	} else if room_alias_name.contains(char::is_whitespace) {
		return Err(Error::BadRequest(
			ErrorKind::InvalidParam,
			"Room alias contained spaces which is not a valid room alias.",
		));
	}

	// check if room alias is forbidden
	if services
		.globals
		.forbidden_alias_names()
		.is_match(room_alias_name)
	{
		return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias name is forbidden."));
	}

	let full_room_alias = RoomAliasId::parse(format!("#{}:{}", room_alias_name, services.globals.config.server_name))
		.map_err(|e| {
		info!("Failed to parse room alias {room_alias_name}: {e}");
		Error::BadRequest(ErrorKind::InvalidParam, "Invalid room alias specified.")
	})?;

	if services
		.rooms
		.alias
		.resolve_local_alias(&full_room_alias)?
		.is_some()
	{
		return Err(Error::BadRequest(ErrorKind::RoomInUse, "Room alias already exists."));
	}

	if let Some(ref info) = appservice_info {
		if !info.aliases.is_match(full_room_alias.as_str()) {
			return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias is not in namespace."));
		}
	} else if services
		.appservice
		.is_exclusive_alias(&full_room_alias)
		.await
	{
		return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias reserved by appservice."));
	}

	debug_info!("Full room alias: {full_room_alias}");

	Ok(full_room_alias)
}

/// if a room is being created with a custom room ID, run our checks against it
fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
	// apply forbidden room alias checks to custom room IDs too
	if services
		.globals
		.forbidden_alias_names()
		.is_match(custom_room_id)
	{
		return Err(Error::BadRequest(ErrorKind::Unknown, "Custom room ID is forbidden."));
	}

	if custom_room_id.contains(':') {
		return Err(Error::BadRequest(
			ErrorKind::InvalidParam,
			"Custom room ID contained `:` which is not allowed. Please note that this expects a localpart, not the \
			 full room ID.",
		));
	} else if custom_room_id.contains(char::is_whitespace) {
		return Err(Error::BadRequest(
			ErrorKind::InvalidParam,
			"Custom room ID contained spaces which is not valid.",
		));
	}

	let full_room_id = format!("!{}:{}", custom_room_id, services.globals.config.server_name);

	debug_info!("Full custom room ID: {full_room_id}");

	RoomId::parse(full_room_id).map_err(|e| {
		info!("User attempted to create room with custom room ID {custom_room_id} but failed parsing: {e}");
		Error::BadRequest(ErrorKind::InvalidParam, "Custom room ID could not be parsed")
	})
}