use std::{
    collections::{HashMap, HashSet},
    ops::Not,
};

use api_models::{
    payments::RedirectionResponse,
    user::{self as user_api, InviteMultipleUserResponse, NameIdUnit, UserMerchantDetailsResponse},
};
use common_enums::{connector_enums, EntityType, MerchantProductType, UserAuthType};
use common_utils::{
    fp_utils, type_name,
    types::{keymanager::Identifier, user::LineageContext},
};
#[cfg(feature = "email")]
use diesel_models::user_role::UserRoleUpdate;
use diesel_models::{
    enums::{TotpStatus, UserRoleVersion, UserStatus},
    organization::OrganizationBridge,
    user as storage_user,
    user_authentication_method::{UserAuthenticationMethodNew, UserAuthenticationMethodUpdate},
};
use error_stack::{report, ResultExt};
use hyperswitch_masking::{ExposeInterface, PeekInterface, Secret};
use router_env::{env, instrument, logger, tracing};
use storage_impl::errors::StorageError;
#[cfg(feature = "v1")]
use subscriptions::RouterResponse;
#[cfg(not(feature = "email"))]
use user_api::dashboard_metadata::SetMetaDataRequest;

use super::errors::{StorageErrorExt, UserErrors, UserResponse, UserResult};
#[cfg(feature = "v1")]
use super::{admin, errors::ApiErrorResponse};
use crate::{
    consts,
    core::encryption::send_request_to_key_service_for_user,
    db::{
        domain::user_authentication_method::DEFAULT_USER_AUTH_METHOD,
        user_role::ListUserRolesByUserIdPayload,
    },
    routes::{app::ReqState, SessionState},
    services::{
        authentication::{self as auth, blacklist::BlackList},
        authorization::{self, permissions::Permission, roles},
        openidconnect, ApplicationResponse,
    },
    types::{domain, transformers::ForeignInto},
    utils::{
        self,
        user::{theme as theme_utils, two_factor_auth as tfa_utils},
    },
};
#[cfg(feature = "v1")]
use crate::{db::user_role::ListUserRolesByOrgIdPayload, types::transformers::ForeignFrom};
#[cfg(feature = "email")]
use crate::{services::email::types as email_types, utils::user as user_utils};

pub mod dashboard_metadata;
pub mod launch_sage;
#[cfg(feature = "dummy_connector")]
pub mod sample_data;
pub mod theme;

#[cfg(feature = "email")]
pub async fn signup_with_merchant_id(
    state: SessionState,
    request: user_api::SignUpWithMerchantIdRequest,
    auth_id: Option<String>,
    theme_id: Option<String>,
) -> UserResponse<user_api::SignUpWithMerchantIdResponse> {
    // Validate platform feature is enabled if Platform org requested
    fp_utils::when(
        request.organization_type == Some(common_enums::OrganizationType::Platform)
            && !state.conf.platform.enabled,
        || {
            Err(report!(UserErrors::InvalidPlatformOperation)
                .attach_printable("Platform feature is not enabled"))
        },
    )?;

    let new_user = domain::NewUser::try_from(request.clone())?;
    new_user
        .get_new_merchant()
        .get_new_organization()
        .insert_org_in_db(state.clone())
        .await?;

    let user_from_db = new_user
        .insert_user_and_merchant_in_db(state.clone())
        .await?;

    let _user_role = new_user
        .insert_org_level_user_role_in_db(
            state.clone(),
            common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
            UserStatus::Active,
            user_from_db.get_user_id().to_string(),
        )
        .await?;

    let theme = theme_utils::get_theme_using_optional_theme_id(&state, theme_id).await?;

    let email_contents = email_types::ResetPassword {
        recipient_email: user_from_db.get_email().try_into()?,
        user_name: domain::UserName::new(user_from_db.get_name())?,
        settings: state.conf.clone(),
        auth_id,
        theme_id: theme.as_ref().map(|theme| theme.theme_id.clone()),
        theme_config: theme
            .map(|theme| theme.email_config())
            .unwrap_or(state.conf.theme.email_config.clone()),
    };

    let send_email_result = state
        .email_client
        .compose_and_send_email(
            user_utils::get_base_url(&state),
            Box::new(email_contents),
            state.conf.proxy.https_url.as_ref(),
        )
        .await;

    logger::info!(?send_email_result);
    Ok(ApplicationResponse::Json(user_api::AuthorizeResponse {
        is_email_sent: send_email_result.is_ok(),
        user_id: user_from_db.get_user_id().to_string(),
    }))
}

pub async fn get_active_user_details(
    state: SessionState,
    user_from_token: auth::UserFromToken,
) -> UserResponse<user_api::GetUserDetailsResponse> {
    let user = user_from_token.get_active_user_from_db(&state).await?;
    let verification_days_left = utils::user::get_verification_days_left(&state, &user)?;
    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Failed to retrieve role information")?;

    let merchant_key_store = state
        .store
        .get_merchant_key_store_by_merchant_id(
            &user_from_token.merchant_id,
            &state.store.get_master_key().to_vec().into(),
        )
        .await;

    let version = if let Ok(merchant_key_store) = merchant_key_store {
        let merchant_account = state
            .store
            .find_merchant_account_by_merchant_id(&user_from_token.merchant_id, &merchant_key_store)
            .await;

        if let Ok(merchant_account) = merchant_account {
            merchant_account.version
        } else if merchant_account
            .as_ref()
            .map_err(|e| e.current_context().is_db_not_found())
            .err()
            .unwrap_or(false)
        {
            common_enums::ApiVersion::V2
        } else {
            Err(merchant_account
                .err()
                .map(|e| e.change_context(UserErrors::InternalServerError))
                .unwrap_or(UserErrors::InternalServerError.into()))?
        }
    } else if merchant_key_store
        .as_ref()
        .map_err(|e| e.current_context().is_db_not_found())
        .err()
        .unwrap_or(false)
    {
        common_enums::ApiVersion::V2
    } else {
        Err(merchant_key_store
            .err()
            .map(|e| e.change_context(UserErrors::InternalServerError))
            .unwrap_or(UserErrors::InternalServerError.into()))?
    };

    let theme = theme_utils::get_most_specific_theme_using_token_and_min_entity(
        &state,
        &user_from_token,
        EntityType::Profile,
    )
    .await?;
    Ok(ApplicationResponse::Json(
        user_api::GetUserDetailsResponse {
            merchant_id: user_from_token.merchant_id,
            name: user.get_name(),
            email: user.get_email(),
            user_id: user.get_user_id().to_string(),
            verification_days_left,
            role_id: user_from_token.role_id,
            org_id: user_from_token.org_id,
            is_two_factor_auth_setup: user.get_totp_status() == TotpStatus::Set,
            recovery_codes_left: user.get_recovery_codes().map(|codes| codes.len()),
            profile_id: user_from_token.profile_id,
            entity_type: role_info.get_entity_type(),
            theme_id: theme.map(|theme| theme.theme_id),
            version,
        },
    ))
}

pub async fn signup_token_only_flow(
    state: SessionState,
    request: user_api::SignUpRequest,
) -> UserResponse<user_api::TokenResponse> {
    let user_email = domain::UserEmail::from_pii_email(request.email.clone())?;
    utils::user::validate_email_domain_auth_type_using_db(
        &state,
        &user_email,
        UserAuthType::Password,
    )
    .await?;

    let new_user = domain::NewUser::try_from(request)?;
    new_user
        .get_new_merchant()
        .get_new_organization()
        .insert_org_in_db(state.clone())
        .await?;
    let user_from_db = new_user
        .insert_user_and_merchant_in_db(state.clone())
        .await?;
    let user_role = new_user
        .insert_org_level_user_role_in_db(
            state.clone(),
            common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
            UserStatus::Active,
            user_from_db.get_user_id().to_string(),
        )
        .await?;

    let next_flow =
        domain::NextFlow::from_origin(domain::Origin::SignUp, user_from_db.clone(), &state).await?;

    let token = next_flow
        .get_token_with_user_role(&state, &user_role)
        .await?;

    let response = user_api::TokenResponse {
        token: token.clone(),
        token_type: next_flow.get_flow().into(),
    };
    auth::cookies::set_cookie_response(response, token)
}

pub async fn signin_token_only_flow(
    state: SessionState,
    request: user_api::SignInRequest,
) -> UserResponse<user_api::TokenResponse> {
    let user_email = domain::UserEmail::from_pii_email(request.email)?;

    utils::user::validate_email_domain_auth_type_using_db(
        &state,
        &user_email,
        UserAuthType::Password,
    )
    .await?;

    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_email(&user_email)
        .await
        .to_not_found_response(UserErrors::InvalidCredentials)?
        .into();

    user_from_db.compare_password(&request.password)?;

    let next_flow =
        domain::NextFlow::from_origin(domain::Origin::SignIn, user_from_db.clone(), &state).await?;

    let token = next_flow.get_token(&state).await?;

    let response = user_api::TokenResponse {
        token: token.clone(),
        token_type: next_flow.get_flow().into(),
    };
    auth::cookies::set_cookie_response(response, token)
}

#[cfg(feature = "email")]
pub async fn connect_account(
    state: SessionState,
    request: user_api::ConnectAccountRequest,
    auth_id: Option<String>,
    theme_id: Option<String>,
) -> UserResponse<user_api::ConnectAccountResponse> {
    let user_email = domain::UserEmail::from_pii_email(request.email.clone())?;

    utils::user::validate_email_domain_auth_type_using_db(
        &state,
        &user_email,
        UserAuthType::MagicLink,
    )
    .await?;

    let find_user = state
        .global_store
        .find_active_user_by_user_email(&user_email)
        .await;

    if let Ok(found_user) = find_user {
        let user_from_db: domain::UserFromStorage = found_user.into();

        let theme = theme_utils::get_theme_using_optional_theme_id(&state, theme_id).await?;

        let email_contents = email_types::MagicLink {
            recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?,
            settings: state.conf.clone(),
            user_name: domain::UserName::new(user_from_db.get_name())?,
            auth_id,
            theme_id: theme.as_ref().map(|theme| theme.theme_id.clone()),
            theme_config: theme
                .map(|theme| theme.email_config())
                .unwrap_or(state.conf.theme.email_config.clone()),
        };

        let send_email_result = state
            .email_client
            .compose_and_send_email(
                user_utils::get_base_url(&state),
                Box::new(email_contents),
                state.conf.proxy.https_url.as_ref(),
            )
            .await;
        logger::info!(?send_email_result);

        Ok(ApplicationResponse::Json(
            user_api::ConnectAccountResponse {
                is_email_sent: send_email_result.is_ok(),
                user_id: user_from_db.get_user_id().to_string(),
            },
        ))
    } else if find_user
        .as_ref()
        .map_err(|e| e.current_context().is_db_not_found())
        .err()
        .unwrap_or(false)
    {
        if matches!(env::which(), env::Env::Production) {
            return Err(report!(UserErrors::InvalidCredentials));
        }

        let new_user = domain::NewUser::try_from(request)?;
        let _ = new_user
            .get_new_merchant()
            .get_new_organization()
            .insert_org_in_db(state.clone())
            .await?;
        let user_from_db = new_user
            .insert_user_and_merchant_in_db(state.clone())
            .await?;
        let _user_role = new_user
            .insert_org_level_user_role_in_db(
                state.clone(),
                common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
                UserStatus::Active,
                user_from_db.get_user_id().to_string(),
            )
            .await?;

        let theme = theme_utils::get_theme_using_optional_theme_id(&state, theme_id).await?;

        let magic_link_email = email_types::VerifyEmail {
            recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?,
            settings: state.conf.clone(),
            auth_id,
            theme_id: theme.as_ref().map(|theme| theme.theme_id.clone()),
            theme_config: theme
                .map(|theme| theme.email_config())
                .unwrap_or(state.conf.theme.email_config.clone()),
        };

        let magic_link_result = state
            .email_client
            .compose_and_send_email(
                user_utils::get_base_url(&state),
                Box::new(magic_link_email),
                state.conf.proxy.https_url.as_ref(),
            )
            .await;

        logger::info!(?magic_link_result);

        if state.tenant.tenant_id.get_string_repr() == common_utils::consts::DEFAULT_TENANT {
            let welcome_to_community_email = email_types::WelcomeToCommunity {
                recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?,
            };

            let welcome_email_result = state
                .email_client
                .compose_and_send_email(
                    user_utils::get_base_url(&state),
                    Box::new(welcome_to_community_email),
                    state.conf.proxy.https_url.as_ref(),
                )
                .await;

            logger::info!(?welcome_email_result);
        }

        Ok(ApplicationResponse::Json(
            user_api::ConnectAccountResponse {
                is_email_sent: magic_link_result.is_ok(),
                user_id: user_from_db.get_user_id().to_string(),
            },
        ))
    } else {
        Err(find_user
            .err()
            .map(|e| e.change_context(UserErrors::InternalServerError))
            .unwrap_or(UserErrors::InternalServerError.into()))
    }
}

pub async fn get_user_merchant_details(
    state: SessionState,
    user_from_token: auth::UserFromToken,
) -> UserResponse<UserMerchantDetailsResponse> {
    let key_store = state
        .store
        .get_merchant_key_store_by_merchant_id(
            &user_from_token.merchant_id,
            &state.store.get_master_key().to_vec().into(),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    let merchant_account = state
        .store
        .find_merchant_account_by_merchant_id(&user_from_token.merchant_id, &key_store)
        .await
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(UserMerchantDetailsResponse {
        product_type: merchant_account.product_type,
        merchant_account_type: merchant_account.merchant_account_type,
    }))
}

pub async fn signout(
    state: SessionState,
    user_from_token: auth::UserIdFromAuth,
) -> UserResponse<()> {
    tfa_utils::delete_totp_from_redis(&state, &user_from_token.user_id).await?;
    tfa_utils::delete_recovery_code_from_redis(&state, &user_from_token.user_id).await?;
    tfa_utils::delete_totp_secret_from_redis(&state, &user_from_token.user_id).await?;

    auth::blacklist::insert_user_in_blacklist(&state, &user_from_token.user_id).await?;
    auth::cookies::remove_cookie_response()
}

pub async fn change_password(
    state: SessionState,
    request: user_api::ChangePasswordRequest,
    user_from_token: auth::UserFromToken,
) -> UserResponse<()> {
    let user: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_from_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    user.compare_password(&request.old_password)
        .change_context(UserErrors::InvalidOldPassword)?;

    if request.old_password == request.new_password {
        return Err(UserErrors::ChangePasswordError.into());
    }
    let new_password = domain::UserPassword::new(request.new_password)?;

    let new_password_hash =
        utils::user::password::generate_password_hash(new_password.get_secret())?;

    let _ = state
        .global_store
        .update_active_user_by_user_id(
            user.get_user_id(),
            diesel_models::user::UserUpdate::PasswordUpdate {
                password: new_password_hash,
            },
        )
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Failed to update user password in the database")?;

    let _ = auth::blacklist::insert_user_in_blacklist(&state, user.get_user_id())
        .await
        .map_err(|error| logger::error!(?error));

    #[cfg(not(feature = "email"))]
    {
        state
            .store
            .delete_user_scoped_dashboard_metadata_by_merchant_id_data_key(
                &user_from_token.user_id,
                &user_from_token.merchant_id,
                diesel_models::enums::DashboardMetadata::IsChangePasswordRequired,
            )
            .await
            .map_err(|e| logger::error!("Error while deleting dashboard metadata {e:?}"))
            .ok();
    }

    Ok(ApplicationResponse::StatusOk)
}

#[cfg(feature = "email")]
pub async fn forgot_password(
    state: SessionState,
    request: user_api::ForgotPasswordRequest,
    auth_id: Option<String>,
    theme_id: Option<String>,
) -> UserResponse<()> {
    let user_email = domain::UserEmail::from_pii_email(request.email)?;

    utils::user::validate_email_domain_auth_type_using_db(
        &state,
        &user_email,
        UserAuthType::Password,
    )
    .await?;

    let user_from_db = state
        .global_store
        .find_active_user_by_user_email(&user_email)
        .await
        .map_err(|e| {
            if e.current_context().is_db_not_found() {
                e.change_context(UserErrors::UserNotFound)
            } else {
                e.change_context(UserErrors::InternalServerError)
            }
        })
        .map(domain::UserFromStorage::from)?;

    let theme = theme_utils::get_theme_using_optional_theme_id(&state, theme_id).await?;

    let email_contents = email_types::ResetPassword {
        recipient_email: domain::UserEmail::from_pii_email(user_from_db.get_email())?,
        settings: state.conf.clone(),
        user_name: domain::UserName::new(user_from_db.get_name())?,
        auth_id,
        theme_id: theme.as_ref().map(|theme| theme.theme_id.clone()),
        theme_config: theme
            .map(|theme| theme.email_config())
            .unwrap_or(state.conf.theme.email_config.clone()),
    };

    state
        .email_client
        .compose_and_send_email(
            user_utils::get_base_url(&state),
            Box::new(email_contents),
            state.conf.proxy.https_url.as_ref(),
        )
        .await
        .map_err(|e| e.change_context(UserErrors::InternalServerError))?;

    Ok(ApplicationResponse::StatusOk)
}

pub async fn rotate_password(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
    request: user_api::RotatePasswordRequest,
    _req_state: ReqState,
) -> UserResponse<()> {
    let user: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    let password = domain::UserPassword::new(request.password.to_owned())?;
    let hash_password = utils::user::password::generate_password_hash(password.get_secret())?;

    if user.compare_password(&request.password).is_ok() {
        return Err(UserErrors::ChangePasswordError.into());
    }

    let user = state
        .global_store
        .update_active_user_by_user_id(
            &user_token.user_id,
            storage_user::UserUpdate::PasswordUpdate {
                password: hash_password,
            },
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    let _ = auth::blacklist::insert_user_in_blacklist(&state, &user.user_id)
        .await
        .map_err(|error| logger::error!(?error));

    auth::cookies::remove_cookie_response()
}

#[cfg(feature = "email")]
pub async fn reset_password_token_only_flow(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
    request: user_api::ResetPasswordRequest,
) -> UserResponse<()> {
    let token = request.token.expose();
    let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
        .await
        .change_context(UserErrors::LinkInvalid)?;

    auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;

    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_email(&email_token.get_email()?)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    if user_from_db.get_user_id() != user_token.user_id {
        return Err(UserErrors::LinkInvalid.into());
    }

    let password = domain::UserPassword::new(request.password)?;
    let hash_password = utils::user::password::generate_password_hash(password.get_secret())?;

    let user = state
        .global_store
        .update_active_user_by_user_id(
            user_from_db.get_user_id(),
            storage_user::UserUpdate::PasswordUpdate {
                password: hash_password,
            },
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    if !user_from_db.is_verified() {
        let _ = state
            .global_store
            .update_active_user_by_user_id(
                user_from_db.get_user_id(),
                storage_user::UserUpdate::VerifyUser,
            )
            .await
            .map_err(|error| logger::error!(?error));
    }

    let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
        .await
        .map_err(|error| logger::error!(?error));
    let _ = auth::blacklist::insert_user_in_blacklist(&state, &user.user_id)
        .await
        .map_err(|error| logger::error!(?error));

    auth::cookies::remove_cookie_response()
}

pub async fn invite_multiple_user(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    requests: Vec<user_api::InviteUserRequest>,
    req_state: ReqState,
    auth_id: Option<String>,
) -> UserResponse<Vec<InviteMultipleUserResponse>> {
    if requests.len() > 10 {
        return Err(report!(UserErrors::MaxInvitationsError))
            .attach_printable("Number of invite requests must not exceed 10");
    }

    let responses = futures::future::join_all(requests.into_iter().map(|request| async {
        match handle_invitation(&state, &user_from_token, &request, &req_state, &auth_id).await {
            Ok(response) => response,
            Err(error) => {
                logger::error!(invite_error=?error);

                InviteMultipleUserResponse {
                    email: request.email,
                    is_email_sent: false,
                    password: None,
                    error: Some(error.current_context().get_error_message().to_string()),
                }
            }
        }
    }))
    .await;

    Ok(ApplicationResponse::Json(responses))
}

async fn handle_invitation(
    state: &SessionState,
    user_from_token: &auth::UserFromToken,
    request: &user_api::InviteUserRequest,
    req_state: &ReqState,
    auth_id: &Option<String>,
) -> UserResult<InviteMultipleUserResponse> {
    let inviter_user = user_from_token.get_active_user_from_db(state).await?;

    if inviter_user.get_email() == request.email {
        Err(report!(UserErrors::InvalidRoleOperationWithMessage(
            "User Inviting themselves".to_string(),
        )))?;
    }

    let inviter_role_info = roles::RoleInfo::from_role_id_in_lineage(
        state,
        &user_from_token.role_id,
        &user_from_token.merchant_id,
        &user_from_token.org_id,
        &user_from_token.profile_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .to_not_found_response(UserErrors::InternalServerError)?;

    let req_role_info = roles::RoleInfo::from_role_id_in_lineage(
        state,
        &request.role_id,
        &user_from_token.merchant_id,
        &user_from_token.org_id,
        &user_from_token.profile_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .to_not_found_response(UserErrors::InvalidRoleId)?;

    if inviter_role_info.get_entity_type() < req_role_info.get_entity_type() {
        Err(report!(UserErrors::InvalidRoleOperationWithMessage(
            "Inviter role entity type is lower than requested role entity type".to_string(),
        )))
        .attach_printable(format!(
            "{} is trying to invite {}",
            inviter_role_info.get_entity_type(),
            req_role_info.get_entity_type()
        ))?;
    };

    if !req_role_info.is_invitable() {
        Err(report!(UserErrors::InvalidRoleId))
            .attach_printable(format!("role_id = {} is not invitable", request.role_id))?;
    }

    if let Some(role_product_type_filter) = req_role_info.get_product_type_filter() {
        match req_role_info.get_entity_type() {
            EntityType::Tenant | EntityType::Organization => {}
            EntityType::Merchant | EntityType::Profile => {
                let merchant_key_store = state
                    .store
                    .get_merchant_key_store_by_merchant_id(
                        &user_from_token.merchant_id,
                        &state.store.get_master_key().to_vec().into(),
                    )
                    .await
                    .change_context(UserErrors::InternalServerError)
                    .attach_printable("Failed to retrieve merchant key store by merchant_id")?;

                let merchant_account = state
                    .store
                    .find_merchant_account_by_merchant_id(
                        &user_from_token.merchant_id,
                        &merchant_key_store,
                    )
                    .await
                    .to_not_found_response(UserErrors::MerchantIdNotFound)?;

                let merchant_product_type = merchant_account
                    .product_type
                    .unwrap_or(MerchantProductType::Orchestration);
                if role_product_type_filter != merchant_product_type {
                    Err(report!(UserErrors::InvalidRoleId)).attach_printable(format!(
                        "role_id = {} is not for product_type = {}",
                        request.role_id, merchant_product_type
                    ))?;
                }
            }
        }
    }

    let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;
    let invitee_user = state
        .global_store
        .find_active_user_by_user_email(&invitee_email)
        .await;

    if let Ok(invitee_user) = invitee_user {
        handle_existing_user_invitation(
            state,
            user_from_token,
            request,
            invitee_user.into(),
            req_role_info,
            auth_id,
        )
        .await
    } else if invitee_user
        .as_ref()
        .map_err(|e| e.current_context().is_db_not_found())
        .err()
        .unwrap_or(false)
    {
        handle_new_or_inactive_user_invitation(
            state,
            user_from_token,
            request,
            req_role_info,
            req_state.clone(),
            auth_id,
        )
        .await
    } else {
        Err(UserErrors::InternalServerError.into())
    }
}

#[allow(unused_variables)]
async fn handle_existing_user_invitation(
    state: &SessionState,
    user_from_token: &auth::UserFromToken,
    request: &user_api::InviteUserRequest,
    invitee_user_from_db: domain::UserFromStorage,
    role_info: roles::RoleInfo,
    auth_id: &Option<String>,
) -> UserResult<InviteMultipleUserResponse> {
    let now = common_utils::date_time::now();

    if state
        .global_store
        .find_user_role_by_user_id_and_lineage_with_entity_type(
            invitee_user_from_db.get_user_id(),
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.org_id,
            &user_from_token.merchant_id,
            &user_from_token.profile_id,
            UserRoleVersion::V1,
        )
        .await
        .is_err_and(|err| err.current_context().is_db_not_found())
        .not()
    {
        return Err(UserErrors::UserExists.into());
    }

    if state
        .global_store
        .find_user_role_by_user_id_and_lineage_with_entity_type(
            invitee_user_from_db.get_user_id(),
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.org_id,
            &user_from_token.merchant_id,
            &user_from_token.profile_id,
            UserRoleVersion::V2,
        )
        .await
        .is_err_and(|err| err.current_context().is_db_not_found())
        .not()
    {
        return Err(UserErrors::UserExists.into());
    }

    let (org_id, merchant_id, profile_id) = match role_info.get_entity_type() {
        EntityType::Tenant => {
            return Err(UserErrors::InvalidRoleOperationWithMessage(
                "Tenant roles are not allowed for this operation".to_string(),
            )
            .into());
        }
        EntityType::Organization => (Some(&user_from_token.org_id), None, None),
        EntityType::Merchant => (
            Some(&user_from_token.org_id),
            Some(&user_from_token.merchant_id),
            None,
        ),
        EntityType::Profile => (
            Some(&user_from_token.org_id),
            Some(&user_from_token.merchant_id),
            Some(&user_from_token.profile_id),
        ),
    };

    if state
        .global_store
        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
            user_id: invitee_user_from_db.get_user_id(),
            tenant_id: user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            org_id,
            merchant_id,
            profile_id,
            entity_id: None,
            version: None,
            status: None,
            limit: Some(1),
        })
        .await
        .is_ok_and(|data| data.is_empty().not())
    {
        return Err(UserErrors::UserExists.into());
    }

    let user_role = domain::NewUserRole {
        user_id: invitee_user_from_db.get_user_id().to_owned(),
        role_id: request.role_id.clone(),
        status: {
            if cfg!(feature = "email") {
                UserStatus::InvitationSent
            } else {
                UserStatus::Active
            }
        },
        created_by: user_from_token.user_id.clone(),
        last_modified_by: user_from_token.user_id.clone(),
        created_at: now,
        last_modified: now,
        entity: domain::NoLevel,
    };

    let _user_role = match role_info.get_entity_type() {
        EntityType::Tenant => {
            return Err(UserErrors::InvalidRoleOperationWithMessage(
                "Tenant roles are not allowed for this operation".to_string(),
            )
            .into());
        }
        EntityType::Organization => {
            user_role
                .add_entity(domain::OrganizationLevel {
                    tenant_id: user_from_token
                        .tenant_id
                        .clone()
                        .unwrap_or(state.tenant.tenant_id.clone()),
                    org_id: user_from_token.org_id.clone(),
                })
                .insert_in_v2(state)
                .await?
        }
        EntityType::Merchant => {
            user_role
                .add_entity(domain::MerchantLevel {
                    tenant_id: user_from_token
                        .tenant_id
                        .clone()
                        .unwrap_or(state.tenant.tenant_id.clone()),
                    org_id: user_from_token.org_id.clone(),
                    merchant_id: user_from_token.merchant_id.clone(),
                })
                .insert_in_v2(state)
                .await?
        }
        EntityType::Profile => {
            user_role
                .add_entity(domain::ProfileLevel {
                    tenant_id: user_from_token
                        .tenant_id
                        .clone()
                        .unwrap_or(state.tenant.tenant_id.clone()),
                    org_id: user_from_token.org_id.clone(),
                    merchant_id: user_from_token.merchant_id.clone(),
                    profile_id: user_from_token.profile_id.clone(),
                })
                .insert_in_v2(state)
                .await?
        }
    };

    let is_email_sent;
    #[cfg(feature = "email")]
    {
        let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;
        let entity = match role_info.get_entity_type() {
            EntityType::Tenant => {
                return Err(UserErrors::InvalidRoleOperationWithMessage(
                    "Tenant roles are not allowed for this operation".to_string(),
                )
                .into());
            }
            EntityType::Organization => email_types::Entity {
                entity_id: user_from_token.org_id.get_string_repr().to_owned(),
                entity_type: EntityType::Organization,
            },
            EntityType::Merchant => email_types::Entity {
                entity_id: user_from_token.merchant_id.get_string_repr().to_owned(),
                entity_type: EntityType::Merchant,
            },
            EntityType::Profile => email_types::Entity {
                entity_id: user_from_token.profile_id.get_string_repr().to_owned(),
                entity_type: EntityType::Profile,
            },
        };

        let theme = theme_utils::get_most_specific_theme_using_token_and_min_entity(
            state,
            user_from_token,
            role_info.get_entity_type(),
        )
        .await?;

        let email_contents = email_types::InviteUser {
            recipient_email: invitee_email,
            user_name: domain::UserName::new(invitee_user_from_db.get_name())?,
            settings: state.conf.clone(),
            entity,
            auth_id: auth_id.clone(),
            theme_id: theme.as_ref().map(|theme| theme.theme_id.clone()),
            theme_config: theme
                .map(|theme| theme.email_config())
                .unwrap_or(state.conf.theme.email_config.clone()),
        };

        is_email_sent = state
            .email_client
            .compose_and_send_email(
                user_utils::get_base_url(state),
                Box::new(email_contents),
                state.conf.proxy.https_url.as_ref(),
            )
            .await
            .map(|email_result| logger::info!(?email_result))
            .map_err(|email_result| logger::error!(?email_result))
            .is_ok();
    }
    #[cfg(not(feature = "email"))]
    {
        is_email_sent = false;
    }

    Ok(InviteMultipleUserResponse {
        email: request.email.clone(),
        is_email_sent,
        password: None,
        error: None,
    })
}

#[allow(unused_variables)]
async fn handle_new_or_inactive_user_invitation(
    state: &SessionState,
    user_from_token: &auth::UserFromToken,
    request: &user_api::InviteUserRequest,
    role_info: roles::RoleInfo,
    req_state: ReqState,
    auth_id: &Option<String>,
) -> UserResult<InviteMultipleUserResponse> {
    let new_user = domain::NewUser::try_from((request.clone(), user_from_token.clone()))?;

    let user_from_storage = new_user
        .insert_or_reactivate_user_in_db(state.global_store.as_ref())
        .await?;

    let invitation_status = if cfg!(feature = "email") {
        UserStatus::InvitationSent
    } else {
        UserStatus::Active
    };

    let now = common_utils::date_time::now();

    let user_role = domain::NewUserRole {
        user_id: user_from_storage.get_user_id().to_string(),
        role_id: request.role_id.clone(),
        status: invitation_status,
        created_by: user_from_token.user_id.clone(),
        last_modified_by: user_from_token.user_id.clone(),
        created_at: now,
        last_modified: now,
        entity: domain::NoLevel,
    };

    let _user_role = match role_info.get_entity_type() {
        EntityType::Tenant => {
            return Err(UserErrors::InvalidRoleOperationWithMessage(
                "Tenant roles are not allowed for this operation".to_string(),
            )
            .into());
        }
        EntityType::Organization => {
            user_role
                .add_entity(domain::OrganizationLevel {
                    tenant_id: user_from_token
                        .tenant_id
                        .clone()
                        .unwrap_or(state.tenant.tenant_id.clone()),
                    org_id: user_from_token.org_id.clone(),
                })
                .insert_in_v2(state)
                .await?
        }
        EntityType::Merchant => {
            user_role
                .add_entity(domain::MerchantLevel {
                    tenant_id: user_from_token
                        .tenant_id
                        .clone()
                        .unwrap_or(state.tenant.tenant_id.clone()),
                    org_id: user_from_token.org_id.clone(),
                    merchant_id: user_from_token.merchant_id.clone(),
                })
                .insert_in_v2(state)
                .await?
        }
        EntityType::Profile => {
            user_role
                .add_entity(domain::ProfileLevel {
                    tenant_id: user_from_token
                        .tenant_id
                        .clone()
                        .unwrap_or(state.tenant.tenant_id.clone()),
                    org_id: user_from_token.org_id.clone(),
                    merchant_id: user_from_token.merchant_id.clone(),
                    profile_id: user_from_token.profile_id.clone(),
                })
                .insert_in_v2(state)
                .await?
        }
    };

    let is_email_sent;

    #[cfg(feature = "email")]
    {
        // TODO: Adding this to avoid clippy lints
        // Will be adding actual usage for this variable later
        let _ = req_state.clone();
        let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;
        let entity = match role_info.get_entity_type() {
            EntityType::Tenant => {
                return Err(UserErrors::InvalidRoleOperationWithMessage(
                    "Tenant roles are not allowed for this operation".to_string(),
                )
                .into());
            }
            EntityType::Organization => email_types::Entity {
                entity_id: user_from_token.org_id.get_string_repr().to_owned(),
                entity_type: EntityType::Organization,
            },
            EntityType::Merchant => email_types::Entity {
                entity_id: user_from_token.merchant_id.get_string_repr().to_owned(),
                entity_type: EntityType::Merchant,
            },
            EntityType::Profile => email_types::Entity {
                entity_id: user_from_token.profile_id.get_string_repr().to_owned(),
                entity_type: EntityType::Profile,
            },
        };

        let theme = theme_utils::get_most_specific_theme_using_token_and_min_entity(
            state,
            user_from_token,
            role_info.get_entity_type(),
        )
        .await?;

        let email_contents = email_types::InviteUser {
            recipient_email: invitee_email,
            user_name: domain::UserName::new(new_user.get_name())?,
            settings: state.conf.clone(),
            entity,
            auth_id: auth_id.clone(),
            theme_id: theme.as_ref().map(|theme| theme.theme_id.clone()),
            theme_config: theme
                .map(|theme| theme.email_config())
                .unwrap_or(state.conf.theme.email_config.clone()),
        };
        let send_email_result = state
            .email_client
            .compose_and_send_email(
                user_utils::get_base_url(state),
                Box::new(email_contents),
                state.conf.proxy.https_url.as_ref(),
            )
            .await;
        logger::info!(?send_email_result);
        is_email_sent = send_email_result.is_ok();
    }
    #[cfg(not(feature = "email"))]
    {
        is_email_sent = false;

        let invited_user_token = auth::UserFromToken {
            user_id: user_from_storage.get_user_id().to_string(),
            merchant_id: user_from_token.merchant_id.clone(),
            org_id: user_from_token.org_id.clone(),
            role_id: request.role_id.clone(),
            profile_id: user_from_token.profile_id.clone(),
            tenant_id: user_from_token.tenant_id.clone(),
        };

        let set_metadata_request = SetMetaDataRequest::IsChangePasswordRequired;
        dashboard_metadata::set_metadata(
            state.clone(),
            invited_user_token,
            set_metadata_request,
            req_state,
        )
        .await?;
    }

    Ok(InviteMultipleUserResponse {
        is_email_sent,
        password: new_user
            .get_password()
            .map(|password| password.get_secret()),
        email: request.email.clone(),
        error: None,
    })
}

#[cfg(feature = "email")]
pub async fn resend_invite(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    request: user_api::ReInviteUserRequest,
    auth_id: Option<String>,
) -> UserResponse<()> {
    let invitee_email = domain::UserEmail::from_pii_email(request.email)?;
    let user: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_email(&invitee_email)
        .await
        .map_err(|e| {
            if e.current_context().is_db_not_found() {
                e.change_context(UserErrors::InvalidRoleOperation)
                    .attach_printable("User not found in the records")
            } else {
                e.change_context(UserErrors::InternalServerError)
            }
        })?
        .into();

    let user_role = match state
        .global_store
        .find_user_role_by_user_id_and_lineage_with_entity_type(
            user.get_user_id(),
            user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            &user_from_token.org_id,
            &user_from_token.merchant_id,
            &user_from_token.profile_id,
            UserRoleVersion::V2,
        )
        .await
    {
        Ok(user_role) => Some(user_role),
        Err(err) => {
            if err.current_context().is_db_not_found() {
                None
            } else {
                return Err(report!(UserErrors::InternalServerError));
            }
        }
    };

    let user_role = match user_role {
        Some(user_role) => user_role,
        None => state
            .global_store
            .find_user_role_by_user_id_and_lineage_with_entity_type(
                user.get_user_id(),
                user_from_token
                    .tenant_id
                    .as_ref()
                    .unwrap_or(&state.tenant.tenant_id),
                &user_from_token.org_id,
                &user_from_token.merchant_id,
                &user_from_token.profile_id,
                UserRoleVersion::V1,
            )
            .await
            .to_not_found_response(UserErrors::InvalidRoleOperationWithMessage(
                "User not found in records".to_string(),
            ))?,
    };

    if !matches!(user_role.status, UserStatus::InvitationSent) {
        return Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable("User status is not InvitationSent".to_string());
    }

    let (entity_id, entity_type) = user_role
        .get_entity_id_and_type()
        .ok_or(UserErrors::InternalServerError)?;

    let invitee_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_role.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)?;

    let theme = theme_utils::get_most_specific_theme_using_token_and_min_entity(
        &state,
        &user_from_token,
        invitee_role_info.get_entity_type(),
    )
    .await?;

    let email_contents = email_types::InviteUser {
        recipient_email: invitee_email,
        user_name: domain::UserName::new(user.get_name())?,
        settings: state.conf.clone(),
        entity: email_types::Entity {
            entity_id,
            entity_type,
        },
        auth_id: auth_id.clone(),
        theme_id: theme.as_ref().map(|theme| theme.theme_id.clone()),
        theme_config: theme
            .map(|theme| theme.email_config())
            .unwrap_or(state.conf.theme.email_config.clone()),
    };

    state
        .email_client
        .compose_and_send_email(
            user_utils::get_base_url(&state),
            Box::new(email_contents),
            state.conf.proxy.https_url.as_ref(),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::StatusOk)
}

#[cfg(feature = "email")]
pub async fn accept_invite_from_email_token_only_flow(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
    request: user_api::AcceptInviteFromEmailRequest,
    status_check: Option<bool>,
) -> UserResponse<user_api::AcceptInviteResponse> {
    let token = request.token.expose();

    let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
        .await
        .change_context(UserErrors::LinkInvalid)?;

    auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;

    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_email(&email_token.get_email()?)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    if user_from_db.get_user_id() != user_token.user_id {
        return Err(UserErrors::LinkInvalid.into());
    }

    let entity = email_token.get_entity().ok_or(UserErrors::LinkInvalid)?;

    let (org_id, merchant_id, profile_id) =
        utils::user_role::get_lineage_for_user_id_and_entity_for_accepting_invite(
            &state,
            &user_token.user_id,
            user_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            entity.entity_id.clone(),
            entity.entity_type,
        )
        .await
        .change_context(UserErrors::InternalServerError)?
        .ok_or(UserErrors::InternalServerError)?;

    let v2_roles = state
        .global_store
        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
            user_id: user_from_db.get_user_id(),
            tenant_id: user_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            org_id: Some(&org_id),
            merchant_id: merchant_id.as_ref(),
            profile_id: profile_id.as_ref(),
            entity_id: None,
            version: Some(UserRoleVersion::V2),
            status: None,
            limit: Some(1),
        })
        .await
        .change_context(UserErrors::InternalServerError)?;

    let user_role = match v2_roles.into_iter().next() {
        Some(role) => role,
        None => {
            let v1_roles = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                    user_id: user_from_db.get_user_id(),
                    tenant_id: user_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: Some(&org_id),
                    merchant_id: merchant_id.as_ref(),
                    profile_id: profile_id.as_ref(),
                    entity_id: None,
                    version: Some(UserRoleVersion::V1),
                    status: None,
                    limit: Some(1),
                })
                .await
                .change_context(UserErrors::InternalServerError)?;

            v1_roles.into_iter().next().ok_or_else(|| {
                report!(UserErrors::InvalidRoleOperation)
                    .attach_printable("User not found in the organization")
            })?
        }
    };
    let invitation_status = match user_role.status {
        UserStatus::Active => user_api::InvitationAcceptanceStatus::AlreadyAccepted,
        UserStatus::InvitationSent => {
            let (update_v1_result, update_v2_result) =
                utils::user_role::update_v1_and_v2_user_roles_in_db(
                    &state,
                    user_from_db.get_user_id(),
                    user_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    &org_id,
                    merchant_id.as_ref(),
                    profile_id.as_ref(),
                    UserRoleUpdate::UpdateStatus {
                        status: UserStatus::Active,
                        modified_by: user_from_db.get_user_id().to_owned(),
                    },
                )
                .await;

            if update_v1_result.is_err() && update_v2_result.is_err() {
                return Err(report!(UserErrors::InvalidRoleOperation))
                    .attach_printable("User not found in the organization")?;
            }

            user_api::InvitationAcceptanceStatus::SuccessfullyAccepted
        }
    };

    if !user_from_db.is_verified() {
        let _ = state
            .global_store
            .update_active_user_by_user_id(
                user_from_db.get_user_id(),
                storage_user::UserUpdate::VerifyUser,
            )
            .await
            .map_err(|error| logger::error!(?error));
    }

    let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
        .await
        .map_err(|error| logger::error!(?error));

    if status_check.unwrap_or(false) {
        Ok(ApplicationResponse::Json(
            user_api::AcceptInviteResponse::Status(invitation_status),
        ))
    } else {
        let current_flow = domain::CurrentFlow::new(
            user_token,
            domain::SPTFlow::AcceptInvitationFromEmail.into(),
        )?;
        let next_flow = current_flow.next(user_from_db.clone(), &state).await?;

        let token = next_flow.get_token(&state).await?;

        let response = user_api::TokenResponse {
            token: token.clone(),
            token_type: next_flow.get_flow().into(),
        };
        auth::cookies::set_cookie_response(user_api::AcceptInviteResponse::Token(response), token)
    }
}

#[cfg(feature = "email")]
pub async fn terminate_accept_invite_only_flow(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
    request: user_api::AcceptInviteFromEmailRequest,
) -> UserResponse<user_api::TokenResponse> {
    let token = request.token.expose();

    let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
        .await
        .change_context(UserErrors::LinkInvalid)?;

    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_email(&email_token.get_email()?)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    if user_from_db.get_user_id() != user_token.user_id {
        return Err(UserErrors::LinkInvalid.into());
    }

    let entity = email_token.get_entity().ok_or(UserErrors::LinkInvalid)?;

    let (org_id, merchant_id, profile_id) =
        utils::user_role::get_lineage_for_user_id_and_entity_for_accepting_invite(
            &state,
            &user_token.user_id,
            user_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            entity.entity_id.clone(),
            entity.entity_type,
        )
        .await
        .change_context(UserErrors::InternalServerError)?
        .ok_or(UserErrors::InternalServerError)?;

    let v2_roles = state
        .global_store
        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
            user_id: user_from_db.get_user_id(),
            tenant_id: user_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            org_id: Some(&org_id),
            merchant_id: merchant_id.as_ref(),
            profile_id: profile_id.as_ref(),
            entity_id: None,
            version: Some(UserRoleVersion::V2),
            status: None,
            limit: Some(1),
        })
        .await
        .change_context(UserErrors::InternalServerError)?;

    let user_role = match v2_roles.into_iter().next() {
        Some(role) => role,
        None => {
            let v1_roles = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                    user_id: user_from_db.get_user_id(),
                    tenant_id: user_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: Some(&org_id),
                    merchant_id: merchant_id.as_ref(),
                    profile_id: profile_id.as_ref(),
                    entity_id: None,
                    version: Some(UserRoleVersion::V1),
                    status: None,
                    limit: Some(1),
                })
                .await
                .change_context(UserErrors::InternalServerError)?;

            v1_roles.into_iter().next().ok_or_else(|| {
                report!(UserErrors::InvalidRoleOperation)
                    .attach_printable("User not found in the organization")
            })?
        }
    };

    match user_role.status {
        UserStatus::Active => {
            let current_flow = domain::CurrentFlow::new(
                user_token,
                domain::SPTFlow::AcceptInvitationFromEmail.into(),
            )?;
            let next_flow = current_flow.next(user_from_db.clone(), &state).await?;

            let token = next_flow.get_token(&state).await?;

            let response = user_api::TokenResponse {
                token: token.clone(),
                token_type: next_flow.get_flow().into(),
            };
            auth::cookies::set_cookie_response(response, token)
        }
        UserStatus::InvitationSent => Err(report!(UserErrors::InvalidRoleOperation))
            .attach_printable("User invitation is pending. Please accept the invitation first."),
    }
}

pub async fn create_internal_user(
    state: SessionState,
    request: user_api::CreateInternalUserRequest,
) -> UserResponse<()> {
    let role_info = roles::RoleInfo::from_predefined_roles(request.role_id.as_str())
        .ok_or(UserErrors::InvalidRoleId)?;

    fp_utils::when(
        role_info.is_internal().not()
            || request.role_id == common_utils::consts::ROLE_ID_INTERNAL_ADMIN,
        || Err(UserErrors::InvalidRoleId),
    )?;

    let key_store = state
        .store
        .get_merchant_key_store_by_merchant_id(
            &common_utils::id_type::MerchantId::get_internal_user_merchant_id(
                consts::user_role::INTERNAL_USER_MERCHANT_ID,
            ),
            &state.store.get_master_key().to_vec().into(),
        )
        .await
        .map_err(|e| {
            if e.current_context().is_db_not_found() {
                e.change_context(UserErrors::MerchantIdNotFound)
            } else {
                e.change_context(UserErrors::InternalServerError)
            }
        })?;

    let default_tenant_id = common_utils::id_type::TenantId::try_from_string(
        common_utils::consts::DEFAULT_TENANT.to_owned(),
    )
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Unable to parse default tenant id")?;

    if state.tenant.tenant_id != default_tenant_id {
        return Err(UserErrors::ForbiddenTenantId)
            .attach_printable("Operation allowed only for the default tenant");
    }

    let internal_merchant_id = common_utils::id_type::MerchantId::get_internal_user_merchant_id(
        consts::user_role::INTERNAL_USER_MERCHANT_ID,
    );

    let internal_merchant = state
        .store
        .find_merchant_account_by_merchant_id(&internal_merchant_id, &key_store)
        .await
        .map_err(|e| {
            if e.current_context().is_db_not_found() {
                e.change_context(UserErrors::MerchantIdNotFound)
            } else {
                e.change_context(UserErrors::InternalServerError)
            }
        })?;

    let new_user = domain::NewUser::try_from((request, internal_merchant.organization_id.clone()))?;

    let mut store_user: storage_user::UserNew = new_user.clone().try_into()?;
    store_user.set_is_verified(true);

    let user_from_storage = state
        .global_store
        .insert_user(store_user)
        .await
        .map_err(|e| {
            if e.current_context().is_db_unique_violation() {
                e.change_context(UserErrors::UserExists)
            } else {
                e.change_context(UserErrors::InternalServerError)
            }
        })
        .map(domain::user::UserFromStorage::from)?;

    new_user
        .get_no_level_user_role(
            role_info.get_role_id().to_string(),
            UserStatus::Active,
            user_from_storage.get_user_id().to_string(),
        )
        .add_entity(domain::MerchantLevel {
            tenant_id: default_tenant_id,
            org_id: internal_merchant.organization_id,
            merchant_id: internal_merchant_id,
        })
        .insert_in_v2(&state)
        .await
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::StatusOk)
}

pub async fn create_tenant_user(
    state: SessionState,
    request: user_api::CreateTenantUserRequest,
) -> UserResponse<()> {
    let (merchant_id, org_id) = state
        .store
        .list_merchant_and_org_ids(1, None)
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Failed to get merchants list for org")?
        .pop()
        .ok_or(UserErrors::InvalidRoleOperation)
        .attach_printable("No merchants found in the tenancy")?;

    let new_user = domain::NewUser::try_from((
        request,
        domain::MerchantAccountIdentifier {
            merchant_id,
            org_id,
        },
    ))?;
    let mut store_user: storage_user::UserNew = new_user.clone().try_into()?;
    store_user.set_is_verified(true);

    let user_from_storage = state
        .global_store
        .insert_user(store_user)
        .await
        .map_err(|e| {
            if e.current_context().is_db_unique_violation() {
                e.change_context(UserErrors::UserExists)
            } else {
                e.change_context(UserErrors::InternalServerError)
            }
        })
        .map(domain::user::UserFromStorage::from)?;

    new_user
        .get_no_level_user_role(
            common_utils::consts::ROLE_ID_TENANT_ADMIN.to_string(),
            UserStatus::Active,
            user_from_storage.get_user_id().to_string(),
        )
        .add_entity(domain::TenantLevel {
            tenant_id: state.tenant.tenant_id.clone(),
        })
        .insert_in_v2(&state)
        .await
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::StatusOk)
}

#[cfg(feature = "v1")]
pub async fn create_platform_account(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    req: user_api::PlatformAccountCreateRequest,
) -> UserResponse<user_api::PlatformAccountCreateResponse> {
    fp_utils::when(matches!(env::which(), env::Env::Production), || {
        Err(report!(UserErrors::InvalidPlatformOperation))
    })?;

    let user_from_db = user_from_token.get_active_user_from_db(&state).await?;

    let new_merchant = domain::NewUserMerchant::try_from(req)?;
    let new_organization = new_merchant.get_new_organization();
    let organization = new_organization.insert_org_in_db(state.clone()).await?;

    let merchant_account = new_merchant
        .create_new_merchant_and_insert_in_db(state.to_owned())
        .await?;

    state
        .accounts_store
        .update_organization_by_org_id(
            &organization.get_organization_id(),
            diesel_models::organization::OrganizationUpdate::UpdatePlatformMerchant {
                platform_merchant_id: merchant_account.get_id().clone(),
            },
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    let now = common_utils::date_time::now();

    let user_role = domain::NewUserRole {
        user_id: user_from_db.get_user_id().to_owned(),
        role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
        status: UserStatus::Active,
        created_by: user_from_token.user_id.clone(),
        last_modified_by: user_from_token.user_id.clone(),
        created_at: now,
        last_modified: now,
        entity: domain::NoLevel,
    };

    user_role
        .add_entity(domain::OrganizationLevel {
            tenant_id: user_from_token
                .tenant_id
                .clone()
                .unwrap_or(state.tenant.tenant_id.clone()),
            org_id: merchant_account.organization_id.clone(),
        })
        .insert_in_v2(&state)
        .await?;

    Ok(ApplicationResponse::Json(
        user_api::PlatformAccountCreateResponse {
            org_id: organization.get_organization_id(),
            org_name: organization.get_organization_name(),
            org_type: organization.organization_type.unwrap_or_default(),
            merchant_id: merchant_account.get_id().to_owned(),
            merchant_account_type: merchant_account.merchant_account_type,
        },
    ))
}

#[cfg(feature = "v1")]
pub async fn create_org_merchant_for_user(
    state: SessionState,
    req: user_api::UserOrgMerchantCreateRequest,
) -> UserResponse<()> {
    let db_organization = ForeignFrom::foreign_from(req.clone());
    let org: diesel_models::organization::Organization = state
        .accounts_store
        .insert_organization(db_organization)
        .await
        .change_context(UserErrors::InternalServerError)?;
    let default_product_type = consts::user::DEFAULT_PRODUCT_TYPE;
    let merchant_account_create_request = utils::user::create_merchant_account_request_for_org(
        req,
        org.clone(),
        default_product_type,
    )?;

    Box::pin(admin::create_merchant_account(
        state.clone(),
        merchant_account_create_request,
        Some(auth::AuthenticationDataWithOrg {
            organization_id: org.get_organization_id(),
        }),
    ))
    .await
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Error while creating a merchant")?;

    Ok(ApplicationResponse::StatusOk)
}

pub async fn create_merchant_account(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    req: user_api::UserMerchantCreate,
) -> UserResponse<user_api::UserMerchantAccountResponse> {
    let user_from_db = user_from_token.get_active_user_from_db(&state).await?;

    let new_merchant = domain::NewUserMerchant::try_from((user_from_db, req, user_from_token))?;
    let domain_merchant_account = new_merchant
        .create_new_merchant_and_insert_in_db(state.to_owned())
        .await?;

    Ok(ApplicationResponse::Json(
        user_api::UserMerchantAccountResponse {
            merchant_id: domain_merchant_account.get_id().to_owned(),
            merchant_name: domain_merchant_account.merchant_name,
            product_type: domain_merchant_account.product_type,
            merchant_account_type: domain_merchant_account.merchant_account_type,
            version: domain_merchant_account.version,
        },
    ))
}

pub async fn list_user_roles_details(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    request: user_api::GetUserRoleDetailsRequest,
    _req_state: ReqState,
) -> UserResponse<Vec<user_api::GetUserRoleDetailsResponseV2>> {
    let required_user =
        utils::user::get_active_user_from_db_by_email(&state, request.email.try_into()?)
            .await
            .to_not_found_response(UserErrors::InvalidRoleOperation)?;

    let requestor_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .to_not_found_response(UserErrors::InternalServerError)
    .attach_printable("Failed to fetch role info")?;

    if requestor_role_info.is_internal() {
        return Err(UserErrors::InvalidRoleOperationWithMessage(
            "Internal roles are not allowed for this operation".to_string(),
        )
        .into());
    }

    let user_roles_set = state
        .global_store
        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
            user_id: required_user.get_user_id(),
            tenant_id: user_from_token
                .tenant_id
                .as_ref()
                .unwrap_or(&state.tenant.tenant_id),
            org_id: Some(&user_from_token.org_id),
            merchant_id: (requestor_role_info.get_entity_type() <= EntityType::Merchant)
                .then_some(&user_from_token.merchant_id),
            profile_id: (requestor_role_info.get_entity_type() <= EntityType::Profile)
                .then_some(&user_from_token.profile_id),
            entity_id: None,
            version: None,
            status: None,
            limit: None,
        })
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Failed to fetch user roles")?
        .into_iter()
        .collect::<HashSet<_>>();

    let org_name = state
        .accounts_store
        .find_organization_by_org_id(&user_from_token.org_id)
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Org id not found")?
        .get_organization_name();

    let org = NameIdUnit {
        id: user_from_token.org_id.clone(),
        name: org_name,
    };

    let (merchant_ids, merchant_profile_ids) = user_roles_set.iter().try_fold(
        (Vec::new(), Vec::new()),
        |(mut merchant, mut merchant_profile), user_role| {
            let (_, entity_type) = user_role
                .get_entity_id_and_type()
                .ok_or(UserErrors::InternalServerError)
                .attach_printable("Failed to compute entity id and type")?;

            match entity_type {
                EntityType::Merchant => {
                    let merchant_id = user_role
                        .merchant_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)
                        .attach_printable(
                            "Merchant id not found in user role for merchant level entity",
                        )?;
                    merchant.push(merchant_id)
                }
                EntityType::Profile => {
                    let merchant_id = user_role
                        .merchant_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)
                        .attach_printable(
                            "Merchant id not found in user role for merchant level entity",
                        )?;
                    let profile_id = user_role
                        .profile_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)
                        .attach_printable(
                            "Profile id not found in user role for profile level entity",
                        )?;

                    merchant.push(merchant_id.clone());
                    merchant_profile.push((merchant_id, profile_id))
                }
                EntityType::Tenant | EntityType::Organization => (),
            };

            Ok::<_, error_stack::Report<UserErrors>>((merchant, merchant_profile))
        },
    )?;

    let merchant_map = state
        .store
        .list_multiple_merchant_accounts(merchant_ids)
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Error while listing merchant accounts")?
        .into_iter()
        .map(|merchant_account| {
            (
                merchant_account.get_id().to_owned(),
                merchant_account.merchant_name.clone(),
            )
        })
        .collect::<HashMap<_, _>>();

    let profile_map = futures::future::try_join_all(merchant_profile_ids.iter().map(
        |merchant_profile_id| async {
            let merchant_key_store = state
                .store
                .get_merchant_key_store_by_merchant_id(
                    &merchant_profile_id.0,
                    &state.store.get_master_key().to_vec().into(),
                )
                .await
                .change_context(UserErrors::InternalServerError)
                .attach_printable("Failed to retrieve merchant key store by merchant_id")?;

            state
                .store
                .find_business_profile_by_profile_id(&merchant_key_store, &merchant_profile_id.1)
                .await
                .change_context(UserErrors::InternalServerError)
                .attach_printable("Failed to retrieve business profile")
        },
    ))
    .await
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Failed to construct profile map")?
    .into_iter()
    .map(|profile| (profile.get_id().to_owned(), profile.profile_name))
    .collect::<HashMap<_, _>>();

    let role_name_map = futures::future::try_join_all(
        user_roles_set
            .iter()
            .map(|user_role| user_role.role_id.clone())
            .collect::<HashSet<_>>()
            .into_iter()
            .map(|role_id| async {
                let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
                    &state,
                    &role_id,
                    &user_from_token.org_id,
                    user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                )
                .await
                .change_context(UserErrors::InternalServerError)?;

                Ok::<_, error_stack::Report<_>>((role_id, role_info.get_role_name().to_string()))
            }),
    )
    .await?
    .into_iter()
    .collect::<HashMap<_, _>>();

    let role_details_list: Vec<_> = user_roles_set
        .iter()
        .map(|user_role| {
            let (_, entity_type) = user_role
                .get_entity_id_and_type()
                .ok_or(UserErrors::InternalServerError)?;

            let (merchant, profile) = match entity_type {
                EntityType::Tenant | EntityType::Organization => (None, None),
                EntityType::Merchant => {
                    let merchant_id = &user_role
                        .merchant_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)?;

                    (
                        Some(NameIdUnit {
                            id: merchant_id.clone(),
                            name: merchant_map
                                .get(merchant_id)
                                .ok_or(UserErrors::InternalServerError)?
                                .to_owned(),
                        }),
                        None,
                    )
                }
                EntityType::Profile => {
                    let merchant_id = &user_role
                        .merchant_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)?;
                    let profile_id = &user_role
                        .profile_id
                        .clone()
                        .ok_or(UserErrors::InternalServerError)?;

                    (
                        Some(NameIdUnit {
                            id: merchant_id.clone(),
                            name: merchant_map
                                .get(merchant_id)
                                .ok_or(UserErrors::InternalServerError)?
                                .to_owned(),
                        }),
                        Some(NameIdUnit {
                            id: profile_id.clone(),
                            name: profile_map
                                .get(profile_id)
                                .ok_or(UserErrors::InternalServerError)?
                                .to_owned(),
                        }),
                    )
                }
            };

            Ok(user_api::GetUserRoleDetailsResponseV2 {
                role_id: user_role.role_id.clone(),
                org: org.clone(),
                merchant,
                profile,
                status: user_role.status.foreign_into(),
                entity_type,
                role_name: role_name_map
                    .get(&user_role.role_id)
                    .ok_or(UserErrors::InternalServerError)
                    .cloned()?,
            })
        })
        .collect::<Result<Vec<_>, UserErrors>>()?;

    Ok(ApplicationResponse::Json(role_details_list))
}

#[cfg(feature = "email")]
pub async fn verify_email_token_only_flow(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
    req: user_api::VerifyEmailRequest,
) -> UserResponse<user_api::TokenResponse> {
    let token = req.token.clone().expose();
    let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
        .await
        .change_context(UserErrors::LinkInvalid)?;

    auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;

    let user_from_email = state
        .global_store
        .find_active_user_by_user_email(&email_token.get_email()?)
        .await
        .change_context(UserErrors::InternalServerError)?;

    if user_from_email.user_id != user_token.user_id {
        return Err(UserErrors::LinkInvalid.into());
    }

    let user_from_db: domain::UserFromStorage = state
        .global_store
        .update_active_user_by_user_id(
            user_from_email.user_id.as_str(),
            storage_user::UserUpdate::VerifyUser,
        )
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
        .await
        .map_err(|error| logger::error!(?error));

    let current_flow = domain::CurrentFlow::new(user_token, domain::SPTFlow::VerifyEmail.into())?;
    let next_flow = current_flow.next(user_from_db, &state).await?;
    let token = next_flow.get_token(&state).await?;

    let response = user_api::TokenResponse {
        token: token.clone(),
        token_type: next_flow.get_flow().into(),
    };

    auth::cookies::set_cookie_response(response, token)
}

#[cfg(feature = "email")]
pub async fn send_verification_mail(
    state: SessionState,
    req: user_api::SendVerifyEmailRequest,
    auth_id: Option<String>,
    theme_id: Option<String>,
) -> UserResponse<()> {
    let user_email = domain::UserEmail::from_pii_email(req.email)?;

    utils::user::validate_email_domain_auth_type_using_db(
        &state,
        &user_email,
        UserAuthType::MagicLink,
    )
    .await?;

    let user = state
        .global_store
        .find_active_user_by_user_email(&user_email)
        .await
        .map_err(|e| {
            if e.current_context().is_db_not_found() {
                e.change_context(UserErrors::UserNotFound)
            } else {
                e.change_context(UserErrors::InternalServerError)
            }
        })?;

    if user.is_verified {
        return Err(UserErrors::UserAlreadyVerified.into());
    }

    let theme = theme_utils::get_theme_using_optional_theme_id(&state, theme_id).await?;

    let email_contents = email_types::VerifyEmail {
        recipient_email: domain::UserEmail::from_pii_email(user.email)?,
        settings: state.conf.clone(),
        auth_id,
        theme_id: theme.as_ref().map(|theme| theme.theme_id.clone()),
        theme_config: theme
            .map(|theme| theme.email_config())
            .unwrap_or(state.conf.theme.email_config.clone()),
    };

    state
        .email_client
        .compose_and_send_email(
            user_utils::get_base_url(&state),
            Box::new(email_contents),
            state.conf.proxy.https_url.as_ref(),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::StatusOk)
}

pub async fn update_user_details(
    state: SessionState,
    user_token: auth::UserFromToken,
    req: user_api::UpdateUserAccountDetailsRequest,
    _req_state: ReqState,
) -> UserResponse<()> {
    let user: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    let name = req.name.map(domain::UserName::new).transpose()?;

    let user_update = storage_user::UserUpdate::AccountUpdate {
        name: name.map(|name| name.get_secret().expose()),
        is_verified: None,
    };

    state
        .global_store
        .update_active_user_by_user_id(user.get_user_id(), user_update)
        .await
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::StatusOk)
}

#[cfg(feature = "email")]
pub async fn user_from_email(
    state: SessionState,
    req: user_api::UserFromEmailRequest,
) -> UserResponse<user_api::TokenResponse> {
    let token = req.token.expose();
    let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
        .await
        .change_context(UserErrors::LinkInvalid)?;

    auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;

    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_email(&email_token.get_email()?)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    let next_flow =
        domain::NextFlow::from_origin(email_token.get_flow(), user_from_db.clone(), &state).await?;

    let token = next_flow.get_token(&state).await?;

    let response = user_api::TokenResponse {
        token: token.clone(),
        token_type: next_flow.get_flow().into(),
    };
    auth::cookies::set_cookie_response(response, token)
}

pub async fn begin_totp(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
) -> UserResponse<user_api::BeginTotpResponse> {
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    if user_from_db.get_totp_status() == TotpStatus::Set {
        return Ok(ApplicationResponse::Json(user_api::BeginTotpResponse {
            secret: None,
        }));
    }

    let totp = tfa_utils::generate_default_totp(
        user_from_db.get_email(),
        None,
        state.conf.user.totp_issuer_name.clone(),
    )?;
    let secret = totp.get_secret_base32().into();
    tfa_utils::insert_totp_secret_in_redis(&state, &user_token.user_id, &secret).await?;

    Ok(ApplicationResponse::Json(user_api::BeginTotpResponse {
        secret: Some(user_api::TotpSecret {
            secret,
            totp_url: totp.get_url().into(),
        }),
    }))
}

pub async fn reset_totp(
    state: SessionState,
    user_token: auth::UserFromToken,
) -> UserResponse<user_api::BeginTotpResponse> {
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    if user_from_db.get_totp_status() != TotpStatus::Set {
        return Err(UserErrors::TotpNotSetup.into());
    }

    if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await?
        && !tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id).await?
    {
        return Err(UserErrors::TwoFactorAuthRequired.into());
    }

    let totp = tfa_utils::generate_default_totp(
        user_from_db.get_email(),
        None,
        state.conf.user.totp_issuer_name.clone(),
    )?;

    let secret = totp.get_secret_base32().into();
    tfa_utils::insert_totp_secret_in_redis(&state, &user_token.user_id, &secret).await?;

    Ok(ApplicationResponse::Json(user_api::BeginTotpResponse {
        secret: Some(user_api::TotpSecret {
            secret,
            totp_url: totp.get_url().into(),
        }),
    }))
}

pub async fn verify_totp(
    state: SessionState,
    user_token: auth::UserIdFromAuth,
    req: user_api::VerifyTotpRequest,
) -> UserResponse<user_api::TokenResponse> {
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    if user_from_db.get_totp_status() != TotpStatus::Set {
        return Err(UserErrors::TotpNotSetup.into());
    }

    let user_totp_attempts =
        tfa_utils::get_totp_attempts_from_redis(&state, &user_token.user_id).await?;

    if user_totp_attempts >= consts::user::TOTP_MAX_ATTEMPTS {
        return Err(UserErrors::MaxTotpAttemptsReached.into());
    }

    let user_totp_secret = user_from_db
        .decrypt_and_get_totp_secret(&state)
        .await?
        .ok_or(UserErrors::InternalServerError)?;

    let totp = tfa_utils::generate_default_totp(
        user_from_db.get_email(),
        Some(user_totp_secret),
        state.conf.user.totp_issuer_name.clone(),
    )?;

    if totp
        .generate_current()
        .change_context(UserErrors::InternalServerError)?
        != req.totp.expose()
    {
        let _ = tfa_utils::insert_totp_attempts_in_redis(
            &state,
            &user_token.user_id,
            user_totp_attempts + 1,
        )
        .await
        .inspect_err(|error| logger::error!(?error));
        return Err(UserErrors::InvalidTotp.into());
    }

    tfa_utils::insert_totp_in_redis(&state, &user_token.user_id).await?;

    Ok(ApplicationResponse::StatusOk)
}

pub async fn update_totp(
    state: SessionState,
    user_token: auth::UserIdFromAuth,
    req: user_api::VerifyTotpRequest,
) -> UserResponse<()> {
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    let new_totp_secret = tfa_utils::get_totp_secret_from_redis(&state, &user_token.user_id)
        .await?
        .ok_or(UserErrors::TotpSecretNotFound)?;

    let totp = tfa_utils::generate_default_totp(
        user_from_db.get_email(),
        Some(new_totp_secret),
        state.conf.user.totp_issuer_name.clone(),
    )?;

    if totp
        .generate_current()
        .change_context(UserErrors::InternalServerError)?
        != req.totp.expose()
    {
        return Err(UserErrors::InvalidTotp.into());
    }

    let key_store = user_from_db.get_or_create_key_store(&state).await?;

    state
        .global_store
        .update_active_user_by_user_id(
            &user_token.user_id,
            storage_user::UserUpdate::TotpUpdate {
                totp_status: None,
                totp_secret: Some(Some(
                    // TODO: Impl conversion trait for User and move this there
                    domain::types::crypto_operation::<String, hyperswitch_masking::WithType>(
                        &(&state).into(),
                        type_name!(storage_user::User),
                        domain::types::CryptoOperation::Encrypt(totp.get_secret_base32().into()),
                        Identifier::User(key_store.user_id.clone()),
                        key_store.key.peek(),
                    )
                    .await
                    .and_then(|val| val.try_into_operation())
                    .change_context(UserErrors::InternalServerError)?
                    .into(),
                )),

                totp_recovery_codes: None,
            },
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    let _ = tfa_utils::delete_totp_secret_from_redis(&state, &user_token.user_id)
        .await
        .map_err(|error| logger::error!(?error));

    // This is not the main task of this API, so we don't throw error if this fails.
    // Any following API which requires TOTP will throw error if TOTP is not set in redis
    // and FE will ask user to enter TOTP again
    let _ = tfa_utils::insert_totp_in_redis(&state, &user_token.user_id)
        .await
        .map_err(|error| logger::error!(?error));

    Ok(ApplicationResponse::StatusOk)
}

pub async fn generate_recovery_codes(
    state: SessionState,
    user_token: auth::UserIdFromAuth,
) -> UserResponse<user_api::RecoveryCodes> {
    if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await? {
        return Err(UserErrors::TotpRequired.into());
    }

    let recovery_codes = domain::RecoveryCodes::generate_new();

    state
        .global_store
        .update_active_user_by_user_id(
            &user_token.user_id,
            storage_user::UserUpdate::TotpUpdate {
                totp_status: None,
                totp_secret: None,
                totp_recovery_codes: Some(Some(
                    recovery_codes
                        .get_hashed()
                        .change_context(UserErrors::InternalServerError)?,
                )),
            },
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(user_api::RecoveryCodes {
        recovery_codes: recovery_codes.into_inner(),
    }))
}

pub async fn transfer_user_key_store_keymanager(
    state: SessionState,
    req: user_api::UserKeyTransferRequest,
) -> UserResponse<user_api::UserTransferKeyResponse> {
    let db = &state.global_store;

    let key_stores = db
        .get_all_user_key_store(
            &state.store.get_master_key().to_vec().into(),
            req.from,
            req.limit,
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(
        user_api::UserTransferKeyResponse {
            total_transferred: send_request_to_key_service_for_user(&state, key_stores)
                .await
                .change_context(UserErrors::InternalServerError)?,
        },
    ))
}

pub async fn verify_recovery_code(
    state: SessionState,
    user_token: auth::UserIdFromAuth,
    req: user_api::VerifyRecoveryCodeRequest,
) -> UserResponse<()> {
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    if user_from_db.get_totp_status() != TotpStatus::Set {
        return Err(UserErrors::TwoFactorAuthNotSetup.into());
    }

    let user_recovery_code_attempts =
        tfa_utils::get_recovery_code_attempts_from_redis(&state, &user_token.user_id).await?;

    if user_recovery_code_attempts >= consts::user::RECOVERY_CODE_MAX_ATTEMPTS {
        return Err(UserErrors::MaxRecoveryCodeAttemptsReached.into());
    }

    let mut recovery_codes = user_from_db
        .get_recovery_codes()
        .ok_or(UserErrors::InternalServerError)?;

    let Some(matching_index) = utils::user::password::get_index_for_correct_recovery_code(
        &req.recovery_code,
        &recovery_codes,
    )?
    else {
        let _ = tfa_utils::insert_recovery_code_attempts_in_redis(
            &state,
            &user_token.user_id,
            user_recovery_code_attempts + 1,
        )
        .await
        .inspect_err(|error| logger::error!(?error));
        return Err(UserErrors::InvalidRecoveryCode.into());
    };

    tfa_utils::insert_recovery_code_in_redis(&state, user_from_db.get_user_id()).await?;
    let _ = recovery_codes.remove(matching_index);

    state
        .global_store
        .update_active_user_by_user_id(
            user_from_db.get_user_id(),
            storage_user::UserUpdate::TotpUpdate {
                totp_status: None,
                totp_secret: None,
                totp_recovery_codes: Some(Some(recovery_codes)),
            },
        )
        .await
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::StatusOk)
}

pub async fn terminate_two_factor_auth(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
    skip_two_factor_auth: bool,
) -> UserResponse<user_api::TokenResponse> {
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    if state.conf.user.force_two_factor_auth || !skip_two_factor_auth {
        if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await?
            && !tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id).await?
        {
            return Err(UserErrors::TwoFactorAuthRequired.into());
        }

        if user_from_db.get_recovery_codes().is_none() {
            return Err(UserErrors::TwoFactorAuthNotSetup.into());
        }

        if user_from_db.get_totp_status() != TotpStatus::Set {
            state
                .global_store
                .update_active_user_by_user_id(
                    user_from_db.get_user_id(),
                    storage_user::UserUpdate::TotpUpdate {
                        totp_status: Some(TotpStatus::Set),
                        totp_secret: None,
                        totp_recovery_codes: None,
                    },
                )
                .await
                .change_context(UserErrors::InternalServerError)?;
        }
    }

    let current_flow = domain::CurrentFlow::new(user_token.clone(), domain::SPTFlow::TOTP.into())?;
    let next_flow = current_flow.next(user_from_db, &state).await?;
    let token = next_flow.get_token(&state).await?;

    let _ = tfa_utils::delete_totp_attempts_from_redis(&state, &user_token.user_id)
        .await
        .inspect_err(|error| logger::error!(?error));
    let _ = tfa_utils::delete_recovery_code_attempts_from_redis(&state, &user_token.user_id)
        .await
        .inspect_err(|error| logger::error!(?error));

    auth::cookies::set_cookie_response(
        user_api::TokenResponse {
            token: token.clone(),
            token_type: next_flow.get_flow().into(),
        },
        token,
    )
}

pub async fn check_two_factor_auth_status(
    state: SessionState,
    user_token: auth::UserFromToken,
) -> UserResponse<user_api::TwoFactorAuthStatusResponse> {
    Ok(ApplicationResponse::Json(
        user_api::TwoFactorAuthStatusResponse {
            totp: tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await?,
            recovery_code: tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id)
                .await?,
        },
    ))
}

pub async fn check_two_factor_auth_status_with_attempts(
    state: SessionState,
    user_token: auth::UserIdFromAuth,
) -> UserResponse<user_api::TwoFactorStatus> {
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    let is_skippable = state.conf.user.force_two_factor_auth.not();
    if user_from_db.get_totp_status() == TotpStatus::NotSet {
        return Ok(ApplicationResponse::Json(user_api::TwoFactorStatus {
            status: None,
            is_skippable,
        }));
    };

    let totp = user_api::TwoFactorAuthAttempts {
        is_completed: tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await?,
        remaining_attempts: consts::user::TOTP_MAX_ATTEMPTS
            - tfa_utils::get_totp_attempts_from_redis(&state, &user_token.user_id).await?,
    };
    let recovery_code = user_api::TwoFactorAuthAttempts {
        is_completed: tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id).await?,
        remaining_attempts: consts::user::RECOVERY_CODE_MAX_ATTEMPTS
            - tfa_utils::get_recovery_code_attempts_from_redis(&state, &user_token.user_id).await?,
    };
    Ok(ApplicationResponse::Json(user_api::TwoFactorStatus {
        status: Some(user_api::TwoFactorAuthStatusResponseWithAttempts {
            totp,
            recovery_code,
        }),
        is_skippable,
    }))
}

pub async fn create_user_authentication_method(
    state: SessionState,
    req: user_api::CreateUserAuthenticationMethodRequest,
) -> UserResponse<user_api::CreateUserAuthenticationMethodResponse> {
    let user_auth_encryption_key = hex::decode(
        state
            .conf
            .user_auth_methods
            .get_inner()
            .encryption_key
            .clone()
            .expose(),
    )
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Failed to decode DEK")?;

    let id = uuid::Uuid::new_v4().to_string();

    let (private_config, public_config) = utils::user::construct_public_and_private_db_configs(
        &state,
        &req.auth_method,
        &user_auth_encryption_key,
        id.clone(),
    )
    .await?;

    let auth_methods = state
        .store
        .list_user_authentication_methods_for_owner_id(&req.owner_id)
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Failed to get list of auth methods for the owner id")?;

    let (auth_id, email_domain) = if let Some(auth_method) = auth_methods.first() {
        let email_domain = match &req.email_domain {
            Some(email_domain) => {
                if email_domain != &auth_method.email_domain {
                    return Err(report!(UserErrors::InvalidAuthMethodOperationWithMessage(
                        "Email domain mismatch".to_string(),
                    )));
                }
                email_domain.clone()
            }
            None => auth_method.email_domain.clone(),
        };

        (auth_method.auth_id.clone(), email_domain)
    } else {
        let email_domain =
            req.email_domain
                .ok_or(UserErrors::InvalidAuthMethodOperationWithMessage(
                    "Email domain not found".to_string(),
                ))?;
        (uuid::Uuid::new_v4().to_string(), email_domain)
    };

    for db_auth_method in auth_methods {
        let is_type_same = db_auth_method.auth_type == (&req.auth_method).foreign_into();
        let is_extra_identifier_same = match &req.auth_method {
            user_api::AuthConfig::OpenIdConnect { public_config, .. } => {
                let db_auth_name = db_auth_method
                    .public_config
                    .map(|config| {
                        utils::user::parse_value::<user_api::OpenIdConnectPublicConfig>(
                            config,
                            "OpenIdConnectPublicConfig",
                        )
                    })
                    .transpose()?
                    .map(|config| config.name);
                db_auth_name.is_some_and(|name| name == public_config.name)
            }
            user_api::AuthConfig::Password | user_api::AuthConfig::MagicLink => true,
        };
        if is_type_same && is_extra_identifier_same {
            return Err(report!(UserErrors::UserAuthMethodAlreadyExists));
        }
    }

    let now = common_utils::date_time::now();
    let inserted_auth_method = state
        .store
        .insert_user_authentication_method(UserAuthenticationMethodNew {
            id,
            auth_id,
            owner_id: req.owner_id,
            owner_type: req.owner_type,
            auth_type: (&req.auth_method).foreign_into(),
            private_config,
            public_config,
            allow_signup: req.allow_signup,
            created_at: now,
            last_modified_at: now,
            email_domain,
        })
        .await
        .to_duplicate_response(UserErrors::UserAuthMethodAlreadyExists)?;

    Ok(ApplicationResponse::Json(
        user_api::CreateUserAuthenticationMethodResponse {
            id: inserted_auth_method.id,
            auth_id: inserted_auth_method.auth_id,
            owner_id: inserted_auth_method.owner_id,
            owner_type: inserted_auth_method.owner_type,
            auth_type: inserted_auth_method.auth_type,
            email_domain: Some(inserted_auth_method.email_domain),
            allow_signup: inserted_auth_method.allow_signup,
        },
    ))
}

pub async fn update_user_authentication_method(
    state: SessionState,
    req: user_api::UpdateUserAuthenticationMethodRequest,
) -> UserResponse<()> {
    let user_auth_encryption_key = hex::decode(
        state
            .conf
            .user_auth_methods
            .get_inner()
            .encryption_key
            .clone()
            .expose(),
    )
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Failed to decode DEK")?;

    match req {
        user_api::UpdateUserAuthenticationMethodRequest::AuthMethod {
            id,
            auth_config: auth_method,
        } => {
            let (private_config, public_config) =
                utils::user::construct_public_and_private_db_configs(
                    &state,
                    &auth_method,
                    &user_auth_encryption_key,
                    id.clone(),
                )
                .await?;

            state
                .store
                .update_user_authentication_method(
                    &id,
                    UserAuthenticationMethodUpdate::UpdateConfig {
                        private_config,
                        public_config,
                    },
                )
                .await
                .map_err(|error| {
                    let user_error = match error.current_context() {
                        StorageError::ValueNotFound(_) => {
                            UserErrors::InvalidAuthMethodOperationWithMessage(
                                "Auth method not found".to_string(),
                            )
                        }
                        StorageError::DuplicateValue { .. } => {
                            UserErrors::UserAuthMethodAlreadyExists
                        }
                        _ => UserErrors::InternalServerError,
                    };
                    error.change_context(user_error)
                })?;
        }
        user_api::UpdateUserAuthenticationMethodRequest::EmailDomain {
            owner_id,
            email_domain,
        } => {
            let auth_methods = state
                .store
                .list_user_authentication_methods_for_owner_id(&owner_id)
                .await
                .change_context(UserErrors::InternalServerError)?;

            futures::future::try_join_all(auth_methods.iter().map(|auth_method| async {
                state
                    .store
                    .update_user_authentication_method(
                        &auth_method.id,
                        UserAuthenticationMethodUpdate::EmailDomain {
                            email_domain: email_domain.clone(),
                        },
                    )
                    .await
                    .to_duplicate_response(UserErrors::UserAuthMethodAlreadyExists)
            }))
            .await?;
        }
    }

    Ok(ApplicationResponse::StatusOk)
}

pub async fn list_user_authentication_methods(
    state: SessionState,
    req: user_api::GetUserAuthenticationMethodsRequest,
) -> UserResponse<Vec<user_api::UserAuthenticationMethodResponse>> {
    let user_authentication_methods = match (req.auth_id, req.email_domain) {
        (Some(auth_id), None) => state
            .store
            .list_user_authentication_methods_for_auth_id(&auth_id)
            .await
            .change_context(UserErrors::InternalServerError)?,
        (None, Some(email_domain)) => state
            .store
            .list_user_authentication_methods_for_email_domain(&email_domain)
            .await
            .change_context(UserErrors::InternalServerError)?,
        (Some(_), Some(_)) | (None, None) => {
            return Err(UserErrors::InvalidUserAuthMethodOperation.into());
        }
    };

    Ok(ApplicationResponse::Json(
        user_authentication_methods
            .into_iter()
            .map(|auth_method| {
                let auth_name = match (auth_method.auth_type, auth_method.public_config) {
                    (UserAuthType::OpenIdConnect, config) => {
                        let open_id_public_config: Option<user_api::OpenIdConnectPublicConfig> =
                            config
                                .map(|config| {
                                    utils::user::parse_value(config, "OpenIdConnectPublicConfig")
                                })
                                .transpose()?;
                        if let Some(public_config) = open_id_public_config {
                            Ok(Some(public_config.name))
                        } else {
                            Err(report!(UserErrors::InternalServerError))
                                .attach_printable("Public config not found for OIDC auth type")
                        }
                    }
                    _ => Ok(None),
                }?;

                Ok(user_api::UserAuthenticationMethodResponse {
                    id: auth_method.id,
                    auth_id: auth_method.auth_id,
                    auth_method: user_api::AuthMethodDetails {
                        name: auth_name,
                        auth_type: auth_method.auth_type,
                    },
                    allow_signup: auth_method.allow_signup,
                })
            })
            .collect::<UserResult<_>>()?,
    ))
}

#[cfg(feature = "v1")]
pub async fn get_sso_auth_url(
    state: SessionState,
    request: user_api::GetSsoAuthUrlRequest,
) -> UserResponse<()> {
    let user_authentication_method = state
        .store
        .get_user_authentication_method_by_id(request.id.as_str())
        .await
        .to_not_found_response(UserErrors::InvalidUserAuthMethodOperation)?;

    let open_id_private_config = utils::user::decrypt_oidc_private_config(
        &state,
        user_authentication_method.private_config,
        request.id.clone(),
    )
    .await?;

    let open_id_public_config = serde_json::from_value::<user_api::OpenIdConnectPublicConfig>(
        user_authentication_method
            .public_config
            .ok_or(UserErrors::InternalServerError)
            .attach_printable("Public config not present")?,
    )
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Unable to parse OpenIdConnectPublicConfig")?;

    let oidc_state = Secret::new(nanoid::nanoid!());
    utils::user::set_sso_id_in_redis(&state, oidc_state.clone(), request.id).await?;

    let redirect_url =
        utils::user::get_oidc_sso_redirect_url(&state, &open_id_public_config.name.to_string());

    openidconnect::get_authorization_url(
        state,
        redirect_url,
        oidc_state,
        open_id_private_config.base_url.into(),
        open_id_private_config.client_id,
    )
    .await
    .map(|url| {
        ApplicationResponse::JsonForRedirection(RedirectionResponse {
            headers: Vec::with_capacity(0),
            return_url: String::new(),
            http_method: String::new(),
            params: Vec::with_capacity(0),
            return_url_with_query_params: url.to_string(),
        })
    })
}

pub async fn sso_sign(
    state: SessionState,
    request: user_api::SsoSignInRequest,
    user_from_single_purpose_token: Option<auth::UserFromSinglePurposeToken>,
) -> UserResponse<user_api::TokenResponse> {
    let authentication_method_id =
        utils::user::get_sso_id_from_redis(&state, request.state.clone()).await?;

    let user_authentication_method = state
        .store
        .get_user_authentication_method_by_id(&authentication_method_id)
        .await
        .change_context(UserErrors::InternalServerError)?;

    let open_id_private_config = utils::user::decrypt_oidc_private_config(
        &state,
        user_authentication_method.private_config,
        authentication_method_id,
    )
    .await?;

    let open_id_public_config = serde_json::from_value::<user_api::OpenIdConnectPublicConfig>(
        user_authentication_method
            .public_config
            .ok_or(UserErrors::InternalServerError)
            .attach_printable("Public config not present")?,
    )
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Unable to parse OpenIdConnectPublicConfig")?;

    let redirect_url =
        utils::user::get_oidc_sso_redirect_url(&state, &open_id_public_config.name.to_string());
    let email = openidconnect::get_user_email_from_oidc_provider(
        &state,
        redirect_url,
        request.state,
        open_id_private_config.base_url.into(),
        open_id_private_config.client_id,
        request.code,
        open_id_private_config.client_secret,
    )
    .await?;

    utils::user::validate_email_domain_auth_type_using_db(
        &state,
        &email,
        UserAuthType::OpenIdConnect,
    )
    .await?;

    // TODO: Use config to handle not found error
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_email(&email)
        .await
        .map(Into::into)
        .to_not_found_response(UserErrors::UserNotFound)?;

    if !user_from_db.is_verified() {
        state
            .global_store
            .update_active_user_by_user_id(
                user_from_db.get_user_id(),
                storage_user::UserUpdate::VerifyUser,
            )
            .await
            .change_context(UserErrors::InternalServerError)?;
    }

    let next_flow = if let Some(user_from_single_purpose_token) = user_from_single_purpose_token {
        let current_flow =
            domain::CurrentFlow::new(user_from_single_purpose_token, domain::SPTFlow::SSO.into())?;
        current_flow.next(user_from_db, &state).await?
    } else {
        domain::NextFlow::from_origin(domain::Origin::SignInWithSSO, user_from_db, &state).await?
    };

    let token = next_flow.get_token(&state).await?;
    let response = user_api::TokenResponse {
        token: token.clone(),
        token_type: next_flow.get_flow().into(),
    };

    auth::cookies::set_cookie_response(response, token)
}

pub async fn terminate_auth_select(
    state: SessionState,
    user_token: auth::UserFromSinglePurposeToken,
    req: user_api::AuthSelectRequest,
) -> UserResponse<user_api::TokenResponse> {
    let user_from_db: domain::UserFromStorage = state
        .global_store
        .find_active_user_by_user_id(&user_token.user_id)
        .await
        .change_context(UserErrors::InternalServerError)?
        .into();

    let user_email = domain::UserEmail::from_pii_email(user_from_db.get_email())?;
    let auth_methods = state
        .store
        .list_user_authentication_methods_for_email_domain(user_email.extract_domain()?)
        .await
        .change_context(UserErrors::InternalServerError)?;

    let user_authentication_method = match (req.id, auth_methods.is_empty()) {
        (Some(id), _) => auth_methods
            .into_iter()
            .find(|auth_method| auth_method.id == id)
            .ok_or(UserErrors::InvalidUserAuthMethodOperation)?,
        (None, true) => DEFAULT_USER_AUTH_METHOD.clone(),
        (None, false) => return Err(UserErrors::InvalidUserAuthMethodOperation.into()),
    };

    let current_flow = domain::CurrentFlow::new(user_token, domain::SPTFlow::AuthSelect.into())?;
    let mut next_flow = current_flow.next(user_from_db.clone(), &state).await?;

    // Skip SSO if continue with password(TOTP)
    if next_flow.get_flow() == domain::UserFlow::SPTFlow(domain::SPTFlow::SSO)
        && !utils::user::is_sso_auth_type(user_authentication_method.auth_type)
    {
        next_flow = next_flow.skip(user_from_db, &state).await?;
    }
    let token = next_flow.get_token(&state).await?;

    auth::cookies::set_cookie_response(
        user_api::TokenResponse {
            token: token.clone(),
            token_type: next_flow.get_flow().into(),
        },
        token,
    )
}

pub async fn list_orgs_for_user(
    state: SessionState,
    user_from_token: auth::UserFromToken,
) -> UserResponse<Vec<user_api::ListOrgsForUserResponse>> {
    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)?;

    if role_info.is_internal() {
        return Err(UserErrors::InvalidRoleOperationWithMessage(
            "Internal roles are not allowed for this operation".to_string(),
        )
        .into());
    }
    let orgs = match role_info.get_entity_type() {
        EntityType::Tenant => state
            .store
            .list_merchant_and_org_ids(consts::user::ORG_LIST_LIMIT_FOR_TENANT, None)
            .await
            .change_context(UserErrors::InternalServerError)?
            .into_iter()
            .map(|(_, org_id)| org_id)
            .collect::<HashSet<_>>(),
        EntityType::Organization | EntityType::Merchant | EntityType::Profile => state
            .global_store
            .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                user_id: user_from_token.user_id.as_str(),
                tenant_id: user_from_token
                    .tenant_id
                    .as_ref()
                    .unwrap_or(&state.tenant.tenant_id),
                org_id: None,
                merchant_id: None,
                profile_id: None,
                entity_id: None,
                version: None,
                status: Some(UserStatus::Active),
                limit: None,
            })
            .await
            .change_context(UserErrors::InternalServerError)?
            .into_iter()
            .filter_map(|user_role| user_role.org_id)
            .collect::<HashSet<_>>(),
    };

    let resp = futures::future::try_join_all(
        orgs.iter()
            .map(|org_id| state.accounts_store.find_organization_by_org_id(org_id)),
    )
    .await
    .change_context(UserErrors::InternalServerError)?
    .into_iter()
    .map(|org| user_api::ListOrgsForUserResponse {
        org_id: org.get_organization_id(),
        org_name: org.get_organization_name(),
        org_type: org.organization_type.unwrap_or_default(),
    })
    .collect::<Vec<_>>();

    if resp.is_empty() {
        Err(UserErrors::InternalServerError).attach_printable("No orgs found for a user")?;
    }

    Ok(ApplicationResponse::Json(resp))
}

pub async fn list_merchants_for_user_in_org(
    state: SessionState,
    user_from_token: auth::UserFromToken,
) -> UserResponse<Vec<user_api::UserMerchantAccountResponse>> {
    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)?;

    if role_info.is_internal() {
        return Err(UserErrors::InvalidRoleOperationWithMessage(
            "Internal roles are not allowed for this operation".to_string(),
        )
        .into());
    }

    let merchant_accounts = match role_info.get_entity_type() {
        EntityType::Tenant | EntityType::Organization => state
            .store
            .list_merchant_accounts_by_organization_id(&user_from_token.org_id)
            .await
            .change_context(UserErrors::InternalServerError)?,
        EntityType::Merchant | EntityType::Profile => {
            let merchant_ids = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                    user_id: user_from_token.user_id.as_str(),
                    tenant_id: user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: Some(&user_from_token.org_id),
                    merchant_id: None,
                    profile_id: None,
                    entity_id: None,
                    version: None,
                    status: Some(UserStatus::Active),
                    limit: None,
                })
                .await
                .change_context(UserErrors::InternalServerError)?
                .into_iter()
                .filter_map(|user_role| user_role.merchant_id)
                .collect::<HashSet<_>>()
                .into_iter()
                .collect();

            state
                .store
                .list_multiple_merchant_accounts(merchant_ids)
                .await
                .change_context(UserErrors::InternalServerError)?
        }
    };

    // TODO: Add a check to see if merchant accounts are empty, and handle accordingly, when a single route will be used instead
    // of having two separate routes.

    Ok(ApplicationResponse::Json(
        merchant_accounts
            .into_iter()
            .map(|merchant_account| user_api::UserMerchantAccountResponse {
                merchant_name: merchant_account.merchant_name.clone(),
                merchant_id: merchant_account.get_id().to_owned(),
                product_type: merchant_account.product_type,
                merchant_account_type: merchant_account.merchant_account_type,
                version: merchant_account.version,
            })
            .collect::<Vec<_>>(),
    ))
}

pub async fn list_profiles_for_user_in_org_and_merchant_account(
    state: SessionState,
    user_from_token: auth::UserFromToken,
) -> UserResponse<Vec<user_api::ListProfilesForUserInOrgAndMerchantAccountResponse>> {
    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)?;

    let key_store = state
        .store
        .get_merchant_key_store_by_merchant_id(
            &user_from_token.merchant_id,
            &state.store.get_master_key().to_vec().into(),
        )
        .await
        .change_context(UserErrors::InternalServerError)?;
    let profiles = match role_info.get_entity_type() {
        EntityType::Tenant | EntityType::Organization | EntityType::Merchant => state
            .store
            .list_profile_by_merchant_id(&key_store, &user_from_token.merchant_id)
            .await
            .change_context(UserErrors::InternalServerError)?,
        EntityType::Profile => {
            let profile_ids = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                    user_id: user_from_token.user_id.as_str(),
                    tenant_id: user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: Some(&user_from_token.org_id),
                    merchant_id: Some(&user_from_token.merchant_id),
                    profile_id: None,
                    entity_id: None,
                    version: None,
                    status: Some(UserStatus::Active),
                    limit: None,
                })
                .await
                .change_context(UserErrors::InternalServerError)?
                .into_iter()
                .filter_map(|user_role| user_role.profile_id)
                .collect::<HashSet<_>>();

            futures::future::try_join_all(profile_ids.iter().map(|profile_id| {
                state
                    .store
                    .find_business_profile_by_profile_id(&key_store, profile_id)
            }))
            .await
            .change_context(UserErrors::InternalServerError)?
        }
    };

    if profiles.is_empty() {
        Err(UserErrors::InternalServerError).attach_printable("No profile found for a user")?;
    }

    Ok(ApplicationResponse::Json(
        profiles
            .into_iter()
            .map(
                |profile| user_api::ListProfilesForUserInOrgAndMerchantAccountResponse {
                    profile_id: profile.get_id().to_owned(),
                    profile_name: profile.profile_name,
                },
            )
            .collect::<Vec<_>>(),
    ))
}

pub async fn switch_org_for_user(
    state: SessionState,
    request: user_api::SwitchOrganizationRequest,
    user_from_token: auth::UserFromToken,
) -> UserResponse<user_api::TokenResponse> {
    if user_from_token.org_id == request.org_id {
        return Err(UserErrors::InvalidRoleOperationWithMessage(
            "User switching to same org".to_string(),
        )
        .into());
    }

    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Failed to retrieve role information")?;

    if role_info.is_internal() {
        return Err(UserErrors::InvalidRoleOperationWithMessage(
            "Org switching not allowed for Internal role".to_string(),
        )
        .into());
    }

    let (merchant_id, profile_id, role_id) = match role_info.get_entity_type() {
        EntityType::Tenant => {
            let merchant_id = state
                .store
                .list_merchant_accounts_by_organization_id(&request.org_id)
                .await
                .change_context(UserErrors::InternalServerError)
                .attach_printable("Failed to get merchant list for org")?
                .pop()
                .ok_or(UserErrors::InvalidRoleOperation)
                .attach_printable("No merchants found for the org id")?
                .get_id()
                .to_owned();

            let key_store = state
                .store
                .get_merchant_key_store_by_merchant_id(
                    &merchant_id,
                    &state.store.get_master_key().to_vec().into(),
                )
                .await
                .change_context(UserErrors::InternalServerError)?;

            let profile_id = state
                .store
                .list_profile_by_merchant_id(&key_store, &merchant_id)
                .await
                .change_context(UserErrors::InternalServerError)?
                .pop()
                .ok_or(UserErrors::InternalServerError)?
                .get_id()
                .to_owned();

            (merchant_id, profile_id, user_from_token.role_id)
        }
        EntityType::Organization | EntityType::Merchant | EntityType::Profile => {
            let user_role = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                    user_id: &user_from_token.user_id,
                    tenant_id: user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: Some(&request.org_id),
                    merchant_id: None,
                    profile_id: None,
                    entity_id: None,
                    version: None,
                    status: Some(UserStatus::Active),
                    limit: Some(1),
                })
                .await
                .change_context(UserErrors::InternalServerError)
                .attach_printable("Failed to list user roles by user_id and org_id")?
                .pop()
                .ok_or(UserErrors::InvalidRoleOperationWithMessage(
                    "No user role found for the requested org_id".to_string(),
                ))?;

            let (merchant_id, profile_id) =
                utils::user_role::get_single_merchant_id_and_profile_id(&state, &user_role).await?;

            (merchant_id, profile_id, user_role.role_id)
        }
    };

    let lineage_context = LineageContext {
        user_id: user_from_token.user_id.clone(),
        merchant_id: merchant_id.clone(),
        role_id: role_id.clone(),
        org_id: request.org_id.clone(),
        profile_id: profile_id.clone(),
        tenant_id: user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id)
            .clone(),
    };

    utils::user::spawn_async_lineage_context_update_to_db(
        &state,
        &user_from_token.user_id,
        lineage_context,
    );

    let token = utils::user::generate_jwt_auth_token_with_attributes(
        &state,
        user_from_token.user_id,
        merchant_id.clone(),
        request.org_id.clone(),
        role_id.clone(),
        profile_id.clone(),
        user_from_token.tenant_id.clone(),
    )
    .await?;

    utils::user_role::set_role_info_in_cache_by_role_id_org_id(
        &state,
        &role_id,
        &request.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await;

    let response = user_api::TokenResponse {
        token: token.clone(),
        token_type: common_enums::TokenPurpose::UserInfo,
    };

    auth::cookies::set_cookie_response(response, token)
}

pub async fn switch_merchant_for_user_in_org(
    state: SessionState,
    request: user_api::SwitchMerchantRequest,
    user_from_token: auth::UserFromToken,
) -> UserResponse<user_api::TokenResponse> {
    if user_from_token.merchant_id == request.merchant_id {
        return Err(UserErrors::InvalidRoleOperationWithMessage(
            "User switching to same merchant".to_string(),
        )
        .into());
    }

    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Failed to retrieve role information")?;

    // Check if the role is internal and handle separately
    let (org_id, merchant_id, profile_id, role_id) = if role_info.is_internal() {
        let merchant_key_store = state
            .store
            .get_merchant_key_store_by_merchant_id(
                &request.merchant_id,
                &state.store.get_master_key().to_vec().into(),
            )
            .await
            .to_not_found_response(UserErrors::MerchantIdNotFound)?;

        let merchant_account = state
            .store
            .find_merchant_account_by_merchant_id(&request.merchant_id, &merchant_key_store)
            .await
            .to_not_found_response(UserErrors::MerchantIdNotFound)?;

        let profile_id = state
            .store
            .list_profile_by_merchant_id(&merchant_key_store, &request.merchant_id)
            .await
            .change_context(UserErrors::InternalServerError)
            .attach_printable("Failed to list business profiles by merchant_id")?
            .pop()
            .ok_or(UserErrors::InternalServerError)
            .attach_printable("No business profile found for the given merchant_id")?
            .get_id()
            .to_owned();

        (
            merchant_account.organization_id,
            request.merchant_id,
            profile_id,
            user_from_token.role_id.clone(),
        )
    } else {
        // Match based on the other entity types
        match role_info.get_entity_type() {
            EntityType::Tenant | EntityType::Organization => {
                let merchant_key_store = state
                    .store
                    .get_merchant_key_store_by_merchant_id(
                        &request.merchant_id,
                        &state.store.get_master_key().to_vec().into(),
                    )
                    .await
                    .to_not_found_response(UserErrors::MerchantIdNotFound)?;

                let merchant_id = state
                    .store
                    .find_merchant_account_by_merchant_id(&request.merchant_id, &merchant_key_store)
                    .await
                    .change_context(UserErrors::MerchantIdNotFound)?
                    .organization_id
                    .eq(&user_from_token.org_id)
                    .then(|| request.merchant_id.clone())
                    .ok_or_else(|| {
                        UserErrors::InvalidRoleOperationWithMessage(
                            "No such merchant_id found for the user in the org".to_string(),
                        )
                    })?;

                let profile_id = state
                    .store
                    .list_profile_by_merchant_id(&merchant_key_store, &merchant_id)
                    .await
                    .change_context(UserErrors::InternalServerError)
                    .attach_printable("Failed to list business profiles by merchant_id")?
                    .pop()
                    .ok_or(UserErrors::InternalServerError)
                    .attach_printable("No business profile found for the merchant_id")?
                    .get_id()
                    .to_owned();
                (
                    user_from_token.org_id.clone(),
                    merchant_id,
                    profile_id,
                    user_from_token.role_id.clone(),
                )
            }

            EntityType::Merchant | EntityType::Profile => {
                let user_role = state
                    .global_store
                    .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
                        user_id: &user_from_token.user_id,
                        tenant_id: user_from_token
                            .tenant_id
                            .as_ref()
                            .unwrap_or(&state.tenant.tenant_id),
                        org_id: Some(&user_from_token.org_id),
                        merchant_id: Some(&request.merchant_id),
                        profile_id: None,
                        entity_id: None,
                        version: None,
                        status: Some(UserStatus::Active),
                        limit: Some(1),
                    })
                    .await
                    .change_context(UserErrors::InternalServerError)
                    .attach_printable(
                        "Failed to list user roles for the given user_id, org_id and merchant_id",
                    )?
                    .pop()
                    .ok_or(UserErrors::InvalidRoleOperationWithMessage(
                        "No user role associated with the requested merchant_id".to_string(),
                    ))?;

                let (merchant_id, profile_id) =
                    utils::user_role::get_single_merchant_id_and_profile_id(&state, &user_role)
                        .await?;
                (
                    user_from_token.org_id,
                    merchant_id,
                    profile_id,
                    user_role.role_id,
                )
            }
        }
    };

    let lineage_context = LineageContext {
        user_id: user_from_token.user_id.clone(),
        merchant_id: merchant_id.clone(),
        role_id: role_id.clone(),
        org_id: org_id.clone(),
        profile_id: profile_id.clone(),
        tenant_id: user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id)
            .clone(),
    };

    utils::user::spawn_async_lineage_context_update_to_db(
        &state,
        &user_from_token.user_id,
        lineage_context,
    );

    let token = utils::user::generate_jwt_auth_token_with_attributes(
        &state,
        user_from_token.user_id,
        merchant_id.clone(),
        org_id.clone(),
        role_id.clone(),
        profile_id,
        user_from_token.tenant_id.clone(),
    )
    .await?;

    utils::user_role::set_role_info_in_cache_by_role_id_org_id(
        &state,
        &role_id,
        &org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await;

    let response = user_api::TokenResponse {
        token: token.clone(),
        token_type: common_enums::TokenPurpose::UserInfo,
    };

    auth::cookies::set_cookie_response(response, token)
}

pub async fn switch_profile_for_user_in_org_and_merchant(
    state: SessionState,
    request: user_api::SwitchProfileRequest,
    user_from_token: auth::UserFromToken,
) -> UserResponse<user_api::TokenResponse> {
    if user_from_token.profile_id == request.profile_id {
        return Err(UserErrors::InvalidRoleOperationWithMessage(
            "User switching to same profile".to_string(),
        )
        .into());
    }

    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
        &state,
        &user_from_token.role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await
    .change_context(UserErrors::InternalServerError)
    .attach_printable("Failed to retrieve role information")?;

    let (profile_id, role_id) = match role_info.get_entity_type() {
        EntityType::Tenant | EntityType::Organization | EntityType::Merchant => {
            let merchant_key_store = state
                .store
                .get_merchant_key_store_by_merchant_id(
                    &user_from_token.merchant_id,
                    &state.store.get_master_key().to_vec().into(),
                )
                .await
                .change_context(UserErrors::InternalServerError)
                .attach_printable("Failed to retrieve merchant key store by merchant_id")?;

            let profile_id = state
                .store
                .find_business_profile_by_merchant_id_profile_id(
                    &merchant_key_store,
                    &user_from_token.merchant_id,
                    &request.profile_id,
                )
                .await
                .change_context(UserErrors::InvalidRoleOperationWithMessage(
                    "No such profile found for the merchant".to_string(),
                ))?
                .get_id()
                .to_owned();
            (profile_id, user_from_token.role_id)
        }

        EntityType::Profile => {
            let user_role = state
                .global_store
                .list_user_roles_by_user_id(ListUserRolesByUserIdPayload{
                    user_id:&user_from_token.user_id,
                    tenant_id: user_from_token
                        .tenant_id
                        .as_ref()
                        .unwrap_or(&state.tenant.tenant_id),
                    org_id: Some(&user_from_token.org_id),
                    merchant_id: Some(&user_from_token.merchant_id),
                    profile_id:Some(&request.profile_id),
                    entity_id: None,
                    version:None,
                    status: Some(UserStatus::Active),
                    limit: Some(1)
                }
                )
                .await
                .change_context(UserErrors::InternalServerError)
                .attach_printable("Failed to list user roles for the given user_id, org_id, merchant_id and profile_id")?
                .pop()
                .ok_or(UserErrors::InvalidRoleOperationWithMessage(
                    "No user role associated with the profile".to_string(),
                ))?;

            (request.profile_id, user_role.role_id)
        }
    };

    let lineage_context = LineageContext {
        user_id: user_from_token.user_id.clone(),
        merchant_id: user_from_token.merchant_id.clone(),
        role_id: role_id.clone(),
        org_id: user_from_token.org_id.clone(),
        profile_id: profile_id.clone(),
        tenant_id: user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id)
            .clone(),
    };

    utils::user::spawn_async_lineage_context_update_to_db(
        &state,
        &user_from_token.user_id,
        lineage_context,
    );

    let token = utils::user::generate_jwt_auth_token_with_attributes(
        &state,
        user_from_token.user_id,
        user_from_token.merchant_id.clone(),
        user_from_token.org_id.clone(),
        role_id.clone(),
        profile_id,
        user_from_token.tenant_id.clone(),
    )
    .await?;

    utils::user_role::set_role_info_in_cache_by_role_id_org_id(
        &state,
        &role_id,
        &user_from_token.org_id,
        user_from_token
            .tenant_id
            .as_ref()
            .unwrap_or(&state.tenant.tenant_id),
    )
    .await;

    let response = user_api::TokenResponse {
        token: token.clone(),
        token_type: common_enums::TokenPurpose::UserInfo,
    };

    auth::cookies::set_cookie_response(response, token)
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn clone_connector(
    state: SessionState,
    user_from_token: auth::UserFromToken,
    request: user_api::CloneConnectorRequest,
) -> UserResponse<api_models::admin::MerchantConnectorResponse> {
    let Some(allowlist) = &state.conf.clone_connector_allowlist else {
        return Err(UserErrors::InvalidCloneConnectorOperation(
            "Connector cloning is not enabled".to_string(),
        )
        .into());
    };

    fp_utils::when(
        user_from_token.profile_id == request.destination_profile_id,
        || {
            Err(UserErrors::InvalidCloneConnectorOperation(
                "Source and destination profiles cannot be the same".to_string(),
            ))
        },
    )?;

    let key_store = state
        .store
        .get_merchant_key_store_by_merchant_id(
            &user_from_token.merchant_id,
            &state.store.get_master_key().to_vec().into(),
        )
        .await
        .to_not_found_response(UserErrors::InvalidCloneConnectorOperation(
            "Merchant account not found".to_string(),
        ))?;

    let source_mca = state
        .store
        .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
            &user_from_token.merchant_id,
            &request.source_mca_id,
            &key_store,
        )
        .await
        .to_not_found_response(UserErrors::InvalidCloneConnectorOperation(
            "Source merchant connector account not found".to_string(),
        ))?;

    fp_utils::when(source_mca.profile_id != user_from_token.profile_id, || {
        Err(UserErrors::InvalidCloneConnectorOperation(
            "Source connector does not belong to the current profile".to_string(),
        ))
    })?;

    state
        .store
        .find_business_profile_by_merchant_id_profile_id(
            &key_store,
            &user_from_token.merchant_id,
            &request.destination_profile_id,
        )
        .await
        .to_not_found_response(UserErrors::InvalidCloneConnectorOperation(
            "Destination profile does not belong to the merchant".to_string(),
        ))?;

    let source_connector = source_mca
        .connector_name
        .parse::<connector_enums::Connector>()
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Invalid connector name received")?;

    fp_utils::when(
        allowlist.connector_names.contains(&source_connector).not(),
        || {
            Err(UserErrors::InvalidCloneConnectorOperation(
                "Cloning is not allowed for this connector".to_string(),
            ))
        },
    )?;

    let merchant_connector_create = utils::user::build_cloned_connector_create_request(
        source_mca,
        request.destination_profile_id.clone(),
        request.connector_label,
        &allowlist.payment_method_types,
    )
    .await?;

    let merchant_account = state
        .store
        .find_merchant_account_by_merchant_id(&user_from_token.merchant_id, &key_store)
        .await
        .to_not_found_response(UserErrors::InvalidCloneConnectorOperation(
            "Merchant account not found".to_string(),
        ))?;

    let platform = domain::Platform::new(
        merchant_account.clone(),
        key_store.clone(),
        merchant_account.clone(),
        key_store.clone(),
        None,
    );

    admin::create_connector(
        state,
        merchant_connector_create,
        platform.get_processor().clone(),
        Some(request.destination_profile_id),
    )
    .await
    .map_err(|e| {
        let message = e.current_context().error_message();
        e.change_context(UserErrors::ErrorCloningConnector(message))
    })
    .attach_printable("Failed to create cloned connector")
}

#[cfg(feature = "v1")]
pub async fn issue_embedded_token(
    state: SessionState,
    processor: domain::Processor,
    business_profile: Option<domain::Profile>,
) -> UserResponse<user_api::IssueEmbeddedTokenResponse> {
    let profile_id = business_profile
        .ok_or(UserErrors::InvalidEmbeddedOperation(
            "No business profile ID provided".to_string(),
        ))
        .attach_printable("No profile_id provided in headers for embedded token")?
        .get_id()
        .clone();
    state
        .store
        .find_business_profile_by_merchant_id_profile_id(
            processor.get_key_store(),
            processor.get_account().get_id(),
            &profile_id,
        )
        .await
        .to_not_found_response(UserErrors::InvalidEmbeddedOperation(
            "Invalid business profile ID".to_string(),
        ))?;
    auth::embedded::EmbeddedToken::new_token(
        state.tenant.tenant_id,
        processor.get_account().get_org_id().clone(),
        processor.get_account().get_id().clone(),
        profile_id,
        &state.conf,
    )
    .await
    .map(|token| {
        ApplicationResponse::Json(user_api::IssueEmbeddedTokenResponse {
            token: token.into(),
        })
    })
}

#[cfg(feature = "v1")]
pub async fn embedded_token_info(
    _state: SessionState,
    processor: domain::Processor,
    business_profile: Option<domain::Profile>,
) -> UserResponse<user_api::EmbeddedTokenInfoResponse> {
    Ok(ApplicationResponse::Json(
        user_api::EmbeddedTokenInfoResponse {
            org_id: processor.get_account().get_org_id().clone(),
            merchant_id: processor.get_account().get_id().clone(),
            merchant_account_version: processor.get_account().version,
            profile_id: business_profile
                .ok_or(UserErrors::InternalServerError)
                .attach_printable("Missing Profile ID")?
                .get_id()
                .clone(),
        },
    ))
}

pub async fn get_user_details_internal(
    state: SessionState,
    user_id: String,
) -> UserResponse<user_api::GetUserInternalDetailsResponse> {
    let user: domain::UserFromStorage = state
        .global_store
        .find_user_by_user_id(&user_id)
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Failed to fetch user from database")?
        .into();

    Ok(ApplicationResponse::Json(
        user_api::GetUserInternalDetailsResponse {
            user_id: user.get_user_id().to_string(),
            name: user.get_name(),
            email: user.get_email(),
            is_active: user.is_active(),
        },
    ))
}

pub async fn list_users_internal(
    state: SessionState,
    req: user_api::ListUsersInternalRequest,
) -> UserResponse<user_api::ListUsersInternalResponse> {
    let users = state
        .global_store
        .list_users_by_user_ids(req.user_ids)
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Failed to fetch users from database")?;

    let users_minimal_details = users
        .into_iter()
        .map(domain::UserFromStorage::from)
        .map(|user| user_api::GetUserInternalDetailsResponse {
            user_id: user.get_user_id().to_string(),
            name: user.get_name(),
            email: user.get_email(),
            is_active: user.is_active(),
        })
        .collect();

    Ok(ApplicationResponse::Json(
        user_api::ListUsersInternalResponse {
            users: users_minimal_details,
        },
    ))
}

#[cfg(feature = "v1")]
pub async fn list_members_for_entity(
    state: SessionState,
    auth: auth::AuthenticationData,
    access_level: EntityType,
) -> UserResponse<user_api::ListUsersInternalResponse> {
    let merchant_id = auth.platform.get_processor().get_account().get_id();

    let profile = auth
        .profile
        .ok_or(report!(UserErrors::InternalServerError))
        .attach_printable("Profile is required for list_members_for_entity")?;

    let profile_id = profile.get_id();
    let org_id = auth.platform.get_processor().get_account().get_org_id();
    let tenant_id = &state.tenant.tenant_id;

    let entity_types_to_query: Vec<EntityType> = match access_level {
        EntityType::Profile => {
            vec![
                EntityType::Organization,
                EntityType::Merchant,
                EntityType::Profile,
            ]
        }
        EntityType::Merchant => {
            vec![EntityType::Organization, EntityType::Merchant]
        }
        EntityType::Organization => {
            vec![EntityType::Organization]
        }
        EntityType::Tenant => {
            return Err(report!(UserErrors::InvalidRoleOperation))
                .attach_printable("Tenant-level access is not supported for this endpoint");
        }
    };

    let user_ids =
        futures::future::try_join_all(entity_types_to_query.into_iter().map(|entity_type| {
            let state = &state;
            async move {
                let (merchant_id_filter, profile_id_filter) = match entity_type {
                    EntityType::Organization => (None, None),
                    EntityType::Merchant => (Some(merchant_id), None),
                    EntityType::Profile => (Some(merchant_id), Some(profile_id)),
                    EntityType::Tenant => {
                        return Err(report!(UserErrors::InternalServerError))
                            .attach_printable("Unexpected tenant entity type in query list");
                    }
                };

                state
                    .global_store
                    .list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
                        user_id: None,
                        tenant_id,
                        org_id,
                        merchant_id: merchant_id_filter,
                        profile_id: profile_id_filter,
                        entity_type: Some(entity_type),
                        version: None,
                        limit: None,
                    })
                    .await
                    .change_context(UserErrors::InternalServerError)
                    .attach_printable(format!("Failed to fetch user roles for {:?}", entity_type))
            }
        }))
        .await?
        .into_iter()
        .flatten()
        .map(|user_role| user_role.user_id)
        .collect::<HashSet<_>>();

    let users = state
        .global_store
        .find_active_users_by_user_ids(user_ids.into_iter().collect())
        .await
        .change_context(UserErrors::InternalServerError)
        .attach_printable("Failed to fetch users from database")?;

    let users_minimal_details = users
        .into_iter()
        .map(domain::UserFromStorage::from)
        .map(|user| user_api::GetUserInternalDetailsResponse {
            user_id: user.get_user_id().to_string(),
            name: user.get_name(),
            email: user.get_email(),
            is_active: user.is_active(),
        })
        .collect();

    Ok(ApplicationResponse::Json(
        user_api::ListUsersInternalResponse {
            users: users_minimal_details,
        },
    ))
}

#[cfg(feature = "v1")]
pub async fn authorize_token(
    state: SessionState,
    payload: user_api::AuthorizeTokenRequest,
) -> RouterResponse<()> {
    let permission: Permission =
        payload
            .permission
            .parse()
            .map_err(|_| ApiErrorResponse::InvalidRequestData {
                message: "Invalid permission".to_string(),
            })?;

    let token = auth::decode_jwt::<auth::AuthToken>(&payload.token.expose(), &state).await?;

    if token.check_in_blacklist(&state).await? {
        return Err(ApiErrorResponse::InvalidJwtToken.into());
    }

    let role_info = authorization::get_role_info(&state, &token).await?;
    authorization::check_permission(permission, &role_info)?;

    Ok(ApplicationResponse::StatusOk)
}
