use axum::{
	Extension, Router,
	extract::{Query, State},
	response::Redirect,
	routing::get,
};
use conduwuit_core::utils::{IterStream, ReadyExt, stream::TryExpect};
use conduwuit_service::threepid::EmailRequirement;
use futures::StreamExt;
use ruma::{
	OwnedClientSecret, OwnedDeviceId, OwnedSessionId,
	api::client::discovery::get_authorization_server_metadata::v1::AccountManagementAction,
};
use serde::{Deserialize, Serialize};

use crate::{
	WebError,
	extract::Expect,
	pages::{
		Result, TemplateContext,
		components::{DeviceCard, DeviceCardStyle, UserCard},
	},
	response,
	session::{LoginTarget, User},
	template,
};

pub(crate) mod cross_signing_reset;
pub(crate) mod deactivate;
pub(crate) mod device;
pub(crate) mod email;
pub(crate) mod login;
pub(crate) mod password;
pub(crate) mod register;

pub(crate) fn build() -> Router<crate::State> {
	#[allow(clippy::wildcard_imports)]
	use self::*;

	Router::new()
		.route("/", get(get_account))
		.route("/deeplink", get(get_account_deeplink))
		.merge(login::build())
		.nest("/password/", password::build())
		.nest("/email/", email::build())
		.nest("/cross_signing_reset", cross_signing_reset::build())
		.nest("/deactivate", deactivate::build())
		.nest("/device/", device::build())
		.nest("/register/", register::build())
}

#[derive(Deserialize, Serialize)]
struct ThreepidQuery {
	client_secret: OwnedClientSecret,
	session_id: OwnedSessionId,
}

template! {
	struct Account use "account.html.j2" {
		user_card: UserCard,
		body: AccountBody
	}
}

#[derive(Debug)]
enum AccountBody {
	Unlocked {
		suspended: bool,
		oidc_enabled: bool,
		email_requirement: EmailRequirement,
		email: Option<String>,
		devices: Vec<DeviceCard>,
		dehydrated_device_id: Option<OwnedDeviceId>,
	},
	Locked,
}

async fn get_account(
	State(services): State<crate::State>,
	Extension(context): Extension<TemplateContext>,
	user: User<true>,
) -> Result {
	let user_id = user.expect(LoginTarget::Account)?;

	let user_card = UserCard::for_local_user(&services, user_id.clone()).await;

	if services.users.is_locked(&user_id).await.unwrap() {
		return response!(Account::new(context, user_card, AccountBody::Locked));
	}

	let email_requirement = services.threepid.email_requirement();
	let email = services
		.threepid
		.get_email_for_localpart(user_id.localpart())
		.await
		.map(|address| address.to_string());

	let dehydrated_device_id = services.users.get_dehydrated_device_id(&user_id).await.ok();

	let mut devices: Vec<_> = services
		.users
		.all_device_ids(&user_id)
		.then(async |device_id| {
			services
				.users
				.get_device_metadata(&user_id, &device_id)
				.await
		})
		.expect_ok()
		.ready_filter(|device| {
			dehydrated_device_id
				.as_ref()
				.is_none_or(|id| device.device_id != *id)
		})
		.collect()
		.await;

	devices.sort_unstable_by(|a, b| a.last_seen_ts.cmp(&b.last_seen_ts).reverse());

	let device_cards = devices
		.into_iter()
		.stream()
		.then(async |device| {
			DeviceCard::for_device(&services, &user_id, device, DeviceCardStyle::Minimal).await
		})
		.collect()
		.await;

	let suspended = services.users.is_suspended(&user_id).await.unwrap();

	response!(Account::new(context, user_card, AccountBody::Unlocked {
		suspended,
		oidc_enabled: services.oidc.enabled(),
		email_requirement,
		email,
		devices: device_cards,
		dehydrated_device_id,
	}))
}

#[derive(Deserialize)]
struct AccountDeeplinkQuery {
	action: Option<AccountManagementAction>,
	device_id: Option<OwnedDeviceId>,
}

async fn get_account_deeplink(
	Expect(Query(query)): Expect<Query<AccountDeeplinkQuery>>,
) -> Result {
	let redirect_target = match query.action.unwrap_or(AccountManagementAction::Profile) {
		| AccountManagementAction::AccountDeactivate => "deactivate".to_owned(),
		| AccountManagementAction::CrossSigningReset => "cross_signing_reset".to_owned(),
		| AccountManagementAction::DeviceDelete => {
			let Some(device_id) = query.device_id else {
				return response!(WebError::BadRequest(
					"A device ID is required for this action".to_owned()
				));
			};

			format!("device/{device_id}/remove")
		},
		| AccountManagementAction::DeviceView => {
			let Some(device_id) = query.device_id else {
				return response!(WebError::BadRequest(
					"A device ID is required for this action".to_owned()
				));
			};

			format!("device/{device_id}/")
		},
		| AccountManagementAction::DevicesList => "#devices".to_owned(),
		| AccountManagementAction::Profile => String::new(),
		| _ => return response!(WebError::BadRequest("Unknown action".to_owned())),
	};

	response!(Redirect::to(&format!("{}/account/{}", crate::ROUTE_PREFIX, redirect_target)))
}
