use std::collections::HashMap;

use rocket::{
	fairing::Fairing,
	outcome::try_outcome,
	request::{FromRequest, Outcome},
	serde::Serialize,
	Request,
};
use rocket_dyn_templates::{tera::try_get_value, Template};
use serde_json::{to_value, Value};

use crate::{auth::session::Session, db::schema::User, ids};

pub fn init_templates() -> impl Fairing {
	Template::custom(|engines| {
		engines
			.tera
			.register_filter("pretty_id", |v: &Value, _: &HashMap<String, Value>| {
				let value = try_get_value!("pretty_id", "value", i32, v);
				Ok(to_value(ids::id_db_to_public(value)).unwrap())
			});
	})
}

/// A bundle of all the state all pages may make use of.
///
/// Implements [`FromRequest`] to provide easy access to it as a guard.
/// This guard never fails.
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct CommonTemplateState {
	/// true if the user is logged in (doesn't check the DB, instead uses [`Session`])
	pub logged_in: bool,
	/// true if the user is an admin (defaults to false, call [`CommonTemplateState::for_user`] to add the additional data)
	pub is_admin: bool,
	/// feature flag - disables the UI for it since it's not implemeted yet.
	pub forgot_password_implemented: bool,
}

impl CommonTemplateState {
	/// Populates the state struct with the additional knowledge provided by the [`User`] passed in parameter,
	/// such as if the current context is an admin-privileges-enabled one (which need a DB lookup).
	pub fn for_user(self, user: &User) -> Self {
		CommonTemplateState {
			is_admin: user.is_admin,
			..self
		}
	}
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for CommonTemplateState {
	type Error = ();

	async fn from_request(request: &'r Request<'_>) -> Outcome<CommonTemplateState, ()> {
		let session_state = try_outcome!(request.guard::<Session>().await);

		Outcome::Success(CommonTemplateState {
			logged_in: session_state.0.is_some(),
			is_admin: false,
			forgot_password_implemented: false,
		})
	}
}

/// FakeContext exists to be used as a replacement for Context when reusing a form for creation and edition, to avoid repeating lots of code.
///
/// Note: i made this custom context thingy because i couldn't find a way to create a real context with the right populated data.
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
pub struct FakeContext {
	/// Values are made to simulate the same structure as the real Context
	pub values: HashMap<&'static str, Vec<String>>,
	/// NOP, used to placehold field errors
	pub errors: HashMap<String, ()>,
	/// NOP, used to placehold global errors
	pub form_errors: Vec<()>,
}
