Skip to content
Snippets Groups Projects
  • Nicolas80's avatar
    89bde1dd
    * Re-did the mapping of DbAccountId so that we don't have to rely on derive... · 89bde1dd
    Nicolas80 authored
    * Re-did the mapping of DbAccountId so that we don't have to rely on derive macro FromJsonQueryResult that resulted in using JSON_TEXT in database column instead of VARCHAR
       * Tricky part I missed the first time was in impl of sea_orm::TryGetable::try_get_by where we should return Err TryGetError::Null in case the value is not present
       * Was otherwise throwing an exception whenever `parent` field (Option<DbAccountId>) was None when trying to persist the case in DB.
    89bde1dd
    History
    * Re-did the mapping of DbAccountId so that we don't have to rely on derive...
    Nicolas80 authored
    * Re-did the mapping of DbAccountId so that we don't have to rely on derive macro FromJsonQueryResult that resulted in using JSON_TEXT in database column instead of VARCHAR
       * Tricky part I missed the first time was in impl of sea_orm::TryGetable::try_get_by where we should return Err TryGetError::Null in case the value is not present
       * Was otherwise throwing an exception whenever `parent` field (Option<DbAccountId>) was None when trying to persist the case in DB.
vault_account.rs 39.55 KiB
use crate::commands::{cesium, vault};
use crate::runtime_config::AccountId;
use crate::utils::GcliError;
use anyhow::anyhow;
use sea_orm::entity::prelude::*;
use sea_orm::prelude::async_trait::async_trait;
use sea_orm::prelude::StringLen;
use sea_orm::ActiveValue::Set;
use sea_orm::PaginatorTrait;
use sea_orm::QueryFilter;
use sea_orm::TryGetError;
use sea_orm::{
	ActiveModelBehavior, ColumnTrait, DbErr, DeriveEntityModel, DerivePrimaryKey, EnumIter, Linked,
	ModelTrait, QueryOrder, RelationDef, RelationTrait, TryFromU64,
};
use sea_orm::{ActiveModelTrait, ConnectionTrait, PrimaryKeyTrait};
use sea_orm::{DeriveActiveEnum, EntityTrait};
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Display;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::str::FromStr;

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "vault_account")]
pub struct Model {
	/// SS58 Address of account
	#[sea_orm(primary_key, auto_increment = false)]
	pub address: DbAccountId,
	/// Optional name for the account
	#[sea_orm(unique)]
	pub name: Option<String>,
	/// derivation path - None if for a "base" account that has `crypto_scheme` and `encrypted_suri` set and no `parent`
	pub path: Option<String>,
	/// Crypto scheme used for the account - Only set for "base" accounts
	pub crypto_scheme: Option<DbCryptoScheme>,
	/// Encrypted SURI for the account - Only set for "base" accounts
	pub encrypted_suri: Option<Vec<u8>>,
	/// ForeignKey to parent vault_account SS58 Address - None if for a "base" account
	pub parent: Option<DbAccountId>,
}

impl Model {
	pub fn is_base_account(&self) -> bool {
		self.parent.is_none()
	}

	#[allow(unused)]
	pub fn is_derivation_account(&self) -> bool {
		self.parent.is_some()
	}

	pub fn account_type(&self) -> String {
		if self.is_base_account() {
			"Base".to_string()
		} else {
			"Derivation".to_string()
		}
	}
}

impl Display for Model {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		if self.is_base_account() {
			match self.crypto_scheme {
				None => {
					unreachable!()
				}
				Some(DbCryptoScheme::Ed25519) => {
					// Also showing G1v1 public key for Ed25519 (base) accounts
					write!(
						f,
						"{}[address:{}, g1v1_pub_key:{}, name:{:?}, crypto_scheme:{:?}]",
						self.account_type(),
						self.address,
						cesium::compute_g1v1_public_key_from_ed25519_account_id(&self.address.0),
						self.name,
						self.crypto_scheme
					)
				}
				Some(DbCryptoScheme::Sr25519) => {
					write!(
						f,
						"{}[address:{}, name:{:?}, crypto_scheme:{:?}]",
						self.account_type(),
						self.address,
						self.name,
						self.crypto_scheme
					)
				}
			}
		} else {
			fn get_parent_name(parent: &Option<DbAccountId>) -> String {
				if let Some(parent) = parent {
					format!("Some(\"{parent}\")")
				} else {
					"None".to_string()
				}
			}

			write!(
				f,
				"{}[address:{}, name:{:?}, path:{:?}, parent:{}]",
				self.account_type(),
				self.address,
				self.name,
				self.path,
				get_parent_name(&self.parent)
			)
		}
	}
}

/// Necessary to create a wrapper over AccountId to implement sea-orm traits
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DbAccountId(pub AccountId);

/// All the next methods are necessary to support the proper mapping of the DbAccountId from/to String in DB and
/// to allow using it as primaryKey
impl sea_orm::sea_query::Nullable for DbAccountId {
	fn null() -> Value {
		Value::String(None)
	}
}

impl sea_orm::TryGetable for DbAccountId {
	/// Had to really pay attention to return proper TryGetError type when value not present => TryGetError::Null
	///
	/// as otherwise, when using `Option<DbAccountId>` with a None value it was crashing (when no "parent")
	fn try_get_by<I: sea_orm::ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
		let value: String = res
			.try_get_by(idx)
			.map_err(|e| TryGetError::Null(e.to_string()))?;
		Ok(DbAccountId(AccountId::from_str(&value).map_err(|e| {
			TryGetError::DbErr(DbErr::Custom(e.to_string()))
		})?))
	}
}

impl sea_orm::sea_query::ValueType for DbAccountId {
	fn try_from(v: Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
		match v {
			Value::String(Some(value)) => Ok(DbAccountId(
				AccountId::from_str(&value).map_err(|_| sea_orm::sea_query::ValueTypeErr)?,
			)),
			_ => Err(sea_orm::sea_query::ValueTypeErr),
		}
	}

	fn type_name() -> String {
		stringify!(DbAccountId).to_owned()
	}

	fn array_type() -> sea_orm::sea_query::ArrayType {
		sea_orm::sea_query::ArrayType::String
	}

	fn column_type() -> ColumnType {
		ColumnType::String(StringLen::None)
	}
}

impl From<DbAccountId> for Value {
	fn from(account_id: DbAccountId) -> Self {
		Value::String(Some(Box::new(account_id.0.to_string())))
	}
}

/// sea-orm forces us to implement this one; but since we map from/to string, we can't convert from a u64
impl TryFromU64 for DbAccountId {
	fn try_from_u64(_v: u64) -> Result<Self, DbErr> {
		Err(DbErr::Custom(
			"AccountIdWrapper cannot be created from U64".to_owned(),
		))
	}
}

impl Display for DbAccountId {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}", self.0)
	}
}

impl FromStr for DbAccountId {
	type Err = GcliError;

	fn from_str(s: &str) -> Result<Self, Self::Err> {
		Ok(DbAccountId(
			AccountId::from_str(s).map_err(|e| GcliError::Input(e.to_string()))?,
		))
	}
}

impl From<String> for DbAccountId {
	fn from(s: String) -> Self {
		DbAccountId(AccountId::from_str(&s).expect("Invalid AccountId format"))
	}
}

impl From<AccountId> for DbAccountId {
	fn from(account_id: AccountId) -> Self {
		DbAccountId(account_id)
	}
}

impl From<DbAccountId> for AccountId {
	fn from(db_account_id: DbAccountId) -> Self {
		db_account_id.0
	}
}

/// Didn't want to pollute the keys::CryptoScheme enum with sea-orm specific derivations
///
/// created a separate enum for the database with conversions between the two
#[derive(Debug, Copy, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[sea_orm(
	rs_type = "String",
	db_type = "String(StringLen::None)",
	rename_all = "PascalCase"
)]
pub enum DbCryptoScheme {
	Ed25519,
	Sr25519,
}

impl From<crate::keys::CryptoScheme> for DbCryptoScheme {
	fn from(scheme: crate::keys::CryptoScheme) -> Self {
		match scheme {
			crate::keys::CryptoScheme::Ed25519 => DbCryptoScheme::Ed25519,
			crate::keys::CryptoScheme::Sr25519 => DbCryptoScheme::Sr25519,
		}
	}
}

impl From<DbCryptoScheme> for crate::keys::CryptoScheme {
	fn from(scheme: DbCryptoScheme) -> Self {
		match scheme {
			DbCryptoScheme::Ed25519 => crate::keys::CryptoScheme::Ed25519,
			DbCryptoScheme::Sr25519 => crate::keys::CryptoScheme::Sr25519,
		}
	}
}

#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
	ParentAccount,
}

impl RelationTrait for Relation {
	fn def(&self) -> RelationDef {
		match self {
			Self::ParentAccount => Entity::belongs_to(Entity)
				.from(Column::Parent)
				.to(Column::Address)
				.into(),
		}
	}
}

pub struct ParentAccountLink;

impl Linked for ParentAccountLink {
	type FromEntity = Entity;

	type ToEntity = Entity;

	fn link(&self) -> Vec<RelationDef> {
		vec![Relation::ParentAccount.def()]
	}
}

#[async_trait]
impl ActiveModelBehavior for ActiveModel {
	/// This method is called before saving or updating the model to the database.
	/// It ensures that the model is valid according to the following constraints:
	///
	/// - A "base" vault account must have path:None, parent:None, crypto_scheme:Some(_), encrypted_suri:Some(_)
	/// - A "derivation" vault account must have path:Some(_), parent:Some(_), crypto_scheme:None, encrypted_suri:None
	async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
	where
		C: ConnectionTrait,
	{
		if insert {
			// If one of the elements of a "base" account is seen, all must be correctly filled
			if (self.path.is_not_set() || self.path.try_as_ref().unwrap().is_none())
				|| (self.parent.is_not_set() || self.parent.try_as_ref().unwrap().is_none())
				|| (self.crypto_scheme.is_set()
					&& self.crypto_scheme.try_as_ref().unwrap().is_some())
				|| (self.encrypted_suri.is_set()
					&& self.encrypted_suri.try_as_ref().unwrap().is_some())
			{
				if !((self.path.is_not_set() || self.path.try_as_ref().unwrap().is_none())
					&& (self.parent.is_not_set() || self.parent.try_as_ref().unwrap().is_none())
					&& (self.crypto_scheme.is_set()
						&& self.crypto_scheme.try_as_ref().unwrap().is_some())
					&& (self.encrypted_suri.is_set()
						&& self.encrypted_suri.try_as_ref().unwrap().is_some()))
				{
					return Err(DbErr::Custom(
                        "A \"base\" vault account must have path:None, parent:None, crypto_scheme:Some(_), encrypted_suri:Some(_)".into(),
                    ));
				}
			} else if !((self.path.is_set() && self.path.try_as_ref().unwrap().is_some())
				&& (self.parent.is_set() && self.parent.try_as_ref().unwrap().is_some())
				&& (self.crypto_scheme.is_not_set()
					|| self.crypto_scheme.try_as_ref().unwrap().is_none())
				&& (self.encrypted_suri.is_not_set()
					|| self.encrypted_suri.try_as_ref().unwrap().is_none()))
			{
				return Err(DbErr::Custom(
                    "A \"derivation\" vault account must have path:Some(_), parent:Some(_), crypto_scheme:None, encrypted_suri:None".into(),
                ));
			}
		} else {
			//Updates to accept:
			//	* Name Only
			//	* Overriding Base with Base account => only changing encrypted_suri
			//		* Should also support changing name at the same time
			//	* Overriding Derivation with Base account => upd Path=>None Parent=>None crypto=>Some enc_suri=>Some
			//		* Should also support changing name at the same time
			//	* Overriding Derivation with Derivation account => upd (Path=>Some) (Parent=>Some)
			//		* Should also support changing name at the same time
			//	* Overriding Base with Derivation account => upd Path=>Some Parent=>Some crypto=>None enc_suri=>None
			//		* Should also support changing name at the same time

			// If updating all path, parent, crypto_scheme, encrypted_suri
			if self.path.is_set()
				&& self.parent.is_set()
				&& self.crypto_scheme.is_set()
				&& self.encrypted_suri.is_set()
			{
				if self.parent.try_as_ref().unwrap().is_some() {
					if !(self.path.try_as_ref().unwrap().is_some()
						&& self.crypto_scheme.try_as_ref().unwrap().is_none()
						&& self.encrypted_suri.try_as_ref().unwrap().is_none())
					{
						return Err(DbErr::Custom(
							"An update to \"derivation\" vault account must have path:Some(_), parent:Some(_), crypto_scheme:None, encrypted_suri:None".into(),
						));
					}
				} else if !(self.path.try_as_ref().unwrap().is_none()
					&& self.crypto_scheme.try_as_ref().unwrap().is_some()
					&& self.encrypted_suri.try_as_ref().unwrap().is_some())
				{
					return Err(DbErr::Custom(
							"An update to \"base\" vault account must have path:None, parent:None, crypto_scheme:Some(_), encrypted_suri:Some(_)".into(),
						));
				}
			}
			// Else if updating path || parent both needs to have Some(_) value (update of Derivation)
			else if (self.path.is_set() || self.parent.is_set())
				&& !(self.parent.try_as_ref().unwrap().is_some()
					&& self.path.try_as_ref().unwrap().is_some())
			{
				return Err(DbErr::Custom(
							"An update of \"derivation\" parent/path must have both path:Some(_), parent:Some(_)".into(),
						));
			}
		}

		Ok(self)
	}
}

pub async fn find_by_id<C>(db: &C, address: &DbAccountId) -> Result<Option<Model>, GcliError>
where
	C: ConnectionTrait,
{
	Entity::find_by_id(address.clone())
		.one(db)
		.await
		.map_err(GcliError::from)
}

pub async fn find_by_name<C>(db: &C, name: &str) -> Result<Option<Model>, GcliError>
where
	C: ConnectionTrait,
{
	Entity::find()
		.filter(Column::Name.eq(Some(name.to_string())))
		.one(db)
		.await
		.map_err(GcliError::from)
}

pub async fn find_base_accounts<C>(db: &C) -> Result<Vec<Model>, GcliError>
where
	C: ConnectionTrait,
{
	Entity::find()
		.filter(Column::Path.is_null())
		.order_by_asc(Column::Address)
		.all(db)
		.await
		.map_err(GcliError::from)
}

/// Represents a node in the hierarchy of accounts
pub struct AccountTreeNode {
	pub account: Model,
	pub children: Vec<Rc<RefCell<AccountTreeNode>>>,
	pub parent: Option<Rc<RefCell<AccountTreeNode>>>,
}

/// Counts the depth of an `AccountTreeNode` in the hierarchy.
pub fn count_depth_account_tree_node(account_tree_node: &Rc<RefCell<AccountTreeNode>>) -> usize {
	let mut depth = 0;
	let mut current_node = Rc::clone(account_tree_node);

	while let Some(parent_node) = {
		let borrowed_node = current_node.borrow();
		borrowed_node.parent.as_ref().map(Rc::clone)
	} {
		depth += 1;
		current_node = parent_node;
	}

	depth
}

/// Counts number of accounts in an `AccountTreeNode` hierarchy, starting from account_tree_node and only visiting children.
pub fn count_accounts_in_account_tree_node_and_children(
	node: &Rc<RefCell<AccountTreeNode>>,
) -> usize {
	let borrowed_node = node.borrow();
	let mut count = 1; // Count the current node

	for child in &borrowed_node.children {
		count += count_accounts_in_account_tree_node_and_children(child);
	}

	count
}

/// Gets the base account tree node of the `AccountTreeNode` hierarchy.
pub fn get_base_account_tree_node(
	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
) -> Rc<RefCell<AccountTreeNode>> {
	//Move up to the base node
	let mut base_node = Rc::clone(account_tree_node);
	while let Some(parent_node) = {
		let borrowed_node = base_node.borrow();
		borrowed_node.parent.as_ref().map(Rc::clone)
	} {
		base_node = parent_node;
	}

	Rc::clone(&base_node)
}

/// Gets the account tree node for given address from the `AccountTreeNode` hierarchy.
pub fn get_account_tree_node_for_address(
	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
	address: &str,
) -> Rc<RefCell<AccountTreeNode>> {
	fn find_address_recursive(
		node: &Rc<RefCell<AccountTreeNode>>,
		address: &str,
	) -> Option<Rc<RefCell<AccountTreeNode>>> {
		let borrowed_node = node.borrow();

		if borrowed_node.account.address.to_string() == address {
			return Some(Rc::clone(node));
		}

		for child in &borrowed_node.children {
			if let Some(found) = find_address_recursive(child, address) {
				return Some(found);
			}
		}

		None
	}

	//Move up to the base node
	let base_account_tree_node = get_base_account_tree_node(account_tree_node);

	let account_tree_node_for_address = find_address_recursive(&base_account_tree_node, address)
		.unwrap_or_else(|| {
			panic!(
				"Could not find account with address:{} in the hierarchy",
				address
			)
		});

	Rc::clone(&account_tree_node_for_address)
}
/// Returns a new (limited) `AccountTreeNode` hierarchy including the selected account_tree_node and all its parents.
///
/// The base of the new hierarchy will be returned
pub fn get_base_parent_hierarchy_account_tree_node(
	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
) -> Rc<RefCell<AccountTreeNode>> {
	// Clone the current node to start the new hierarchy
	let new_node = Rc::new(RefCell::new(AccountTreeNode {
		account: account_tree_node.borrow().account.clone(),
		children: Vec::new(),
		parent: None,
	}));

	// Traverse up to the base node, creating new nodes for each parent
	let mut current_new_node = Rc::clone(&new_node);
	let mut current_node = Rc::clone(account_tree_node);

	while let Some(parent_node) = {
		let borrowed_node = current_node.borrow();
		borrowed_node.parent.as_ref().map(Rc::clone)
	} {
		let new_parent_node = Rc::new(RefCell::new(AccountTreeNode {
			account: parent_node.borrow().account.clone(),
			children: vec![Rc::clone(&current_new_node)],
			parent: None,
		}));

		current_new_node.borrow_mut().parent = Some(Rc::clone(&new_parent_node));
		current_new_node = new_parent_node;
		current_node = parent_node;
	}

	// Return the base of the new hierarchy
	current_new_node
}

/// Returns a vec of all the accounts starting from `account_tree_node` and all its children; depth first
///
/// Can be used to delete all the accounts in the hierarchy in the proper order
pub fn extract_accounts_depth_first_from_account_tree_node(
	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
) -> Result<Vec<Model>, GcliError> {
	fn retrieve_recursive_depth_first(
		node: &Rc<RefCell<AccountTreeNode>>,
		accounts: &mut Vec<Model>,
	) -> Result<(), GcliError> {
		let borrowed_node = node.borrow();

		for child in &borrowed_node.children {
			retrieve_recursive_depth_first(child, accounts)?;
		}

		accounts.push(borrowed_node.account.clone());

		Ok(())
	}

	let mut accounts = Vec::new();
	retrieve_recursive_depth_first(account_tree_node, &mut accounts)?;

	Ok(accounts)
}

/// Computes the name to reference the `AccountTreeNode` in the hierarchy if we can find/compute one.
///
/// Returns `None` otherwise.
pub fn compute_name_account_tree_node(
	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
) -> Option<String> {
	let mut name = String::new();
	let mut current_node = Rc::clone(account_tree_node);

	while let Some(parent_node) = {
		let borrowed_node = current_node.borrow();
		if let Some(account_name) = &borrowed_node.account.name {
			name.insert_str(0, account_name);
			return Some(name);
		} else if let Some(account_path) = &borrowed_node.account.path {
			name.insert_str(0, account_path);
		} else {
			return None;
		}
		borrowed_node.parent.as_ref().map(Rc::clone)
	} {
		current_node = parent_node;
	}

	Some(name)
}

/// Computes a map of names to reference of `AccountTreeNodes` in the hierarchy of its children.
pub fn compute_name_map_for_account_tree_node(
	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
) -> Result<HashMap<String, Rc<RefCell<AccountTreeNode>>>, GcliError> {
	let mut names_to_ref_map = HashMap::<String, Rc<RefCell<AccountTreeNode>>>::new();

	fn compute_recursive_name_map(
		node: &Rc<RefCell<AccountTreeNode>>,
		current_name: Option<String>,
		names_to_ref_map: &mut HashMap<String, Rc<RefCell<AccountTreeNode>>>,
	) -> Result<(), GcliError> {
		let borrowed_node = node.borrow();

		let current_name = match &borrowed_node.account.name {
			Some(name) => Some(name.clone()),
			None => match &borrowed_node.account.path {
				Some(path) => current_name
					.as_ref()
					.map(|name| format!("{}{}", name, path)),
				None => None,
			},
		};

		if let Some(name) = &current_name {
			names_to_ref_map.insert(name.clone(), Rc::clone(node));
		}

		for child in &borrowed_node.children {
			compute_recursive_name_map(child, current_name.clone(), names_to_ref_map)?;
		}

		Ok(())
	}

	compute_recursive_name_map(account_tree_node, None, &mut names_to_ref_map)?;

	Ok(names_to_ref_map)
}

/// Computes the SURI of the `AccountTreeNode` in the hierarchy; using the password to decrypt the encrypted SURI of Base account.
pub fn compute_suri_account_tree_node(
	account_tree_node: &Rc<RefCell<AccountTreeNode>>,
	password: String,
) -> Result<String, GcliError> {
	let mut suri = String::new();
	let mut current_node = Rc::clone(account_tree_node);

	while let Some(parent_node) = {
		let borrowed_node = current_node.borrow();
		if let Some(account_path) = &borrowed_node.account.path {
			suri.insert_str(0, account_path);
		} else if let Some(encrypted_suri) = &borrowed_node.account.encrypted_suri {
			let decrypted_suri = vault::decrypt(encrypted_suri, password.clone())
				.map_err(|e| GcliError::Input(e.to_string()))?;
			let secret_suri = String::from_utf8(decrypted_suri).map_err(|e| anyhow!(e))?;
			suri.insert_str(0, &secret_suri);
		} else {
			return Err(GcliError::Input("No encrypted SURI found".to_string()));
		}

		borrowed_node.parent.as_ref().map(Rc::clone)
	} {
		current_node = parent_node;
	}

	Ok(suri)
}

/// Fetches all the `base` account tree nodes with their hierarchies
pub async fn fetch_all_base_account_tree_node_hierarchies<C>(
	db: &C,
) -> Result<Vec<Rc<RefCell<AccountTreeNode>>>, GcliError>
where
	C: ConnectionTrait,
{
	let base_accounts = find_base_accounts(db).await?;

	let mut account_tree_nodes = Vec::new();

	for base_account in base_accounts {
		let base_account_tree_node =
			fetch_children_account_tree_nodes_boxed(db, base_account, None).await?;
		account_tree_nodes.push(base_account_tree_node);
	}

	Ok(account_tree_nodes)
}

/// Only returns the `base` accounts without their children
///
/// To be used in compute_vault_accounts_table to display only the base accounts
pub async fn fetch_only_base_account_tree_nodes<C>(
	db: &C,
) -> Result<Vec<Rc<RefCell<AccountTreeNode>>>, GcliError>
where
	C: ConnectionTrait,
{
	let base_accounts = find_base_accounts(db).await?;

	let mut account_tree_nodes = Vec::new();

	for base_account in base_accounts {
		let current_node = Rc::new(RefCell::new(AccountTreeNode {
			account: base_account.clone(),
			children: Vec::new(),
			parent: None,
		}));

		account_tree_nodes.push(current_node);
	}

	Ok(account_tree_nodes)
}

/// Fetches the `base` account tree node hierarchy for the given address
///
/// This one unwraps the Option and gives a proper error message in case of None
pub async fn fetch_base_account_tree_node_hierarchy_unwrapped<C>(
	db: &C,
	address: &str,
) -> Result<Rc<RefCell<AccountTreeNode>>, GcliError>
where
	C: ConnectionTrait,
{
	fetch_base_account_tree_node_hierarchy(db, address)
		.await?
		.ok_or(GcliError::Input(format!(
			"Could not compute tree of accounts for address:'{}'",
			address
		)))
}

/// Fetches the `base` account tree node hierarchy for the given address using db
pub async fn fetch_base_account_tree_node_hierarchy<C>(
	db: &C,
	address: &str,
) -> Result<Option<Rc<RefCell<AccountTreeNode>>>, GcliError>
where
	C: ConnectionTrait,
{
	if let Some(base_parent_account) = find_base_parent_account(db, address).await? {
		let base_account_tree_node =
			fetch_children_account_tree_nodes_boxed(db, base_parent_account, None).await?;
		Ok(Some(base_account_tree_node))
	} else {
		Ok(None)
	}
}

/// Finds the `base` account in db for the given address
async fn find_base_parent_account<C>(db: &C, address: &str) -> Result<Option<Model>, GcliError>
where
	C: ConnectionTrait,
{
	let account = find_by_id(
		db,
		&DbAccountId::from_str(address).expect("invalid address"),
	)
	.await?;

	if account.is_none() {
		return Ok(None);
	}

	let mut base_parent_account = account.unwrap();

	while let Some(parent_account) = base_parent_account
		.find_linked(ParentAccountLink)
		.one(db)
		.await
		.map_err(GcliError::from)?
	{
		base_parent_account = parent_account;
	}

	Ok(Some(base_parent_account))
}

async fn find_direct_children_accounts<C>(
	db: &C,
	current_account: &Model,
) -> Result<Vec<Model>, GcliError>
where
	C: ConnectionTrait,
{
	Entity::find()
		.filter(Column::Parent.eq(current_account.address.clone()))
		.order_by_asc(Column::Address)
		.all(db)
		.await
		.map_err(GcliError::from)
}

/// To make clippy happy... "warning: very complex type used. Consider factoring parts into `type` definitions"
type AccountTreeNodeResult<'c> =
	Pin<Box<dyn Future<Output = Result<Rc<RefCell<AccountTreeNode>>, GcliError>> + 'c>>;

/// This one seems necessary in order to handle async + recursion issue
///
/// Was suggested by AI and seems to work (might be improved)
fn fetch_children_account_tree_nodes_boxed<'c, C>(
	db: &'c C,
	current_account: Model,
	parent_node: Option<Rc<RefCell<AccountTreeNode>>>,
) -> AccountTreeNodeResult<'c>
where
	C: ConnectionTrait + 'c,
{
	Box::pin(fetch_children_account_tree_nodes(
		db,
		current_account,
		parent_node,
	))
}

/// Fetches the children account tree nodes for the given account and parent node
async fn fetch_children_account_tree_nodes<C>(
	db: &C,
	current_account: Model,
	parent_node: Option<Rc<RefCell<AccountTreeNode>>>,
) -> Result<Rc<RefCell<AccountTreeNode>>, GcliError>
where
	C: ConnectionTrait,
{
	let children_accounts = find_direct_children_accounts(db, &current_account).await?;

	let current_node = Rc::new(RefCell::new(AccountTreeNode {
		account: current_account.clone(),
		children: Vec::new(),
		parent: parent_node,
	}));

	let mut children = Vec::new();

	for child_account in children_accounts {
		let child_node = fetch_children_account_tree_nodes_boxed(
			db,
			child_account,
			Some(Rc::clone(&current_node)),
		)
		.await?;
		children.push(child_node);
	}

	current_node.borrow_mut().children = children;

	Ok(current_node)
}

pub async fn check_name_available<C>(
	db: &C,
	old_name: Option<&String>,
	new_name: Option<&String>,
) -> Result<bool, GcliError>
where
	C: ConnectionTrait,
{
	if old_name == new_name {
		return Ok(true);
	}

	if let Some(new_name) = new_name {
		let name_usage_count = Entity::find()
			.filter(Column::Name.eq(Some(new_name.clone())))
			.count(db)
			.await?;

		Ok(name_usage_count == 0)
	} else {
		Ok(true)
	}
}

pub async fn update_account<C>(db: &C, vault_account: ActiveModel) -> Result<Model, GcliError>
where
	C: ConnectionTrait,
{
	Ok(vault_account.update(db).await?)
}

/// Creates a `base` vault account and returns it
///
/// Typically used for `vault import|migrate` commands
pub async fn create_base_account<C>(
	db: &C,
	address: &str,
	name: Option<&String>,
	crypto_scheme: crate::keys::CryptoScheme,
	encrypted_suri: Vec<u8>,
) -> Result<Model, GcliError>
where
	C: ConnectionTrait,
{
	let account_id = DbAccountId::from_str(address)?;
	let existing_vault_account = Entity::find_by_id(account_id.clone()).one(db).await?;

	Ok(match existing_vault_account {
		Some(existing_vault_account) => {
			// To be safe
			return Err(GcliError::Input(format!(
				"Already existing vault account {existing_vault_account}"
			)));
		}
		None => {
			let vault_account = ActiveModel {
				address: Set(account_id),
				name: Set(name.cloned()),
				path: Set(None),
				crypto_scheme: Set(Some(crypto_scheme.into())),
				encrypted_suri: Set(Some(encrypted_suri)),
				parent: Default::default(),
			};
			vault_account.insert(db).await?
		}
	})
}

/// Creates a `derivation` vault account and returns it
///
/// Typically used for `vault derive` command
pub async fn create_derivation_account<C>(
	db: &C,
	address: &str,
	name: Option<&String>,
	derivation_path: &str,
	parent_address: &str,
) -> Result<Model, GcliError>
where
	C: ConnectionTrait,
{
	let account_id = DbAccountId::from_str(address)?;
	let vault_account = Entity::find_by_id(account_id.clone()).one(db).await?;

	Ok(match vault_account {
		Some(vault_account) => {
			// To be safe
			return Err(GcliError::Input(format!(
				"Already existing vault account {vault_account}"
			)));
		}
		None => {
			let vault_account = ActiveModel {
				address: Set(address.to_string().into()),
				name: Set(name.cloned()),
				path: Set(Some(derivation_path.to_string())),
				crypto_scheme: Set(None),
				encrypted_suri: Set(None),
				parent: Set(Some(parent_address.to_string().into())),
			};
			vault_account.insert(db).await?
		}
	})
}

pub async fn update_account_name<C>(
	db: &C,
	account: Model,
	new_name: Option<&String>,
) -> Result<Model, GcliError>
where
	C: ConnectionTrait,
{
	let old_name = account.name.clone();
	let mut account: ActiveModel = account.into();
	account.name = Set(new_name.cloned());
	let account = account.update(db).await?;
	println!(
		"Renamed address:'{}' from {:?} to {:?}",
		&account.address, old_name, new_name
	);

	Ok(account)
}

// Unit tests
#[cfg(test)]
pub mod tests {
	use super::*;
	pub mod account_tree_node_tests {
		use super::*;
		use crate::commands::vault;
		use crate::keys;
		use crate::keys::SUBSTRATE_MNEMONIC;

		pub fn mother_account_tree_node() -> Rc<RefCell<AccountTreeNode>> {
			let mother_address =
				DbAccountId::from_str("5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV").unwrap();
			let child1_address =
				DbAccountId::from_str("5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH").unwrap();
			let grandchild1_address =
				DbAccountId::from_str("5Fh5PLQNt1xuEXm71dfDtQdnwceSew4oHewWBLsWAkKspV7d").unwrap();
			let child2_address =
				DbAccountId::from_str("5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o").unwrap();
			let grandchild2_address =
				DbAccountId::from_str("5CvdJuB9HLXSi5FS9LW57cyHF13iCv5HDimo2C45KxnxriCT").unwrap();

			let grandchild1 = Rc::new(RefCell::new(AccountTreeNode {
				account: Model {
					address: grandchild1_address.clone(),
					name: Some("Grandchild 1".to_string()),
					path: Some("//0".to_string()),
					crypto_scheme: None,
					encrypted_suri: None,
					parent: Some(child1_address.clone()),
				},
				children: vec![],
				parent: None,
			}));

			let grandchild2 = Rc::new(RefCell::new(AccountTreeNode {
				account: Model {
					address: grandchild2_address.clone(),
					// name: Some("Grandchild 2".to_string()),
					name: None,
					path: Some("//1".to_string()),
					crypto_scheme: None,
					encrypted_suri: None,
					parent: Some(child2_address.clone()),
				},
				children: vec![],
				parent: None,
			}));

			let child1 = Rc::new(RefCell::new(AccountTreeNode {
				account: Model {
					address: child1_address.clone(),
					name: Some("Child 1".to_string()),
					path: Some("//0".to_string()),
					crypto_scheme: None,
					encrypted_suri: None,
					parent: Some(mother_address.clone()),
				},
				children: vec![grandchild1.clone()],
				parent: None,
			}));

			let child2 = Rc::new(RefCell::new(AccountTreeNode {
				account: Model {
					address: child2_address.clone(),
					// name: Some("Child 2".to_string()),
					name: None,
					path: Some("//1".to_string()),
					crypto_scheme: None,
					encrypted_suri: None,
					parent: Some(mother_address.clone()),
				},
				children: vec![grandchild2.clone()],
				parent: None,
			}));

			let mother = Rc::new(RefCell::new(AccountTreeNode {
				account: Model {
					address: mother_address.clone(),
					name: Some("Mother".to_string()),
					path: None,
					crypto_scheme: Some(DbCryptoScheme::Sr25519),
					encrypted_suri: Some(
						vault::encrypt(SUBSTRATE_MNEMONIC.as_bytes(), "".to_string()).unwrap(),
					),
					parent: None,
				},
				children: vec![child1.clone(), child2.clone()],
				parent: None,
			}));

			// Set parent references
			grandchild1.borrow_mut().parent = Some(child1.clone());
			grandchild2.borrow_mut().parent = Some(child2.clone());
			child1.borrow_mut().parent = Some(mother.clone());
			child2.borrow_mut().parent = Some(mother.clone());

			mother
		}

		pub fn mother_g1v1_account_tree_node() -> Rc<RefCell<AccountTreeNode>> {
			let mother_address =
				DbAccountId::from_str("5ET2jhgJFoNQUpgfdSkdwftK8DKWdqZ1FKm5GKWdPfMWhPr4").unwrap();

			let cesium_id = "test_cesium_id".to_string();
			let cesium_pwd = "test_cesium_pwd".to_string();

			let seed = keys::seed_from_cesium(&cesium_id, &cesium_pwd);
			let secret_suri = format!("0x{}", hex::encode(seed));

			let mother_g1v1 = Rc::new(RefCell::new(AccountTreeNode {
				account: Model {
					address: mother_address.clone(),
					name: Some("MotherG1v1".to_string()),
					path: None,
					crypto_scheme: Some(DbCryptoScheme::Ed25519),
					encrypted_suri: Some(
						vault::encrypt(secret_suri.as_bytes(), "".to_string()).unwrap(),
					),
					parent: None,
				},
				children: vec![],
				parent: None,
			}));

			mother_g1v1
		}

		#[test]
		fn test_count_depth_account_tree_node() {
			let mother = mother_account_tree_node();
			assert_eq!(count_depth_account_tree_node(&mother), 0);

			let child1 = mother.borrow().children[0].clone();
			assert_eq!(count_depth_account_tree_node(&child1), 1);

			let grandchild1 = child1.borrow().children[0].clone();
			assert_eq!(count_depth_account_tree_node(&grandchild1), 2);
		}

		#[test]
		fn test_count_accounts_in_account_tree_node_and_children() {
			let mother = mother_account_tree_node();
			assert_eq!(count_accounts_in_account_tree_node_and_children(&mother), 5);

			let child1 = mother.borrow().children[0].clone();
			assert_eq!(count_accounts_in_account_tree_node_and_children(&child1), 2);

			let grandchild1 = child1.borrow().children[0].clone();
			assert_eq!(
				count_accounts_in_account_tree_node_and_children(&grandchild1),
				1
			);
		}

		#[test]
		fn test_get_base_parent_hierarchy_account_tree_node() {
			let mother = mother_account_tree_node();
			let child1 = mother.borrow().children[0].clone();

			let new_mother = get_base_parent_hierarchy_account_tree_node(&child1);

			// Check if the base of the new hierarchy is the mother node
			assert_eq!(
				new_mother.borrow().account.address.to_string(),
				mother.borrow().account.address.to_string()
			);
			assert_eq!(new_mother.borrow().children.len(), 1);

			// Check if the child1 node is correctly linked in the new hierarchy
			let new_child1 = new_mother.borrow().children[0].clone();
			assert_eq!(
				new_child1.borrow().account.address.to_string(),
				child1.borrow().account.address.to_string()
			);
			assert_eq!(new_child1.borrow().children.len(), 0);

			// Check if the parent references are correctly set
			assert!(new_mother.borrow().parent.is_none());
			assert_eq!(
				new_child1
					.borrow()
					.parent
					.as_ref()
					.unwrap()
					.borrow()
					.account
					.address
					.to_string(),
				new_mother.borrow().account.address.to_string()
			);
		}

		#[test]
		fn test_retrieve_accounts_depth_first_from_account_tree_node() {
			let mother = mother_account_tree_node();
			let accounts = extract_accounts_depth_first_from_account_tree_node(&mother).unwrap();
			assert_eq!(accounts.len(), 5);
			assert_eq!(
				accounts[0].address.to_string(),
				mother.borrow().children[0].borrow().children[0]
					.borrow()
					.account
					.address
					.to_string()
			);
			assert_eq!(
				accounts[1].address.to_string(),
				mother.borrow().children[0]
					.borrow()
					.account
					.address
					.to_string()
			);
			assert_eq!(
				accounts[2].address.to_string(),
				mother.borrow().children[1].borrow().children[0]
					.borrow()
					.account
					.address
					.to_string()
			);
			assert_eq!(
				accounts[3].address.to_string(),
				mother.borrow().children[1]
					.borrow()
					.account
					.address
					.to_string()
			);
			assert_eq!(
				accounts[4].address.to_string(),
				mother.borrow().account.address.to_string()
			);
			let child1 = mother.borrow().children[0].clone();
			let accounts = extract_accounts_depth_first_from_account_tree_node(&child1).unwrap();
			assert_eq!(accounts.len(), 2);
			assert_eq!(
				accounts[0].address.to_string(),
				mother.borrow().children[0].borrow().children[0]
					.borrow()
					.account
					.address
					.to_string()
			);
			assert_eq!(
				accounts[1].address.to_string(),
				mother.borrow().children[0]
					.borrow()
					.account
					.address
					.to_string()
			);

			let grandchild1 = child1.borrow().children[0].clone();
			let accounts =
				extract_accounts_depth_first_from_account_tree_node(&grandchild1).unwrap();
			assert_eq!(accounts.len(), 1);
			assert_eq!(
				accounts[0].address.to_string(),
				mother.borrow().children[0].borrow().children[0]
					.borrow()
					.account
					.address
					.to_string()
			);
		}

		#[test]
		fn test_compute_name_account_tree_node() {
			let mother = mother_account_tree_node();
			assert_eq!(
				compute_name_account_tree_node(&mother),
				Some("Mother".to_string())
			);

			let child1 = mother.borrow().children[0].clone();
			assert_eq!(
				compute_name_account_tree_node(&child1),
				Some("Child 1".to_string())
			);

			let grandchild1 = child1.borrow().children[0].clone();
			assert_eq!(
				compute_name_account_tree_node(&grandchild1),
				Some("Grandchild 1".to_string())
			);

			let child2 = mother.borrow().children[1].clone();
			assert_eq!(
				compute_name_account_tree_node(&child2),
				Some("Mother//1".to_string())
			);

			let grandchild2 = child2.borrow().children[0].clone();
			assert_eq!(
				compute_name_account_tree_node(&grandchild2),
				Some("Mother//1//1".to_string())
			);
		}

		#[test]
		fn test_compute_name_account_tree_node_mother_without_name() {
			let mother = mother_account_tree_node();
			mother.borrow_mut().account.name = None;
			assert_eq!(compute_name_account_tree_node(&mother), None);

			let child1 = mother.borrow().children[0].clone();
			assert_eq!(
				compute_name_account_tree_node(&child1),
				Some("Child 1".to_string())
			);

			let grandchild1 = child1.borrow().children[0].clone();
			assert_eq!(
				compute_name_account_tree_node(&grandchild1),
				Some("Grandchild 1".to_string())
			);

			let child2 = mother.borrow().children[1].clone();
			assert_eq!(compute_name_account_tree_node(&child2), None);

			let grandchild2 = child2.borrow().children[0].clone();
			assert_eq!(compute_name_account_tree_node(&grandchild2), None);
		}

		#[test]
		fn test_compute_name_account_tree_node_grandchild1_without_name() {
			let mother = mother_account_tree_node();
			mother.borrow().children[0].borrow().children[0]
				.borrow_mut()
				.account
				.name = None;

			assert_eq!(
				compute_name_account_tree_node(&mother),
				Some("Mother".to_string())
			);

			let child1 = mother.borrow().children[0].clone();
			assert_eq!(
				compute_name_account_tree_node(&child1),
				Some("Child 1".to_string())
			);

			let grandchild1 = child1.borrow().children[0].clone();
			assert_eq!(
				compute_name_account_tree_node(&grandchild1),
				Some("Child 1//0".to_string())
			);
		}

		#[test]
		fn test_compute_name_map_for_account_tree_node() {
			let mother = mother_account_tree_node();
			let name_map = compute_name_map_for_account_tree_node(&mother).unwrap();
			assert_eq!(name_map.len(), 5);
			assert_eq!(
				name_map
					.get("Mother")
					.unwrap()
					.borrow()
					.account
					.address
					.to_string(),
				mother.borrow().account.address.to_string()
			);
			assert_eq!(
				name_map
					.get("Child 1")
					.unwrap()
					.borrow()
					.account
					.address
					.to_string(),
				mother.borrow().children[0]
					.borrow()
					.account
					.address
					.to_string()
			);
			assert_eq!(
				name_map
					.get("Grandchild 1")
					.unwrap()
					.borrow()
					.account
					.address
					.to_string(),
				mother.borrow().children[0].borrow().children[0]
					.borrow()
					.account
					.address
					.to_string()
			);
			assert_eq!(
				name_map
					.get("Mother//1")
					.unwrap()
					.borrow()
					.account
					.address
					.to_string(),
				mother.borrow().children[1]
					.borrow()
					.account
					.address
					.to_string()
			);
			assert_eq!(
				name_map
					.get("Mother//1//1")
					.unwrap()
					.borrow()
					.account
					.address
					.to_string(),
				mother.borrow().children[1].borrow().children[0]
					.borrow()
					.account
					.address
					.to_string()
			);
		}

		#[test]
		fn test_get_base_account_tree_node() {
			let mother = mother_account_tree_node();
			let child1 = mother.borrow().children[0].clone();
			let grandchild1 = child1.borrow().children[0].clone();
			assert_eq!(
				get_base_account_tree_node(&grandchild1)
					.borrow()
					.account
					.address
					.to_string(),
				mother.borrow().account.address.to_string()
			);
		}

		#[test]
		fn test_get_account_tree_node_for_address() {
			let mother = mother_account_tree_node();
			let grandchild1 = mother.borrow().children[0].borrow().children[0].clone();
			let grandchild1_address = &grandchild1.borrow().account.address.to_string();
			assert_eq!(
				get_account_tree_node_for_address(&mother, grandchild1_address)
					.borrow()
					.account
					.address
					.to_string(),
				grandchild1_address.to_string()
			);
		}

		#[test]
		fn test_compute_suri_account_tree_node() {
			let mother = mother_account_tree_node();
			let password = "".to_string();
			assert_eq!(
				compute_suri_account_tree_node(&mother, password.clone()).unwrap(),
				SUBSTRATE_MNEMONIC
			);

			let child1 = mother.borrow().children[0].clone();
			assert_eq!(
				compute_suri_account_tree_node(&child1, password.clone()).unwrap(),
				SUBSTRATE_MNEMONIC.to_string() + "//0"
			);

			let grandchild1 = child1.borrow().children[0].clone();
			assert_eq!(
				compute_suri_account_tree_node(&grandchild1, password.clone()).unwrap(),
				SUBSTRATE_MNEMONIC.to_string() + "//0//0"
			);

			let child2 = mother.borrow().children[1].clone();
			assert_eq!(
				compute_suri_account_tree_node(&child2, password.clone()).unwrap(),
				SUBSTRATE_MNEMONIC.to_string() + "//1"
			);

			let grandchild2 = child2.borrow().children[0].clone();
			assert_eq!(
				compute_suri_account_tree_node(&grandchild2, password.clone()).unwrap(),
				SUBSTRATE_MNEMONIC.to_string() + "//1//1"
			);
		}
	}
}