// File: crates/router/src/services/authentication/detached.rs

use std::{borrow::Cow, string::ToString};

use actix_web::http::header::HeaderMap;
use common_utils::{
    crypto::VerifySignature,
    id_type::{ApiKeyId, MerchantId},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;

use crate::core::errors::RouterResult;

const HEADER_AUTH_TYPE: &str = "x-auth-type";
const HEADER_MERCHANT_ID: &str = "x-merchant-id";
const HEADER_KEY_ID: &str = "x-key-id";
const HEADER_CHECKSUM: &str = "x-checksum";

#[derive(Debug)]
pub struct ExtractedPayload {
    pub payload_type: PayloadType,
    pub merchant_id: Option<MerchantId>,
    pub key_id: Option<ApiKeyId>,
}

#[derive(strum::EnumString, strum::Display, PartialEq, Debug)]
#[strum(serialize_all = "snake_case")]
pub enum PayloadType {
    ApiKey,
    PublishableKey,
}

pub trait GetAuthType {
    fn get_auth_type(&self) -> PayloadType;
}

impl ExtractedPayload {
    pub fn from_headers(headers: &HeaderMap) -> RouterResult<Self> {
        let merchant_id = headers
            .get(HEADER_MERCHANT_ID)
            .and_then(|value| value.to_str().ok())
            .ok_or_else(|| ApiErrorResponse::InvalidRequestData {
                message: format!("`{HEADER_MERCHANT_ID}` header is invalid or not present"),
            })
            .map_err(error_stack::Report::from)
            .and_then(|merchant_id| {
                MerchantId::try_from(Cow::from(merchant_id.to_string())).change_context(
                    ApiErrorResponse::InvalidRequestData {
                        message: format!(
                            "`{HEADER_MERCHANT_ID}` header is invalid or not present",

                        ),
                    },
                )
            })?;

        let auth_type: PayloadType = headers
            .get(HEADER_AUTH_TYPE)
            .and_then(|inner| inner.to_str().ok())
            .ok_or_else(|| ApiErrorResponse::InvalidRequestData {
                message: format!("`{HEADER_AUTH_TYPE}` header not present"),
            })?
            .parse::<PayloadType>()
            .change_context(ApiErrorResponse::InvalidRequestData {
                message: format!("`{HEADER_AUTH_TYPE}` header not present"),
            })?;

        let key_id = headers
            .get(HEADER_KEY_ID)
            .and_then(|value| value.to_str().ok())
            .map(|key_id| ApiKeyId::try_from(Cow::from(key_id.to_string())))
            .transpose()
            .change_context(ApiErrorResponse::InvalidRequestData {
                message: format!("`{HEADER_KEY_ID}` header is invalid or not present"),
            })?;

        Ok(Self {
            payload_type: auth_type,
            merchant_id: Some(merchant_id),
            key_id,
        })
    }

    pub fn verify_checksum(
        &self,
        headers: &HeaderMap,
        algo: impl VerifySignature,
        secret: &[u8],
    ) -> bool {
        let output = || {
            let checksum = headers.get(HEADER_CHECKSUM)?.to_str().ok()?;
            let payload = self.generate_payload();

            algo.verify_signature(secret, &hex::decode(checksum).ok()?, payload.as_bytes())
                .ok()
        };

        output().unwrap_or(false)
    }

    // The payload should be `:` separated strings of all the fields
    fn generate_payload(&self) -> String {
        append_option(
            &self.payload_type.to_string(),
            &self
                .merchant_id
                .as_ref()
                .map(|inner| append_api_key(inner.get_string_repr(), &self.key_id)),
        )
    }
}

#[inline]
fn append_option(prefix: &str, data: &Option<String>) -> String {
    match data {
        Some(inner) => format!("{prefix}:{inner}"),
        None => prefix.to_string(),
    }
}

#[inline]
fn append_api_key(prefix: &str, data: &Option<ApiKeyId>) -> String {
    match data {
        Some(inner) => format!("{}:{}", prefix, inner.get_string_repr()),
        None => prefix.to_string(),
    }
}


// File: crates/router/src/services/authentication/cookies.rs

use cookie::Cookie;
#[cfg(feature = "olap")]
use cookie::{
    time::{Duration, OffsetDateTime},
    SameSite,
};
use error_stack::{report, ResultExt};
#[cfg(feature = "olap")]
use masking::Mask;
#[cfg(feature = "olap")]
use masking::{ExposeInterface, Secret};

use crate::{
    consts::JWT_TOKEN_COOKIE_NAME,
    core::errors::{ApiErrorResponse, RouterResult},
};
#[cfg(feature = "olap")]
use crate::{
    consts::JWT_TOKEN_TIME_IN_SECS,
    core::errors::{UserErrors, UserResponse},
    services::ApplicationResponse,
};

#[cfg(feature = "olap")]
pub fn set_cookie_response<R>(response: R, token: Secret<String>) -> UserResponse<R> {
    let jwt_expiry_in_seconds = JWT_TOKEN_TIME_IN_SECS
        .try_into()
        .map_err(|_| UserErrors::InternalServerError)?;
    let (expiry, max_age) = get_expiry_and_max_age_from_seconds(jwt_expiry_in_seconds);

    let header_value = create_cookie(token, expiry, max_age)
        .to_string()
        .into_masked();
    let header_key = get_set_cookie_header();
    let header = vec![(header_key, header_value)];

    Ok(ApplicationResponse::JsonWithHeaders((response, header)))
}

#[cfg(feature = "olap")]
pub fn remove_cookie_response() -> UserResponse<()> {
    let (expiry, max_age) = get_expiry_and_max_age_from_seconds(0);

    let header_key = get_set_cookie_header();
    let header_value = create_cookie("".to_string().into(), expiry, max_age)
        .to_string()
        .into_masked();
    let header = vec![(header_key, header_value)];
    Ok(ApplicationResponse::JsonWithHeaders(((), header)))
}

pub fn get_jwt_from_cookies(cookies: &str) -> RouterResult<String> {
    Cookie::split_parse(cookies)
        .find_map(|cookie| {
            cookie
                .ok()
                .filter(|parsed_cookie| parsed_cookie.name() == JWT_TOKEN_COOKIE_NAME)
                .map(|parsed_cookie| parsed_cookie.value().to_owned())
        })
        .ok_or(report!(ApiErrorResponse::InvalidJwtToken))
        .attach_printable("Unable to find JWT token in cookies")
}

#[cfg(feature = "olap")]
fn create_cookie<'c>(
    token: Secret<String>,
    expires: OffsetDateTime,
    max_age: Duration,
) -> Cookie<'c> {
    Cookie::build((JWT_TOKEN_COOKIE_NAME, token.expose()))
        .http_only(true)
        .secure(true)
        .same_site(SameSite::Strict)
        .path("/")
        .expires(expires)
        .max_age(max_age)
        .build()
}

#[cfg(feature = "olap")]
fn get_expiry_and_max_age_from_seconds(seconds: i64) -> (OffsetDateTime, Duration) {
    let max_age = Duration::seconds(seconds);
    let expiry = OffsetDateTime::now_utc().saturating_add(max_age);
    (expiry, max_age)
}

#[cfg(feature = "olap")]
fn get_set_cookie_header() -> String {
    actix_http::header::SET_COOKIE.to_string()
}

pub fn get_cookie_header() -> String {
    actix_http::header::COOKIE.to_string()
}


// File: crates/router/src/services/authentication/blacklist.rs

use std::sync::Arc;

#[cfg(feature = "olap")]
use common_utils::date_time;
use error_stack::ResultExt;
use redis_interface::RedisConnectionPool;

use super::AuthToken;
#[cfg(feature = "olap")]
use super::{SinglePurposeOrLoginToken, SinglePurposeToken};
#[cfg(feature = "email")]
use crate::consts::{EMAIL_TOKEN_BLACKLIST_PREFIX, EMAIL_TOKEN_TIME_IN_SECS};
use crate::{
    consts::{JWT_TOKEN_TIME_IN_SECS, ROLE_BLACKLIST_PREFIX, USER_BLACKLIST_PREFIX},
    core::errors::{ApiErrorResponse, RouterResult},
    routes::app::SessionStateInfo,
};
#[cfg(feature = "olap")]
use crate::{
    core::errors::{UserErrors, UserResult},
    routes::SessionState,
    services::authorization as authz,
};

#[cfg(feature = "olap")]
pub async fn insert_user_in_blacklist(state: &SessionState, user_id: &str) -> UserResult<()> {
    let user_blacklist_key = format!("{USER_BLACKLIST_PREFIX}{user_id}");
    let expiry =
        expiry_to_i64(JWT_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    redis_conn
        .set_key_with_expiry(
            &user_blacklist_key.as_str().into(),
            date_time::now_unix_timestamp(),
            expiry,
        )
        .await
        .change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "olap")]
pub async fn insert_role_in_blacklist(state: &SessionState, role_id: &str) -> UserResult<()> {
    let role_blacklist_key = format!("{ROLE_BLACKLIST_PREFIX}{role_id}");
    let expiry =
        expiry_to_i64(JWT_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    redis_conn
        .set_key_with_expiry(
            &role_blacklist_key.as_str().into(),
            date_time::now_unix_timestamp(),
            expiry,
        )
        .await
        .change_context(UserErrors::InternalServerError)?;
    invalidate_role_cache(state, role_id)
        .await
        .change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "olap")]
async fn invalidate_role_cache(state: &SessionState, role_id: &str) -> RouterResult<()> {
    let redis_conn = get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .delete_key(&authz::get_cache_key_from_role_id(role_id).as_str().into())
        .await
        .map(|_| ())
        .change_context(ApiErrorResponse::InternalServerError)
}

pub async fn check_user_in_blacklist<A: SessionStateInfo>(
    state: &A,
    user_id: &str,
    token_expiry: u64,
) -> RouterResult<bool> {
    let token = format!("{USER_BLACKLIST_PREFIX}{user_id}");
    let token_issued_at = expiry_to_i64(token_expiry - JWT_TOKEN_TIME_IN_SECS)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .get_key::<Option<i64>>(&token.as_str().into())
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .map(|timestamp| timestamp > Some(token_issued_at))
}

pub async fn check_role_in_blacklist<A: SessionStateInfo>(
    state: &A,
    role_id: &str,
    token_expiry: u64,
) -> RouterResult<bool> {
    let token = format!("{ROLE_BLACKLIST_PREFIX}{role_id}");
    let token_issued_at = expiry_to_i64(token_expiry - JWT_TOKEN_TIME_IN_SECS)?;
    let redis_conn = get_redis_connection_for_global_tenant(state)?;
    redis_conn
        .get_key::<Option<i64>>(&token.as_str().into())
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .map(|timestamp| timestamp > Some(token_issued_at))
}

#[cfg(feature = "email")]
pub async fn insert_email_token_in_blacklist(state: &SessionState, token: &str) -> UserResult<()> {
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    let blacklist_key = format!("{EMAIL_TOKEN_BLACKLIST_PREFIX}{token}");
    let expiry =
        expiry_to_i64(EMAIL_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?;
    redis_conn
        .set_key_with_expiry(&blacklist_key.as_str().into(), true, expiry)
        .await
        .change_context(UserErrors::InternalServerError)
}

#[cfg(feature = "email")]
pub async fn check_email_token_in_blacklist(state: &SessionState, token: &str) -> UserResult<()> {
    let redis_conn = get_redis_connection_for_global_tenant(state)
        .change_context(UserErrors::InternalServerError)?;
    let blacklist_key = format!("{EMAIL_TOKEN_BLACKLIST_PREFIX}{token}");
    let key_exists = redis_conn
        .exists::<()>(&blacklist_key.as_str().into())
        .await
        .change_context(UserErrors::InternalServerError)?;

    if key_exists {
        return Err(UserErrors::LinkInvalid.into());
    }
    Ok(())
}

fn get_redis_connection_for_global_tenant<A: SessionStateInfo>(
    state: &A,
) -> RouterResult<Arc<RedisConnectionPool>> {
    state
        .global_store()
        .get_redis_conn()
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get redis connection")
}

fn expiry_to_i64(expiry: u64) -> RouterResult<i64> {
    i64::try_from(expiry).change_context(ApiErrorResponse::InternalServerError)
}

#[async_trait::async_trait]
pub trait BlackList {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync;
}

#[async_trait::async_trait]
impl BlackList for AuthToken {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync,
    {
        Ok(
            check_user_in_blacklist(state, &self.user_id, self.exp).await?
                || check_role_in_blacklist(state, &self.role_id, self.exp).await?,
        )
    }
}

#[cfg(feature = "olap")]
#[async_trait::async_trait]
impl BlackList for SinglePurposeToken {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync,
    {
        check_user_in_blacklist(state, &self.user_id, self.exp).await
    }
}

#[cfg(feature = "olap")]
#[async_trait::async_trait]
impl BlackList for SinglePurposeOrLoginToken {
    async fn check_in_blacklist<A>(&self, state: &A) -> RouterResult<bool>
    where
        A: SessionStateInfo + Sync,
    {
        check_user_in_blacklist(state, &self.user_id, self.exp).await
    }
}


// File: crates/router/src/services/email/types.rs

use api_models::user::dashboard_metadata::ProdIntent;
use common_enums::{EntityType, MerchantProductType};
use common_utils::{errors::CustomResult, pii, types::user::EmailThemeConfig};
use error_stack::ResultExt;
use external_services::email::{EmailContents, EmailData, EmailError};
use masking::{ExposeInterface, PeekInterface, Secret};

use crate::{configs, consts, routes::SessionState};
#[cfg(feature = "olap")]
use crate::{
    core::errors::{UserErrors, UserResult},
    services::jwt,
    types::domain,
};

pub enum EmailBody {
    Verify {
        link: String,
        entity_name: String,
        entity_logo_url: String,
        primary_color: String,
        background_color: String,
        foreground_color: String,
    },
    Reset {
        link: String,
        user_name: String,
        entity_name: String,
        entity_logo_url: String,
        primary_color: String,
        background_color: String,
        foreground_color: String,
    },
    MagicLink {
        link: String,
        user_name: String,
        entity_name: String,
        entity_logo_url: String,
        primary_color: String,
        background_color: String,
        foreground_color: String,
    },
    InviteUser {
        link: String,
        user_name: String,
        entity_name: String,
        entity_logo_url: String,
        primary_color: String,
        background_color: String,
        foreground_color: String,
    },
    AcceptInviteFromEmail {
        link: String,
        user_name: String,
        entity_name: String,
        entity_logo_url: String,
        primary_color: String,
        background_color: String,
        foreground_color: String,
    },
    BizEmailProd {
        user_name: String,
        poc_email: String,
        legal_business_name: String,
        business_location: String,
        business_website: String,
        product_type: MerchantProductType,
    },
    ReconActivation {
        user_name: String,
    },
    ProFeatureRequest {
        feature_name: String,
        merchant_id: common_utils::id_type::MerchantId,
        user_name: String,
        user_email: String,
    },
    ApiKeyExpiryReminder {
        expires_in: u8,
        api_key_name: String,
        prefix: String,
    },
    WelcomeToCommunity,
}

pub mod html {
    use crate::services::email::types::EmailBody;

    pub fn get_html_body(email_body: EmailBody) -> String {
        match email_body {
            EmailBody::Verify {
                link,
                entity_name,
                entity_logo_url,
                primary_color,
                background_color,
                foreground_color,
            } => {
                format!(
                    include_str!("assets/verify.html"),
                    link = link,
                    entity_name = entity_name,
                    entity_logo_url = entity_logo_url,
                    primary_color = primary_color,
                    background_color = background_color,
                    foreground_color = foreground_color
                )
            }
            EmailBody::Reset {
                link,
                user_name,
                entity_name,
                entity_logo_url,
                primary_color,
                background_color,
                foreground_color,
            } => {
                format!(
                    include_str!("assets/reset.html"),
                    link = link,
                    username = user_name,
                    entity_name = entity_name,
                    entity_logo_url = entity_logo_url,
                    primary_color = primary_color,
                    background_color = background_color,
                    foreground_color = foreground_color
                )
            }
            EmailBody::MagicLink {
                link,
                user_name,
                entity_name,
                entity_logo_url,
                primary_color,
                background_color,
                foreground_color,
            } => {
                format!(
                    include_str!("assets/magic_link.html"),
                    username = user_name,
                    link = link,
                    entity_name = entity_name,
                    entity_logo_url = entity_logo_url,
                    primary_color = primary_color,
                    background_color = background_color,
                    foreground_color = foreground_color
                )
            }

            EmailBody::InviteUser {
                link,
                user_name,
                entity_name,
                entity_logo_url,
                primary_color,
                background_color,
                foreground_color,
            } => {
                format!(
                    include_str!("assets/invite.html"),
                    username = user_name,
                    link = link,
                    entity_name = entity_name,
                    entity_logo_url = entity_logo_url,
                    primary_color = primary_color,
                    background_color = background_color,
                    foreground_color = foreground_color
                )
            }
            // TODO: Change the linked html for accept invite from email
            EmailBody::AcceptInviteFromEmail {
                link,
                user_name,
                entity_name,
                entity_logo_url,
                primary_color,
                background_color,
                foreground_color,
            } => {
                format!(
                    include_str!("assets/invite.html"),
                    username = user_name,
                    link = link,
                    entity_name = entity_name,
                    entity_logo_url = entity_logo_url,
                    primary_color = primary_color,
                    background_color = background_color,
                    foreground_color = foreground_color
                )
            }
            EmailBody::ReconActivation { user_name } => {
                format!(
                    include_str!("assets/recon_activation.html"),
                    username = user_name,
                )
            }
            EmailBody::BizEmailProd {
                user_name,
                poc_email,
                legal_business_name,
                business_location,
                business_website,
                product_type,
            } => {
                format!(
                    include_str!("assets/bizemailprod.html"),
                    poc_email = poc_email,
                    legal_business_name = legal_business_name,
                    business_location = business_location,
                    business_website = business_website,
                    username = user_name,
                    product_type = product_type
                )
            }
            EmailBody::ProFeatureRequest {
                feature_name,
                merchant_id,
                user_name,
                user_email,
            } => format!(
                "Dear Hyperswitch Support Team,

Dashboard Pro Feature Request,
Feature name  : {feature_name}
Merchant ID   : {}
Merchant Name : {user_name}
Email         : {user_email}

(note: This is an auto generated email. Use merchant email for any further communications)",
                merchant_id.get_string_repr()
            ),
            EmailBody::ApiKeyExpiryReminder {
                expires_in,
                api_key_name,
                prefix,
            } => format!(
                include_str!("assets/api_key_expiry_reminder.html"),
                api_key_name = api_key_name,
                prefix = prefix,
                expires_in = expires_in,
            ),
            EmailBody::WelcomeToCommunity => {
                include_str!("assets/welcome_to_community.html").to_string()
            }
        }
    }
}

#[derive(serde::Serialize, serde::Deserialize)]
pub struct EmailToken {
    email: String,
    flow: domain::Origin,
    exp: u64,
    entity: Option<Entity>,
}

#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct Entity {
    pub entity_id: String,
    pub entity_type: EntityType,
}

impl Entity {
    pub fn get_entity_type(&self) -> EntityType {
        self.entity_type
    }

    pub fn get_entity_id(&self) -> &str {
        &self.entity_id
    }
}

impl EmailToken {
    pub async fn new_token(
        email: domain::UserEmail,
        entity: Option<Entity>,
        flow: domain::Origin,
        settings: &configs::Settings,
    ) -> UserResult<String> {
        let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS);
        let exp = jwt::generate_exp(expiration_duration)?.as_secs();
        let token_payload = Self {
            email: email.get_secret().expose(),
            flow,
            exp,
            entity,
        };
        jwt::generate_jwt(&token_payload, settings).await
    }

    pub fn get_email(&self) -> UserResult<domain::UserEmail> {
        pii::Email::try_from(self.email.clone())
            .change_context(UserErrors::InternalServerError)
            .and_then(domain::UserEmail::from_pii_email)
    }

    pub fn get_entity(&self) -> Option<&Entity> {
        self.entity.as_ref()
    }

    pub fn get_flow(&self) -> domain::Origin {
        self.flow.clone()
    }
}

pub fn get_link_with_token(
    base_url: impl std::fmt::Display,
    token: impl std::fmt::Display,
    action: impl std::fmt::Display,
    auth_id: &Option<impl std::fmt::Display>,
    theme_id: &Option<impl std::fmt::Display>,
) -> String {
    let mut email_url = format!("{base_url}/user/{action}?token={token}");
    if let Some(auth_id) = auth_id {
        email_url = format!("{email_url}&auth_id={auth_id}");
    }
    if let Some(theme_id) = theme_id {
        email_url = format!("{email_url}&theme_id={theme_id}");
    }

    email_url
}
pub struct VerifyEmail {
    pub recipient_email: domain::UserEmail,
    pub settings: std::sync::Arc<configs::Settings>,
    pub auth_id: Option<String>,
    pub theme_id: Option<String>,
    pub theme_config: EmailThemeConfig,
}

/// Currently only HTML is supported
#[async_trait::async_trait]
impl EmailData for VerifyEmail {
    async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError> {
        let token = EmailToken::new_token(
            self.recipient_email.clone(),
            None,
            domain::Origin::VerifyEmail,
            &self.settings,
        )
        .await
        .change_context(EmailError::TokenGenerationFailure)?;

        let verify_email_link = get_link_with_token(
            base_url,
            token,
            "verify_email",
            &self.auth_id,
            &self.theme_id,
        );

        let body = html::get_html_body(EmailBody::Verify {
            link: verify_email_link,
            entity_name: self.theme_config.entity_name.clone(),
            entity_logo_url: self.theme_config.entity_logo_url.clone(),
            primary_color: self.theme_config.primary_color.clone(),
            background_color: self.theme_config.background_color.clone(),
            foreground_color: self.theme_config.foreground_color.clone(),
        });

        Ok(EmailContents {
            subject: format!(
                "Welcome to the {} community!",
                self.theme_config.entity_name
            ),
            body: external_services::email::IntermediateString::new(body),
            recipient: self.recipient_email.clone().into_inner(),
        })
    }
}

pub struct ResetPassword {
    pub recipient_email: domain::UserEmail,
    pub user_name: domain::UserName,
    pub settings: std::sync::Arc<configs::Settings>,
    pub auth_id: Option<String>,
    pub theme_id: Option<String>,
    pub theme_config: EmailThemeConfig,
}

#[async_trait::async_trait]
impl EmailData for ResetPassword {
    async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError> {
        let token = EmailToken::new_token(
            self.recipient_email.clone(),
            None,
            domain::Origin::ResetPassword,
            &self.settings,
        )
        .await
        .change_context(EmailError::TokenGenerationFailure)?;

        let reset_password_link = get_link_with_token(
            base_url,
            token,
            "set_password",
            &self.auth_id,
            &self.theme_id,
        );

        let body = html::get_html_body(EmailBody::Reset {
            link: reset_password_link,
            user_name: self.user_name.clone().get_secret().expose(),
            entity_name: self.theme_config.entity_name.clone(),
            entity_logo_url: self.theme_config.entity_logo_url.clone(),
            primary_color: self.theme_config.primary_color.clone(),
            background_color: self.theme_config.background_color.clone(),
            foreground_color: self.theme_config.foreground_color.clone(),
        });

        Ok(EmailContents {
            subject: format!(
                "Get back to {} - Reset Your Password Now!",
                self.theme_config.entity_name
            ),
            body: external_services::email::IntermediateString::new(body),
            recipient: self.recipient_email.clone().into_inner(),
        })
    }
}

pub struct MagicLink {
    pub recipient_email: domain::UserEmail,
    pub user_name: domain::UserName,
    pub settings: std::sync::Arc<configs::Settings>,
    pub auth_id: Option<String>,
    pub theme_id: Option<String>,
    pub theme_config: EmailThemeConfig,
}

#[async_trait::async_trait]
impl EmailData for MagicLink {
    async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError> {
        let token = EmailToken::new_token(
            self.recipient_email.clone(),
            None,
            domain::Origin::MagicLink,
            &self.settings,
        )
        .await
        .change_context(EmailError::TokenGenerationFailure)?;

        let magic_link_login = get_link_with_token(
            base_url,
            token,
            "verify_email",
            &self.auth_id,
            &self.theme_id,
        );

        let body = html::get_html_body(EmailBody::MagicLink {
            link: magic_link_login,
            user_name: self.user_name.clone().get_secret().expose(),
            entity_name: self.theme_config.entity_name.clone(),
            entity_logo_url: self.theme_config.entity_logo_url.clone(),
            primary_color: self.theme_config.primary_color.clone(),
            background_color: self.theme_config.background_color.clone(),
            foreground_color: self.theme_config.foreground_color.clone(),
        });

        Ok(EmailContents {
            subject: format!(
                "Unlock {}: Use Your Magic Link to Sign In",
                self.theme_config.entity_name
            ),
            body: external_services::email::IntermediateString::new(body),
            recipient: self.recipient_email.clone().into_inner(),
        })
    }
}

pub struct InviteUser {
    pub recipient_email: domain::UserEmail,
    pub user_name: domain::UserName,
    pub settings: std::sync::Arc<configs::Settings>,
    pub entity: Entity,
    pub auth_id: Option<String>,
    pub theme_id: Option<String>,
    pub theme_config: EmailThemeConfig,
}

#[async_trait::async_trait]
impl EmailData for InviteUser {
    async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError> {
        let token = EmailToken::new_token(
            self.recipient_email.clone(),
            Some(self.entity.clone()),
            domain::Origin::AcceptInvitationFromEmail,
            &self.settings,
        )
        .await
        .change_context(EmailError::TokenGenerationFailure)?;

        let invite_user_link = get_link_with_token(
            base_url,
            token,
            "accept_invite_from_email",
            &self.auth_id,
            &self.theme_id,
        );
        let body = html::get_html_body(EmailBody::AcceptInviteFromEmail {
            link: invite_user_link,
            user_name: self.user_name.clone().get_secret().expose(),
            entity_name: self.theme_config.entity_name.clone(),
            entity_logo_url: self.theme_config.entity_logo_url.clone(),
            primary_color: self.theme_config.primary_color.clone(),
            background_color: self.theme_config.background_color.clone(),
            foreground_color: self.theme_config.foreground_color.clone(),
        });

        Ok(EmailContents {
            subject: format!(
                "You have been invited to join {} Community!",
                self.theme_config.entity_name
            ),
            body: external_services::email::IntermediateString::new(body),
            recipient: self.recipient_email.clone().into_inner(),
        })
    }
}

pub struct ReconActivation {
    pub recipient_email: domain::UserEmail,
    pub user_name: domain::UserName,
    pub subject: &'static str,
    pub theme_id: Option<String>,
    pub theme_config: EmailThemeConfig,
}

#[async_trait::async_trait]
impl EmailData for ReconActivation {
    async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
        let body = html::get_html_body(EmailBody::ReconActivation {
            user_name: self.user_name.clone().get_secret().expose(),
        });

        Ok(EmailContents {
            subject: self.subject.to_string(),
            body: external_services::email::IntermediateString::new(body),
            recipient: self.recipient_email.clone().into_inner(),
        })
    }
}

pub struct BizEmailProd {
    pub recipient_email: domain::UserEmail,
    pub user_name: Secret<String>,
    pub poc_email: Secret<String>,
    pub legal_business_name: String,
    pub business_location: String,
    pub business_website: String,
    pub settings: std::sync::Arc<configs::Settings>,
    pub theme_id: Option<String>,
    pub theme_config: EmailThemeConfig,
    pub product_type: MerchantProductType,
}

impl BizEmailProd {
    pub fn new(
        state: &SessionState,
        data: ProdIntent,
        theme_id: Option<String>,
        theme_config: EmailThemeConfig,
    ) -> UserResult<Self> {
        Ok(Self {
            recipient_email: domain::UserEmail::from_pii_email(
                state.conf.email.prod_intent_recipient_email.clone(),
            )?,
            settings: state.conf.clone(),
            user_name: data
                .poc_name
                .map(|s| Secret::new(s.peek().clone().into_inner()))
                .unwrap_or_default(),
            poc_email: data
                .poc_email
                .map(|s| Secret::new(s.peek().clone()))
                .unwrap_or_default(),
            legal_business_name: data
                .legal_business_name
                .map(|s| s.into_inner())
                .unwrap_or_default(),
            business_location: data
                .business_location
                .unwrap_or(common_enums::CountryAlpha2::AD)
                .to_string(),
            business_website: data
                .business_website
                .map(|s| s.into_inner())
                .unwrap_or_default(),
            theme_id,
            theme_config,
            product_type: data.product_type,
        })
    }
}

#[async_trait::async_trait]
impl EmailData for BizEmailProd {
    async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
        let body = html::get_html_body(EmailBody::BizEmailProd {
            user_name: self.user_name.clone().expose(),
            poc_email: self.poc_email.clone().expose(),
            legal_business_name: self.legal_business_name.clone(),
            business_location: self.business_location.clone(),
            business_website: self.business_website.clone(),
            product_type: self.product_type,
        });

        Ok(EmailContents {
            subject: "New Prod Intent".to_string(),
            body: external_services::email::IntermediateString::new(body),
            recipient: self.recipient_email.clone().into_inner(),
        })
    }
}

pub struct ProFeatureRequest {
    pub recipient_email: domain::UserEmail,
    pub feature_name: String,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub user_name: domain::UserName,
    pub user_email: domain::UserEmail,
    pub subject: String,
    pub theme_id: Option<String>,
    pub theme_config: EmailThemeConfig,
}

#[async_trait::async_trait]
impl EmailData for ProFeatureRequest {
    async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
        let recipient = self.recipient_email.clone().into_inner();

        let body = html::get_html_body(EmailBody::ProFeatureRequest {
            user_name: self.user_name.clone().get_secret().expose(),
            feature_name: self.feature_name.clone(),
            merchant_id: self.merchant_id.clone(),
            user_email: self.user_email.clone().get_secret().expose(),
        });

        Ok(EmailContents {
            subject: self.subject.clone(),
            body: external_services::email::IntermediateString::new(body),
            recipient,
        })
    }
}

pub struct ApiKeyExpiryReminder {
    pub recipient_email: domain::UserEmail,
    pub subject: &'static str,
    pub expires_in: u8,
    pub api_key_name: String,
    pub prefix: String,
    pub theme_id: Option<String>,
    pub theme_config: EmailThemeConfig,
}

#[async_trait::async_trait]
impl EmailData for ApiKeyExpiryReminder {
    async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
        let recipient = self.recipient_email.clone().into_inner();

        let body = html::get_html_body(EmailBody::ApiKeyExpiryReminder {
            expires_in: self.expires_in,
            api_key_name: self.api_key_name.clone(),
            prefix: self.prefix.clone(),
        });

        Ok(EmailContents {
            subject: self.subject.to_string(),
            body: external_services::email::IntermediateString::new(body),
            recipient,
        })
    }
}

pub struct WelcomeToCommunity {
    pub recipient_email: domain::UserEmail,
}

#[async_trait::async_trait]
impl EmailData for WelcomeToCommunity {
    async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
        let body = html::get_html_body(EmailBody::WelcomeToCommunity);

        Ok(EmailContents {
            subject: "Thank you for signing up on Hyperswitch Dashboard!".to_string(),
            body: external_services::email::IntermediateString::new(body),
            recipient: self.recipient_email.clone().into_inner(),
        })
    }
}


// File: config/loki.yaml

auth_enabled: false

server:
  http_listen_port: 3100

common:
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

ingester:
  # These ingester settings are configured for local setup only
  # for more quicker log viewing at the expense of perf efficiency
  chunk_idle_period: 10s
  max_chunk_age: 1m

schema_config:
  configs:
    - from: 2020-10-24
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

analytics:
  reporting_enabled: false