// File: crates/router/src/core/user/theme.rs
// Module: router
// Public functions: 13

use api_models::user::theme as theme_api;
use common_enums::EntityType;
use common_utils::{
    ext_traits::{ByteSliceExt, Encode},
    types::user::ThemeLineage,
};
use diesel_models::user::theme::{ThemeNew, ThemeUpdate};
use error_stack::ResultExt;
use hyperswitch_domain_models::api::ApplicationResponse;
use masking::ExposeInterface;
use rdkafka::message::ToBytes;
use uuid::Uuid;

use crate::{
    core::errors::{StorageErrorExt, UserErrors, UserResponse},
    routes::SessionState,
    services::authentication::UserFromToken,
    utils::user::theme as theme_utils,
};

// TODO: To be deprecated
pub async fn get_theme_using_lineage(
    state: SessionState,
    lineage: ThemeLineage,
) -> UserResponse<theme_api::GetThemeResponse> {
    let theme = state
        .store
        .find_theme_by_lineage(lineage)
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    let file = theme_utils::retrieve_file_from_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&theme.theme_id),
    )
    .await?;

    let parsed_data = file
        .to_bytes()
        .parse_struct("ThemeData")
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
        email_config: theme.email_config(),
        theme_id: theme.theme_id,
        theme_name: theme.theme_name,
        entity_type: theme.entity_type,
        tenant_id: theme.tenant_id,
        org_id: theme.org_id,
        merchant_id: theme.merchant_id,
        profile_id: theme.profile_id,
        theme_data: parsed_data,
    }))
}

// TODO: To be deprecated
pub async fn get_theme_using_theme_id(
    state: SessionState,
    theme_id: String,
) -> UserResponse<theme_api::GetThemeResponse> {
    let theme = state
        .store
        .find_theme_by_theme_id(theme_id.clone())
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    let file = theme_utils::retrieve_file_from_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&theme_id),
    )
    .await?;

    let parsed_data = file
        .to_bytes()
        .parse_struct("ThemeData")
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
        email_config: theme.email_config(),
        theme_id: theme.theme_id,
        theme_name: theme.theme_name,
        entity_type: theme.entity_type,
        tenant_id: theme.tenant_id,
        org_id: theme.org_id,
        merchant_id: theme.merchant_id,
        profile_id: theme.profile_id,
        theme_data: parsed_data,
    }))
}

// TODO: To be deprecated
pub async fn upload_file_to_theme_storage(
    state: SessionState,
    theme_id: String,
    request: theme_api::UploadFileRequest,
) -> UserResponse<()> {
    let db_theme = state
        .store
        .find_theme_by_theme_id(theme_id)
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    theme_utils::upload_file_to_theme_bucket(
        &state,
        &theme_utils::get_specific_file_key(&db_theme.theme_id, &request.asset_name),
        request.asset_data.expose(),
    )
    .await?;

    Ok(ApplicationResponse::StatusOk)
}

// TODO: To be deprecated
pub async fn create_theme(
    state: SessionState,
    request: theme_api::CreateThemeRequest,
) -> UserResponse<theme_api::GetThemeResponse> {
    theme_utils::validate_lineage(&state, &request.lineage).await?;

    let email_config = if cfg!(feature = "email") {
        request.email_config.ok_or(UserErrors::MissingEmailConfig)?
    } else {
        request
            .email_config
            .unwrap_or(state.conf.theme.email_config.clone())
    };

    let new_theme = ThemeNew::new(
        Uuid::new_v4().to_string(),
        request.theme_name,
        request.lineage,
        email_config,
    );

    let db_theme = state
        .store
        .insert_theme(new_theme)
        .await
        .to_duplicate_response(UserErrors::ThemeAlreadyExists)?;

    theme_utils::upload_file_to_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&db_theme.theme_id),
        request
            .theme_data
            .encode_to_vec()
            .change_context(UserErrors::InternalServerError)?,
    )
    .await?;

    let file = theme_utils::retrieve_file_from_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&db_theme.theme_id),
    )
    .await?;

    let parsed_data = file
        .to_bytes()
        .parse_struct("ThemeData")
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
        email_config: db_theme.email_config(),
        theme_id: db_theme.theme_id,
        entity_type: db_theme.entity_type,
        tenant_id: db_theme.tenant_id,
        org_id: db_theme.org_id,
        merchant_id: db_theme.merchant_id,
        profile_id: db_theme.profile_id,
        theme_name: db_theme.theme_name,
        theme_data: parsed_data,
    }))
}

// TODO: To be deprecated
pub async fn update_theme(
    state: SessionState,
    theme_id: String,
    request: theme_api::UpdateThemeRequest,
) -> UserResponse<theme_api::GetThemeResponse> {
    let db_theme = match request.email_config {
        Some(email_config) => {
            let theme_update = ThemeUpdate::EmailConfig { email_config };
            state
                .store
                .update_theme_by_theme_id(theme_id.clone(), theme_update)
                .await
                .to_not_found_response(UserErrors::ThemeNotFound)?
        }
        None => state
            .store
            .find_theme_by_theme_id(theme_id)
            .await
            .to_not_found_response(UserErrors::ThemeNotFound)?,
    };

    if let Some(theme_data) = request.theme_data {
        theme_utils::upload_file_to_theme_bucket(
            &state,
            &theme_utils::get_theme_file_key(&db_theme.theme_id),
            theme_data
                .encode_to_vec()
                .change_context(UserErrors::InternalServerError)
                .attach_printable("Failed to parse ThemeData")?,
        )
        .await?;
    }

    let file = theme_utils::retrieve_file_from_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&db_theme.theme_id),
    )
    .await?;

    let parsed_data = file
        .to_bytes()
        .parse_struct("ThemeData")
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
        email_config: db_theme.email_config(),
        theme_id: db_theme.theme_id,
        entity_type: db_theme.entity_type,
        tenant_id: db_theme.tenant_id,
        org_id: db_theme.org_id,
        merchant_id: db_theme.merchant_id,
        profile_id: db_theme.profile_id,
        theme_name: db_theme.theme_name,
        theme_data: parsed_data,
    }))
}

// TODO: To be deprecated
pub async fn delete_theme(state: SessionState, theme_id: String) -> UserResponse<()> {
    state
        .store
        .delete_theme_by_theme_id(theme_id.clone())
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    // TODO (#6717): Delete theme folder from the theme storage.
    // Currently there is no simple or easy way to delete a whole folder from S3.
    // So, we are not deleting the theme folder from the theme storage.

    Ok(ApplicationResponse::StatusOk)
}

pub async fn create_user_theme(
    state: SessionState,
    user_from_token: UserFromToken,
    request: theme_api::CreateUserThemeRequest,
) -> UserResponse<theme_api::GetThemeResponse> {
    let email_config = if cfg!(feature = "email") {
        request.email_config.ok_or(UserErrors::MissingEmailConfig)?
    } else {
        request
            .email_config
            .unwrap_or(state.conf.theme.email_config.clone())
    };
    let lineage = theme_utils::get_theme_lineage_from_user_token(
        &user_from_token,
        &state,
        &request.entity_type,
    )
    .await?;
    let new_theme = ThemeNew::new(
        Uuid::new_v4().to_string(),
        request.theme_name,
        lineage,
        email_config,
    );

    let db_theme = state
        .store
        .insert_theme(new_theme)
        .await
        .to_duplicate_response(UserErrors::ThemeAlreadyExists)?;

    theme_utils::upload_file_to_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&db_theme.theme_id),
        request
            .theme_data
            .encode_to_vec()
            .change_context(UserErrors::InternalServerError)?,
    )
    .await?;

    let file = theme_utils::retrieve_file_from_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&db_theme.theme_id),
    )
    .await?;

    let parsed_data = file
        .to_bytes()
        .parse_struct("ThemeData")
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
        email_config: db_theme.email_config(),
        theme_id: db_theme.theme_id,
        entity_type: db_theme.entity_type,
        tenant_id: db_theme.tenant_id,
        org_id: db_theme.org_id,
        merchant_id: db_theme.merchant_id,
        profile_id: db_theme.profile_id,
        theme_name: db_theme.theme_name,
        theme_data: parsed_data,
    }))
}

pub async fn delete_user_theme(
    state: SessionState,
    user_from_token: UserFromToken,
    theme_id: String,
) -> UserResponse<()> {
    let db_theme = state
        .store
        .find_theme_by_theme_id(theme_id.clone())
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    let user_role_info = user_from_token
        .get_role_info_from_db(&state)
        .await
        .attach_printable("Invalid role_id in JWT")?;
    let user_entity_type = user_role_info.get_entity_type();

    theme_utils::can_user_access_theme(&user_from_token, &user_entity_type, &db_theme).await?;
    state
        .store
        .delete_theme_by_theme_id(theme_id.clone())
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;
    // TODO (#6717): Delete theme folder from the theme storage.
    // Currently there is no simple or easy way to delete a whole folder from S3.
    // So, we are not deleting the theme folder from the theme storage.

    Ok(ApplicationResponse::StatusOk)
}

pub async fn update_user_theme(
    state: SessionState,
    theme_id: String,
    user_from_token: UserFromToken,
    request: theme_api::UpdateThemeRequest,
) -> UserResponse<theme_api::GetThemeResponse> {
    let db_theme = state
        .store
        .find_theme_by_theme_id(theme_id.clone())
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    let user_role_info = user_from_token
        .get_role_info_from_db(&state)
        .await
        .attach_printable("Invalid role_id in JWT")?;
    let user_entity_type = user_role_info.get_entity_type();

    theme_utils::can_user_access_theme(&user_from_token, &user_entity_type, &db_theme).await?;

    let db_theme = match request.email_config {
        Some(email_config) => {
            let theme_update = ThemeUpdate::EmailConfig { email_config };
            state
                .store
                .update_theme_by_theme_id(theme_id.clone(), theme_update)
                .await
                .change_context(UserErrors::InternalServerError)?
        }
        None => db_theme,
    };

    if let Some(theme_data) = request.theme_data {
        theme_utils::upload_file_to_theme_bucket(
            &state,
            &theme_utils::get_theme_file_key(&db_theme.theme_id),
            theme_data
                .encode_to_vec()
                .change_context(UserErrors::InternalServerError)
                .attach_printable("Failed to parse ThemeData")?,
        )
        .await?;
    }

    let file = theme_utils::retrieve_file_from_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&db_theme.theme_id),
    )
    .await?;

    let parsed_data = file
        .to_bytes()
        .parse_struct("ThemeData")
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
        email_config: db_theme.email_config(),
        theme_id: db_theme.theme_id,
        entity_type: db_theme.entity_type,
        tenant_id: db_theme.tenant_id,
        org_id: db_theme.org_id,
        merchant_id: db_theme.merchant_id,
        profile_id: db_theme.profile_id,
        theme_name: db_theme.theme_name,
        theme_data: parsed_data,
    }))
}

pub async fn upload_file_to_user_theme_storage(
    state: SessionState,
    theme_id: String,
    user_from_token: UserFromToken,
    request: theme_api::UploadFileRequest,
) -> UserResponse<()> {
    let db_theme = state
        .store
        .find_theme_by_theme_id(theme_id)
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    let user_role_info = user_from_token
        .get_role_info_from_db(&state)
        .await
        .attach_printable("Invalid role_id in JWT")?;
    let user_entity_type = user_role_info.get_entity_type();

    theme_utils::can_user_access_theme(&user_from_token, &user_entity_type, &db_theme).await?;
    theme_utils::upload_file_to_theme_bucket(
        &state,
        &theme_utils::get_specific_file_key(&db_theme.theme_id, &request.asset_name),
        request.asset_data.expose(),
    )
    .await?;

    Ok(ApplicationResponse::StatusOk)
}

pub async fn list_all_themes_in_lineage(
    state: SessionState,
    user: UserFromToken,
    entity_type: EntityType,
) -> UserResponse<Vec<theme_api::GetThemeResponse>> {
    let lineage =
        theme_utils::get_theme_lineage_from_user_token(&user, &state, &entity_type).await?;

    let db_themes = state
        .store
        .list_themes_at_and_under_lineage(lineage)
        .await
        .change_context(UserErrors::InternalServerError)?;

    let mut themes = Vec::new();
    for theme in db_themes {
        match theme_utils::retrieve_file_from_theme_bucket(
            &state,
            &theme_utils::get_theme_file_key(&theme.theme_id),
        )
        .await
        {
            Ok(file) => {
                match file
                    .to_bytes()
                    .parse_struct("ThemeData")
                    .change_context(UserErrors::InternalServerError)
                {
                    Ok(parsed_data) => {
                        themes.push(theme_api::GetThemeResponse {
                            email_config: theme.email_config(),
                            theme_id: theme.theme_id,
                            theme_name: theme.theme_name,
                            entity_type: theme.entity_type,
                            tenant_id: theme.tenant_id,
                            org_id: theme.org_id,
                            merchant_id: theme.merchant_id,
                            profile_id: theme.profile_id,
                            theme_data: parsed_data,
                        });
                    }
                    Err(_) => {
                        return Err(UserErrors::ErrorRetrievingFile.into());
                    }
                }
            }
            Err(_) => {
                return Err(UserErrors::ErrorRetrievingFile.into());
            }
        }
    }

    Ok(ApplicationResponse::Json(themes))
}

pub async fn get_user_theme_using_theme_id(
    state: SessionState,
    user_from_token: UserFromToken,
    theme_id: String,
) -> UserResponse<theme_api::GetThemeResponse> {
    let db_theme = state
        .store
        .find_theme_by_theme_id(theme_id.clone())
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    let user_role_info = user_from_token
        .get_role_info_from_db(&state)
        .await
        .attach_printable("Invalid role_id in JWT")?;

    let user_role_entity = user_role_info.get_entity_type();

    theme_utils::can_user_access_theme(&user_from_token, &user_role_entity, &db_theme).await?;
    let file = theme_utils::retrieve_file_from_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&theme_id),
    )
    .await?;

    let parsed_data = file
        .to_bytes()
        .parse_struct("ThemeData")
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
        email_config: db_theme.email_config(),
        theme_id: db_theme.theme_id,
        theme_name: db_theme.theme_name,
        entity_type: db_theme.entity_type,
        tenant_id: db_theme.tenant_id,
        org_id: db_theme.org_id,
        merchant_id: db_theme.merchant_id,
        profile_id: db_theme.profile_id,
        theme_data: parsed_data,
    }))
}

pub async fn get_user_theme_using_lineage(
    state: SessionState,
    user_from_token: UserFromToken,
    entity_type: EntityType,
) -> UserResponse<theme_api::GetThemeResponse> {
    let lineage =
        theme_utils::get_theme_lineage_from_user_token(&user_from_token, &state, &entity_type)
            .await?;
    let theme = state
        .store
        .find_theme_by_lineage(lineage)
        .await
        .to_not_found_response(UserErrors::ThemeNotFound)?;

    let file = theme_utils::retrieve_file_from_theme_bucket(
        &state,
        &theme_utils::get_theme_file_key(&theme.theme_id),
    )
    .await?;

    let parsed_data = file
        .to_bytes()
        .parse_struct("ThemeData")
        .change_context(UserErrors::InternalServerError)?;

    Ok(ApplicationResponse::Json(theme_api::GetThemeResponse {
        email_config: theme.email_config(),
        theme_id: theme.theme_id,
        theme_name: theme.theme_name,
        entity_type: theme.entity_type,
        tenant_id: theme.tenant_id,
        org_id: theme.org_id,
        merchant_id: theme.merchant_id,
        profile_id: theme.profile_id,
        theme_data: parsed_data,
    }))
}


// Struct: DeutschebankThreeDSInitializeRequestCustomerBillingData
// File: crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct DeutschebankThreeDSInitializeRequestCustomerBillingData


// Struct: CheckoutWebhookData
// File: crates/hyperswitch_connectors/src/connectors/checkout/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct CheckoutWebhookData


// Implementation: impl ConnectorValidation for for Prophetpay
// File: crates/hyperswitch_connectors/src/connectors/prophetpay.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl ConnectorValidation for for Prophetpay


// Function: diesel_enum_derive_inner
// File: crates/router_derive/src/macros/diesel.rs
// Module: router_derive

pub fn diesel_enum_derive_inner(ast: &DeriveInput) -> syn::Result<TokenStream>


// Struct: Metadata
// File: crates/hyperswitch_connectors/src/connectors/coinbase/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct Metadata


// Implementation: impl UserAuthenticationMethodInterface for for KafkaStore
// File: crates/router/src/db/kafka_store.rs
// Module: router
// Methods: 6 total (0 public)

impl UserAuthenticationMethodInterface for for KafkaStore


// Function: server
// File: crates/router/src/routes/app.rs
// Module: router

pub fn server(state: AppState) -> Scope


// Struct: PixBankTransferAdditionalData
// File: crates/api_models/src/payments/additional_info.rs
// Module: api_models
// Implementations: 0

pub struct PixBankTransferAdditionalData


// File: crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs
// Module: hyperswitch_connectors
// Public functions: 2
// Public structs: 20

use base64::Engine;
use cards::CardNumber;
use common_enums::{enums, AttemptStatus};
use common_utils::{consts, errors::CustomResult, request::Method};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{
        ApplePayWalletData, BankRedirectData, Card, PaymentMethodData, WalletData,
    },
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{
        MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        RefundsRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use url::Url;

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{
        self, CardData, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, WalletData as _,
    },
};

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsPaymentsRequest {
    initial_amount: i64,
    currency: enums::Currency,
    channel: NexinetsChannel,
    product: NexinetsProduct,
    payment: Option<NexinetsPaymentDetails>,
    #[serde(rename = "async")]
    nexinets_async: NexinetsAsyncDetails,
    merchant_order_id: Option<String>,
}

#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexinetsChannel {
    #[default]
    Ecom,
}

#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum NexinetsProduct {
    #[default]
    Creditcard,
    Paypal,
    Giropay,
    Sofort,
    Eps,
    Ideal,
    Applepay,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum NexinetsPaymentDetails {
    Card(Box<NexiCardDetails>),
    Wallet(Box<NexinetsWalletDetails>),
    BankRedirects(Box<NexinetsBankRedirects>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexiCardDetails {
    #[serde(flatten)]
    card_data: CardDataDetails,
    cof_contract: Option<CofContract>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum CardDataDetails {
    CardDetails(Box<CardDetails>),
    PaymentInstrument(Box<PaymentInstrument>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardDetails {
    card_number: CardNumber,
    expiry_month: Secret<String>,
    expiry_year: Secret<String>,
    verification: Secret<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentInstrument {
    payment_instrument_id: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
pub struct CofContract {
    #[serde(rename = "type")]
    recurring_type: RecurringType,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RecurringType {
    Unscheduled,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsBankRedirects {
    bic: Option<NexinetsBIC>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsAsyncDetails {
    pub success_url: Option<String>,
    pub cancel_url: Option<String>,
    pub failure_url: Option<String>,
}

#[derive(Debug, Serialize)]
pub enum NexinetsBIC {
    #[serde(rename = "ABNANL2A")]
    AbnAmro,
    #[serde(rename = "ASNBNL21")]
    AsnBank,
    #[serde(rename = "BUNQNL2A")]
    Bunq,
    #[serde(rename = "INGBNL2A")]
    Ing,
    #[serde(rename = "KNABNL2H")]
    Knab,
    #[serde(rename = "RABONL2U")]
    Rabobank,
    #[serde(rename = "RBRBNL21")]
    Regiobank,
    #[serde(rename = "SNSBNL2A")]
    SnsBank,
    #[serde(rename = "TRIONL2U")]
    TriodosBank,
    #[serde(rename = "FVLBNL22")]
    VanLanschot,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum NexinetsWalletDetails {
    ApplePayToken(Box<ApplePayDetails>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayDetails {
    payment_data: serde_json::Value,
    payment_method: ApplepayPaymentMethod,
    transaction_identifier: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplepayPaymentMethod {
    display_name: String,
    network: String,
    #[serde(rename = "type")]
    token_type: String,
}

impl TryFrom<&PaymentsAuthorizeRouterData> for NexinetsPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
        let return_url = item.request.router_return_url.clone();
        let nexinets_async = NexinetsAsyncDetails {
            success_url: return_url.clone(),
            cancel_url: return_url.clone(),
            failure_url: return_url,
        };
        let (payment, product) = get_payment_details_and_product(item)?;
        let merchant_order_id = match item.payment_method {
            // Merchant order id is sent only in case of card payment
            enums::PaymentMethod::Card => Some(item.connector_request_reference_id.clone()),
            _ => None,
        };
        Ok(Self {
            initial_amount: item.request.amount,
            currency: item.request.currency,
            channel: NexinetsChannel::Ecom,
            product,
            payment,
            nexinets_async,
            merchant_order_id,
        })
    }
}

// Auth Struct
pub struct NexinetsAuthType {
    pub(super) api_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for NexinetsAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::BodyKey { api_key, key1 } => {
                let auth_key = format!("{}:{}", key1.peek(), api_key.peek());
                let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key));
                Ok(Self {
                    api_key: Secret::new(auth_header),
                })
            }
            _ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
        }
    }
}
// PaymentsResponse
#[derive(Debug, Deserialize, Clone, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexinetsPaymentStatus {
    Success,
    Pending,
    Ok,
    Failure,
    Declined,
    InProgress,
    Expired,
    Aborted,
}

fn get_status(status: NexinetsPaymentStatus, method: NexinetsTransactionType) -> AttemptStatus {
    match status {
        NexinetsPaymentStatus::Success => match method {
            NexinetsTransactionType::Preauth => AttemptStatus::Authorized,
            NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => {
                AttemptStatus::Charged
            }
            NexinetsTransactionType::Cancel => AttemptStatus::Voided,
        },
        NexinetsPaymentStatus::Declined
        | NexinetsPaymentStatus::Failure
        | NexinetsPaymentStatus::Expired
        | NexinetsPaymentStatus::Aborted => match method {
            NexinetsTransactionType::Preauth => AttemptStatus::AuthorizationFailed,
            NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => {
                AttemptStatus::CaptureFailed
            }
            NexinetsTransactionType::Cancel => AttemptStatus::VoidFailed,
        },
        NexinetsPaymentStatus::Ok => match method {
            NexinetsTransactionType::Preauth => AttemptStatus::Authorized,
            _ => AttemptStatus::Pending,
        },
        NexinetsPaymentStatus::Pending => AttemptStatus::AuthenticationPending,
        NexinetsPaymentStatus::InProgress => AttemptStatus::Pending,
    }
}

impl TryFrom<&enums::BankNames> for NexinetsBIC {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(bank: &enums::BankNames) -> Result<Self, Self::Error> {
        match bank {
            enums::BankNames::AbnAmro => Ok(Self::AbnAmro),
            enums::BankNames::AsnBank => Ok(Self::AsnBank),
            enums::BankNames::Bunq => Ok(Self::Bunq),
            enums::BankNames::Ing => Ok(Self::Ing),
            enums::BankNames::Knab => Ok(Self::Knab),
            enums::BankNames::Rabobank => Ok(Self::Rabobank),
            enums::BankNames::Regiobank => Ok(Self::Regiobank),
            enums::BankNames::SnsBank => Ok(Self::SnsBank),
            enums::BankNames::TriodosBank => Ok(Self::TriodosBank),
            enums::BankNames::VanLanschot => Ok(Self::VanLanschot),
            _ => Err(errors::ConnectorError::FlowNotSupported {
                flow: bank.to_string(),
                connector: "Nexinets".to_string(),
            }
            .into()),
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsPreAuthOrDebitResponse {
    order_id: String,
    transaction_type: NexinetsTransactionType,
    transactions: Vec<NexinetsTransaction>,
    payment_instrument: PaymentInstrument,
    redirect_url: Option<Url>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsTransaction {
    pub transaction_id: String,
    #[serde(rename = "type")]
    pub transaction_type: NexinetsTransactionType,
    pub currency: enums::Currency,
    pub status: NexinetsPaymentStatus,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexinetsTransactionType {
    Preauth,
    Debit,
    Capture,
    Cancel,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NexinetsPaymentsMetadata {
    pub transaction_id: Option<String>,
    pub order_id: Option<String>,
    pub psync_flow: NexinetsTransactionType,
}

impl<F, T> TryFrom<ResponseRouterData<F, NexinetsPreAuthOrDebitResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, NexinetsPreAuthOrDebitResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let transaction = match item.response.transactions.first() {
            Some(order) => order,
            _ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
        };
        let connector_metadata = serde_json::to_value(NexinetsPaymentsMetadata {
            transaction_id: Some(transaction.transaction_id.clone()),
            order_id: Some(item.response.order_id.clone()),
            psync_flow: item.response.transaction_type.clone(),
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)?;
        let redirection_data = item
            .response
            .redirect_url
            .map(|url| RedirectForm::from((url, Method::Get)));
        let resource_id = match item.response.transaction_type.clone() {
            NexinetsTransactionType::Preauth => ResponseId::NoResponseId,
            NexinetsTransactionType::Debit => {
                ResponseId::ConnectorTransactionId(transaction.transaction_id.clone())
            }
            _ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
        };
        let mandate_reference = item
            .response
            .payment_instrument
            .payment_instrument_id
            .map(|id| MandateReference {
                connector_mandate_id: Some(id.expose()),
                payment_method_id: None,
                mandate_metadata: None,
                connector_mandate_request_reference_id: None,
            });
        Ok(Self {
            status: get_status(transaction.status.clone(), item.response.transaction_type),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id,
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(mandate_reference),
                connector_metadata: Some(connector_metadata),
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.order_id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsCaptureOrVoidRequest {
    pub initial_amount: i64,
    pub currency: enums::Currency,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsOrder {
    pub order_id: String,
}

impl TryFrom<&PaymentsCaptureRouterData> for NexinetsCaptureOrVoidRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            initial_amount: item.request.amount_to_capture,
            currency: item.request.currency,
        })
    }
}

impl TryFrom<&PaymentsCancelRouterData> for NexinetsCaptureOrVoidRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            initial_amount: item.request.get_amount()?,
            currency: item.request.get_currency()?,
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsPaymentResponse {
    pub transaction_id: String,
    pub status: NexinetsPaymentStatus,
    pub order: NexinetsOrder,
    #[serde(rename = "type")]
    pub transaction_type: NexinetsTransactionType,
}

impl<F, T> TryFrom<ResponseRouterData<F, NexinetsPaymentResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, NexinetsPaymentResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = Some(item.response.transaction_id.clone());
        let connector_metadata = serde_json::to_value(NexinetsPaymentsMetadata {
            transaction_id,
            order_id: Some(item.response.order.order_id.clone()),
            psync_flow: item.response.transaction_type.clone(),
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)?;
        let resource_id = match item.response.transaction_type.clone() {
            NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => {
                ResponseId::ConnectorTransactionId(item.response.transaction_id)
            }
            _ => ResponseId::NoResponseId,
        };
        Ok(Self {
            status: get_status(item.response.status, item.response.transaction_type),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id,
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: Some(connector_metadata),
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.order.order_id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

// REFUND :
// Type definition for RefundRequest
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsRefundRequest {
    pub initial_amount: i64,
    pub currency: enums::Currency,
}

impl<F> TryFrom<&RefundsRouterData<F>> for NexinetsRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &RefundsRouterData<F>) -> Result<Self, Self::Error> {
        Ok(Self {
            initial_amount: item.request.refund_amount,
            currency: item.request.currency,
        })
    }
}

// Type definition for Refund Response
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsRefundResponse {
    pub transaction_id: String,
    pub status: RefundStatus,
    pub order: NexinetsOrder,
    #[serde(rename = "type")]
    pub transaction_type: RefundType,
}

#[allow(dead_code)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatus {
    Success,
    Ok,
    Failure,
    Declined,
    InProgress,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundType {
    Refund,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Success => Self::Success,
            RefundStatus::Failure | RefundStatus::Declined => Self::Failure,
            RefundStatus::InProgress | RefundStatus::Ok => Self::Pending,
        }
    }
}

impl TryFrom<RefundsResponseRouterData<Execute, NexinetsRefundResponse>>
    for RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, NexinetsRefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.transaction_id,
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, NexinetsRefundResponse>>
    for RefundsRouterData<RSync>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, NexinetsRefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.transaction_id,
                refund_status: enums::RefundStatus::from(item.response.status),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct NexinetsErrorResponse {
    pub status: u16,
    pub code: u16,
    pub message: String,
    pub errors: Vec<OrderErrorDetails>,
}

#[derive(Debug, Deserialize, Clone, Serialize)]
pub struct OrderErrorDetails {
    pub code: u16,
    pub message: String,
    pub field: Option<String>,
}

fn get_payment_details_and_product(
    item: &PaymentsAuthorizeRouterData,
) -> Result<
    (Option<NexinetsPaymentDetails>, NexinetsProduct),
    error_stack::Report<errors::ConnectorError>,
> {
    match &item.request.payment_method_data {
        PaymentMethodData::Card(card) => Ok((
            Some(get_card_data(item, card)?),
            NexinetsProduct::Creditcard,
        )),
        PaymentMethodData::Wallet(wallet) => Ok(get_wallet_details(wallet)?),
        PaymentMethodData::BankRedirect(bank_redirect) => match bank_redirect {
            BankRedirectData::Eps { .. } => Ok((None, NexinetsProduct::Eps)),
            BankRedirectData::Giropay { .. } => Ok((None, NexinetsProduct::Giropay)),
            BankRedirectData::Ideal { bank_name, .. } => Ok((
                Some(NexinetsPaymentDetails::BankRedirects(Box::new(
                    NexinetsBankRedirects {
                        bic: bank_name
                            .map(|bank_name| NexinetsBIC::try_from(&bank_name))
                            .transpose()?,
                    },
                ))),
                NexinetsProduct::Ideal,
            )),
            BankRedirectData::Sofort { .. } => Ok((None, NexinetsProduct::Sofort)),
            BankRedirectData::BancontactCard { .. }
            | BankRedirectData::Blik { .. }
            | BankRedirectData::Bizum { .. }
            | BankRedirectData::Eft { .. }
            | BankRedirectData::Interac { .. }
            | BankRedirectData::OnlineBankingCzechRepublic { .. }
            | BankRedirectData::OnlineBankingFinland { .. }
            | BankRedirectData::OnlineBankingPoland { .. }
            | BankRedirectData::OnlineBankingSlovakia { .. }
            | BankRedirectData::OpenBankingUk { .. }
            | BankRedirectData::Przelewy24 { .. }
            | BankRedirectData::Trustly { .. }
            | BankRedirectData::OnlineBankingFpx { .. }
            | BankRedirectData::OnlineBankingThailand { .. }
            | BankRedirectData::LocalBankRedirect {} => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("nexinets"),
                ))?
            }
        },
        PaymentMethodData::CardRedirect(_)
        | PaymentMethodData::PayLater(_)
        | PaymentMethodData::BankDebit(_)
        | PaymentMethodData::BankTransfer(_)
        | PaymentMethodData::Crypto(_)
        | PaymentMethodData::MandatePayment
        | PaymentMethodData::Reward
        | PaymentMethodData::RealTimePayment(_)
        | PaymentMethodData::MobilePayment(_)
        | PaymentMethodData::Upi(_)
        | PaymentMethodData::Voucher(_)
        | PaymentMethodData::GiftCard(_)
        | PaymentMethodData::OpenBanking(_)
        | PaymentMethodData::CardToken(_)
        | PaymentMethodData::NetworkToken(_)
        | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
            Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("nexinets"),
            ))?
        }
    }
}

fn get_card_data(
    item: &PaymentsAuthorizeRouterData,
    card: &Card,
) -> Result<NexinetsPaymentDetails, errors::ConnectorError> {
    let (card_data, cof_contract) = match item.request.is_mandate_payment() {
        true => {
            let card_data = match item.request.off_session {
                Some(true) => CardDataDetails::PaymentInstrument(Box::new(PaymentInstrument {
                    payment_instrument_id: item.request.connector_mandate_id().map(Secret::new),
                })),
                _ => CardDataDetails::CardDetails(Box::new(get_card_details(card)?)),
            };
            let cof_contract = Some(CofContract {
                recurring_type: RecurringType::Unscheduled,
            });
            (card_data, cof_contract)
        }
        false => (
            CardDataDetails::CardDetails(Box::new(get_card_details(card)?)),
            None,
        ),
    };
    Ok(NexinetsPaymentDetails::Card(Box::new(NexiCardDetails {
        card_data,
        cof_contract,
    })))
}

fn get_applepay_details(
    wallet_data: &WalletData,
    applepay_data: &ApplePayWalletData,
) -> CustomResult<ApplePayDetails, errors::ConnectorError> {
    let payment_data = WalletData::get_wallet_token_as_json(wallet_data, "Apple Pay".to_string())?;
    Ok(ApplePayDetails {
        payment_data,
        payment_method: ApplepayPaymentMethod {
            display_name: applepay_data.payment_method.display_name.to_owned(),
            network: applepay_data.payment_method.network.to_owned(),
            token_type: applepay_data.payment_method.pm_type.to_owned(),
        },
        transaction_identifier: applepay_data.transaction_identifier.to_owned(),
    })
}

fn get_card_details(req_card: &Card) -> Result<CardDetails, errors::ConnectorError> {
    Ok(CardDetails {
        card_number: req_card.card_number.clone(),
        expiry_month: req_card.card_exp_month.clone(),
        expiry_year: req_card.get_card_expiry_year_2_digit()?,
        verification: req_card.card_cvc.clone(),
    })
}

fn get_wallet_details(
    wallet: &WalletData,
) -> Result<
    (Option<NexinetsPaymentDetails>, NexinetsProduct),
    error_stack::Report<errors::ConnectorError>,
> {
    match wallet {
        WalletData::PaypalRedirect(_) => Ok((None, NexinetsProduct::Paypal)),
        WalletData::ApplePay(applepay_data) => Ok((
            Some(NexinetsPaymentDetails::Wallet(Box::new(
                NexinetsWalletDetails::ApplePayToken(Box::new(get_applepay_details(
                    wallet,
                    applepay_data,
                )?)),
            ))),
            NexinetsProduct::Applepay,
        )),
        WalletData::AliPayQr(_)
        | WalletData::AliPayRedirect(_)
        | WalletData::AliPayHkRedirect(_)
        | WalletData::AmazonPay(_)
        | WalletData::AmazonPayRedirect(_)
        | WalletData::Paysera(_)
        | WalletData::Skrill(_)
        | WalletData::BluecodeRedirect {}
        | WalletData::MomoRedirect(_)
        | WalletData::KakaoPayRedirect(_)
        | WalletData::GoPayRedirect(_)
        | WalletData::GcashRedirect(_)
        | WalletData::ApplePayRedirect(_)
        | WalletData::ApplePayThirdPartySdk(_)
        | WalletData::DanaRedirect { .. }
        | WalletData::GooglePay(_)
        | WalletData::GooglePayRedirect(_)
        | WalletData::GooglePayThirdPartySdk(_)
        | WalletData::MbWayRedirect(_)
        | WalletData::MobilePayRedirect(_)
        | WalletData::PaypalSdk(_)
        | WalletData::Paze(_)
        | WalletData::SamsungPay(_)
        | WalletData::TwintRedirect { .. }
        | WalletData::VippsRedirect { .. }
        | WalletData::TouchNGoRedirect(_)
        | WalletData::WeChatPayRedirect(_)
        | WalletData::WeChatPayQr(_)
        | WalletData::CashappQr(_)
        | WalletData::SwishQr(_)
        | WalletData::Mifinity(_)
        | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented(
            utils::get_unimplemented_payment_method_error_message("nexinets"),
        ))?,
    }
}

pub fn get_order_id(
    meta: &NexinetsPaymentsMetadata,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
    let order_id = meta.order_id.clone().ok_or(
        errors::ConnectorError::MissingConnectorRelatedTransactionID {
            id: "order_id".to_string(),
        },
    )?;
    Ok(order_id)
}

pub fn get_transaction_id(
    meta: &NexinetsPaymentsMetadata,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
    let transaction_id = meta.transaction_id.clone().ok_or(
        errors::ConnectorError::MissingConnectorRelatedTransactionID {
            id: "transaction_id".to_string(),
        },
    )?;
    Ok(transaction_id)
}


// Implementation: impl webhooks::IncomingWebhook for for Digitalvirgo
// File: crates/hyperswitch_connectors/src/connectors/digitalvirgo.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl webhooks::IncomingWebhook for for Digitalvirgo


// File: crates/analytics/src/payments/metrics/sessionized_metrics/retries_count.rs
// Module: analytics

use std::collections::HashSet;

use api_models::{
    analytics::{
        payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier},
        Granularity, TimeRange,
    },
    enums::IntentStatus,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::PaymentMetricRow;
use crate::{
    enums::AuthInfo,
    query::{
        Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql,
        Window,
    },
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};

#[derive(Default)]
pub(crate) struct RetriesCount;

#[async_trait::async_trait]
impl<T> super::PaymentMetric<T> for RetriesCount
where
    T: AnalyticsDataSource + super::PaymentMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        _dimensions: &[PaymentDimensions],
        auth: &AuthInfo,
        _filters: &PaymentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::PaymentIntentSessionized);
        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Sum {
                field: "amount",
                alias: Some("total"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;
        auth.set_filter_clause(&mut query_builder).switch()?;

        query_builder
            .add_custom_filter_clause("attempt_count", "1", FilterTypes::Gt)
            .switch()?;
        query_builder
            .add_custom_filter_clause("status", IntentStatus::Succeeded, FilterTypes::Equal)
            .switch()?;
        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<PaymentMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentMetricsBucketIdentifier::new(
                        i.currency.as_ref().map(|i| i.0),
                        None,
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.client_source.clone(),
                        i.client_version.clone(),
                        i.profile_id.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        i.routing_approach.as_ref().map(|i| i.0.clone()),
                        i.signature_network.clone(),
                        i.is_issuer_regulated,
                        i.is_debit_routed,
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// File: crates/external_services/src/grpc_client/dynamic_routing/contract_routing_client.rs
// Module: external_services

use api_models::routing::{
    ContractBasedRoutingConfig, ContractBasedRoutingConfigBody, ContractBasedTimeScale,
    LabelInformation, RoutableConnectorChoice, RoutableConnectorChoiceWithStatus,
};
use common_utils::{
    ext_traits::OptionExt,
    transformers::{ForeignFrom, ForeignTryFrom},
};
pub use contract_routing::{
    contract_score_calculator_client::ContractScoreCalculatorClient, CalContractScoreConfig,
    CalContractScoreRequest, CalContractScoreResponse, InvalidateContractRequest,
    InvalidateContractResponse, LabelInformation as ProtoLabelInfo, TimeScale,
    UpdateContractRequest, UpdateContractResponse,
};
use error_stack::ResultExt;
use router_env::logger;

use crate::grpc_client::{self, GrpcHeaders};
#[allow(
    missing_docs,
    unused_qualifications,
    clippy::unwrap_used,
    clippy::as_conversions,
    clippy::use_self
)]
pub mod contract_routing {
    tonic::include_proto!("contract_routing");
}
pub use tonic::Code;

use super::{Client, DynamicRoutingError, DynamicRoutingResult};
/// The trait ContractBasedDynamicRouting would have the functions required to support the calculation and updation window
#[async_trait::async_trait]
pub trait ContractBasedDynamicRouting: dyn_clone::DynClone + Send + Sync {
    /// To calculate the contract scores for the list of chosen connectors
    async fn calculate_contract_score(
        &self,
        id: String,
        config: ContractBasedRoutingConfig,
        params: String,
        label_input: Vec<RoutableConnectorChoice>,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<CalContractScoreResponse>;
    /// To update the contract scores with the given labels
    async fn update_contracts(
        &self,
        id: String,
        label_info: Vec<LabelInformation>,
        params: String,
        response: Vec<RoutableConnectorChoiceWithStatus>,
        incr_count: u64,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<UpdateContractResponse>;
    /// To invalidates the contract scores against the id
    async fn invalidate_contracts(
        &self,
        id: String,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<InvalidateContractResponse>;
}

#[async_trait::async_trait]
impl ContractBasedDynamicRouting for ContractScoreCalculatorClient<Client> {
    async fn calculate_contract_score(
        &self,
        id: String,
        config: ContractBasedRoutingConfig,
        params: String,
        label_input: Vec<RoutableConnectorChoice>,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<CalContractScoreResponse> {
        let labels = label_input
            .into_iter()
            .map(|conn_choice| conn_choice.to_string())
            .collect::<Vec<_>>();

        let config = config
            .config
            .map(ForeignTryFrom::foreign_try_from)
            .transpose()?;

        let request = grpc_client::create_grpc_request(
            CalContractScoreRequest {
                id,
                params,
                labels,
                config,
            },
            headers,
        );

        let response = self
            .clone()
            .fetch_contract_score(request)
            .await
            .map_err(|err| match err.code() {
                Code::NotFound => DynamicRoutingError::ContractNotFound,
                _ => DynamicRoutingError::ContractBasedRoutingFailure(err.to_string()),
            })?
            .into_inner();

        logger::info!(dynamic_routing_response=?response);

        Ok(response)
    }

    async fn update_contracts(
        &self,
        id: String,
        label_info: Vec<LabelInformation>,
        params: String,
        _response: Vec<RoutableConnectorChoiceWithStatus>,
        incr_count: u64,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<UpdateContractResponse> {
        let mut labels_information = label_info
            .into_iter()
            .map(ProtoLabelInfo::foreign_from)
            .collect::<Vec<_>>();

        labels_information
            .iter_mut()
            .for_each(|info| info.current_count += incr_count);

        let request = grpc_client::create_grpc_request(
            UpdateContractRequest {
                id,
                params,
                labels_information,
            },
            headers,
        );

        let response = self
            .clone()
            .update_contract(request)
            .await
            .change_context(DynamicRoutingError::ContractBasedRoutingFailure(
                "Failed to update the contracts".to_string(),
            ))?
            .into_inner();

        logger::info!(dynamic_routing_response=?response);

        Ok(response)
    }
    async fn invalidate_contracts(
        &self,
        id: String,
        headers: GrpcHeaders,
    ) -> DynamicRoutingResult<InvalidateContractResponse> {
        let request = grpc_client::create_grpc_request(InvalidateContractRequest { id }, headers);

        let response = self
            .clone()
            .invalidate_contract(request)
            .await
            .change_context(DynamicRoutingError::ContractBasedRoutingFailure(
                "Failed to invalidate the contracts".to_string(),
            ))?
            .into_inner();
        Ok(response)
    }
}

impl ForeignFrom<ContractBasedTimeScale> for TimeScale {
    fn foreign_from(scale: ContractBasedTimeScale) -> Self {
        Self {
            time_scale: match scale {
                ContractBasedTimeScale::Day => 0,
                _ => 1,
            },
        }
    }
}

impl ForeignTryFrom<ContractBasedRoutingConfigBody> for CalContractScoreConfig {
    type Error = error_stack::Report<DynamicRoutingError>;
    fn foreign_try_from(config: ContractBasedRoutingConfigBody) -> Result<Self, Self::Error> {
        Ok(Self {
            constants: config
                .constants
                .get_required_value("constants")
                .change_context(DynamicRoutingError::MissingRequiredField {
                    field: "constants".to_string(),
                })?,
            time_scale: config.time_scale.clone().map(TimeScale::foreign_from),
        })
    }
}

impl ForeignFrom<LabelInformation> for ProtoLabelInfo {
    fn foreign_from(config: LabelInformation) -> Self {
        Self {
            label: format!(
                "{}:{}",
                config.label.clone(),
                config.mca_id.get_string_repr()
            ),
            target_count: config.target_count,
            target_time: config.target_time,
            current_count: u64::default(),
        }
    }
}


// Function: validate
// File: crates/router/src/configs/validations.rs
// Module: router

pub fn validate(&self) -> Result<(), ApplicationError>


// Function: update_trackers
// File: crates/router/src/core/authentication/utils.rs
// Module: router

pub fn update_trackers<F: Clone, Req>(
    state: &SessionState,
    router_data: RouterData<F, Req, AuthenticationResponseData>,
    authentication: storage::Authentication,
    acquirer_details: Option<super::types::AcquirerDetails>,
    merchant_key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
) -> RouterResult<storage::Authentication>


// Implementation: impl api::PaymentSync for for Zsl
// File: crates/hyperswitch_connectors/src/connectors/zsl.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSync for for Zsl


// Struct: KafkaPaymentAttemptEvent
// File: crates/router/src/services/kafka/payment_attempt_event.rs
// Module: router
// Implementations: 3
// Traits: super::KafkaMessage

pub struct KafkaPaymentAttemptEvent<'a>


// File: crates/router/src/routes/payments/helpers.rs
// Module: router
// Public functions: 1

use error_stack::ResultExt;

use crate::{
    core::errors::{self, RouterResult},
    logger,
    types::{self, api},
    utils::{Encode, ValueExt},
};

#[cfg(feature = "v1")]
pub fn populate_browser_info(
    req: &actix_web::HttpRequest,
    payload: &mut api::PaymentsRequest,
    header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
) -> RouterResult<()> {
    let mut browser_info: types::BrowserInformation = payload
        .browser_info
        .clone()
        .map(|v| v.parse_value("BrowserInformation"))
        .transpose()
        .change_context_lazy(|| errors::ApiErrorResponse::InvalidRequestData {
            message: "invalid format for 'browser_info' provided".to_string(),
        })?
        .unwrap_or(types::BrowserInformation {
            color_depth: None,
            java_enabled: None,
            java_script_enabled: None,
            language: None,
            screen_height: None,
            screen_width: None,
            time_zone: None,
            accept_header: None,
            user_agent: None,
            ip_address: None,
            os_type: None,
            os_version: None,
            device_model: None,
            accept_language: None,
            referer: None,
        });

    let ip_address = req
        .connection_info()
        .realip_remote_addr()
        .map(ToOwned::to_owned);

    if ip_address.is_some() {
        logger::debug!("Extracted ip address from request");
    }

    browser_info.ip_address = browser_info.ip_address.or_else(|| {
        ip_address
            .as_ref()
            .map(|ip| ip.parse())
            .transpose()
            .unwrap_or_else(|error| {
                logger::error!(
                    ?error,
                    "failed to parse ip address which is extracted from the request"
                );
                None
            })
    });

    // If the locale is present in the header payload, we will use it as the accept language
    if header_payload.locale.is_some() {
        browser_info.accept_language = browser_info
            .accept_language
            .or(header_payload.locale.clone());
    }

    if let Some(api::MandateData {
        customer_acceptance:
            Some(api::CustomerAcceptance {
                online:
                    Some(api::OnlineMandate {
                        ip_address: req_ip, ..
                    }),
                ..
            }),
        ..
    }) = &mut payload.mandate_data
    {
        *req_ip = req_ip
            .clone()
            .or_else(|| ip_address.map(|ip| masking::Secret::new(ip.to_string())));
    }

    let encoded = browser_info
        .encode_to_value()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable(
            "failed to re-encode browser information to json after setting ip address",
        )?;

    payload.browser_info = Some(encoded);
    Ok(())
}


// Function: populate_bin_details_for_payment_method_create
// File: crates/payment_methods/src/helpers.rs
// Module: payment_methods

pub fn populate_bin_details_for_payment_method_create(
    card_details: api_models::payment_methods::CardDetail,
    db: Box<dyn state::PaymentMethodsStorageInterface>,
) -> api_models::payment_methods::CardDetail


// Implementation: impl CommonStorageInterface for for MockDb
// File: crates/router/src/db.rs
// Module: router
// Methods: 3 total (0 public)

impl CommonStorageInterface for for MockDb


// Struct: TrustpayErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/trustpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct TrustpayErrorResponse


// File: crates/analytics/src/auth_events/metrics/authentication_exemption_requested_count.rs
// Module: analytics

use std::collections::HashSet;

use api_models::analytics::{
    auth_events::{AuthEventDimensions, AuthEventFilters, AuthEventMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::AuthEventMetricRow;
use crate::{
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
    AuthInfo,
};

#[derive(Default)]
pub(super) struct AuthenticationExemptionRequestedCount;

#[async_trait::async_trait]
impl<T> super::AuthEventMetric<T> for AuthenticationExemptionRequestedCount
where
    T: AnalyticsDataSource + super::AuthEventMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        auth: &AuthInfo,
        dimensions: &[AuthEventDimensions],
        filters: &AuthEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::Authentications);
        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }
        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;

        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;

        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;

        query_builder
            .add_filter_clause(AuthEventDimensions::ExemptionRequested, true)
            .switch()?;

        filters.set_filter_clause(&mut query_builder).switch()?;
        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;
        auth.set_filter_clause(&mut query_builder).switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<AuthEventMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    AuthEventMetricsBucketIdentifier::new(
                        i.authentication_status.as_ref().map(|i| i.0),
                        i.trans_status.as_ref().map(|i| i.0.clone()),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.error_message.clone(),
                        i.authentication_connector.as_ref().map(|i| i.0),
                        i.message_version.clone(),
                        i.acs_reference_number.clone(),
                        i.mcc.clone(),
                        i.currency.as_ref().map(|i| i.0),
                        i.merchant_country.clone(),
                        i.billing_country.clone(),
                        i.shipping_country.clone(),
                        i.issuer_country.clone(),
                        i.earliest_supported_version.clone(),
                        i.latest_supported_version.clone(),
                        i.whitelist_decision,
                        i.device_manufacturer.clone(),
                        i.device_type.clone(),
                        i.device_brand.clone(),
                        i.device_os.clone(),
                        i.device_display.clone(),
                        i.browser_name.clone(),
                        i.browser_version.clone(),
                        i.issuer_id.clone(),
                        i.scheme_name.clone(),
                        i.exemption_requested,
                        i.exemption_accepted,
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// File: crates/api_models/src/analytics/outgoing_webhook_event.rs
// Module: api_models
// Public structs: 1

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct OutgoingWebhookLogsRequest {
    pub payment_id: common_utils::id_type::PaymentId,
    pub event_id: Option<String>,
    pub refund_id: Option<String>,
    pub dispute_id: Option<String>,
    pub mandate_id: Option<String>,
    pub payment_method_id: Option<String>,
    pub attempt_id: Option<String>,
}


// Struct: PaymentProcessorTokenDetails
// File: crates/router/src/types/storage/revenue_recovery_redis_operation.rs
// Module: router
// Implementations: 0

pub struct PaymentProcessorTokenDetails


// Implementation: impl api::PaymentSession for for Tsys
// File: crates/hyperswitch_connectors/src/connectors/tsys.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentSession for for Tsys


// Implementation: impl Responder
// File: crates/router/src/routes/routing.rs
// Module: router
// Methods: 0 total (0 public)

impl Responder


// Implementation: impl ThemeLineage
// File: crates/common_utils/src/types/user/theme.rs
// Module: common_utils
// Methods: 7 total (7 public)

impl ThemeLineage


// Function: get_payment_response_hash_key
// File: crates/api_models/src/admin.rs
// Module: api_models

pub fn get_payment_response_hash_key(&self) -> Option<String>


// Function: get_decrypted_wallet_payment_method_token
// File: crates/router/src/core/payments.rs
// Module: router

pub fn get_decrypted_wallet_payment_method_token<F, Req, D>(
    operation: &BoxedOperation<'_, F, Req, D>,
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_data: &mut D,
    connector_call_type_optional: Option<&ConnectorCallType>,
) -> CustomResult<Option<PaymentMethodToken>, errors::ApiErrorResponse>
where
    F: Send + Clone + Sync,
    D: OperationSessionGetters<F> + Send + Sync + Clone,


// Function: update_storage_scheme
// File: crates/diesel_models/src/customers.rs
// Module: diesel_models

pub fn update_storage_scheme(&mut self, storage_scheme: common_enums::MerchantStorageScheme)


// File: crates/router/src/routes/proxy.rs
// Module: router
// Public functions: 1

use actix_web::{web, Responder};
use router_env::{instrument, tracing, Flow};

use crate::{
    self as app,
    core::{api_locking, proxy},
    services::{api, authentication as auth},
    types::domain,
};

#[instrument(skip_all, fields(flow = ?Flow::Proxy))]
pub async fn proxy(
    state: web::Data<app::AppState>,
    req: actix_web::HttpRequest,
    payload: web::Json<api_models::proxy::ProxyRequest>,
) -> impl Responder {
    let flow = Flow::Proxy;

    Box::pin(api::server_wrap(
        flow,
        state,
        &req,
        payload.into_inner(),
        |state, auth: auth::AuthenticationData, req, _| {
            let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
                domain::Context(auth.merchant_account, auth.key_store),
            ));
            proxy::proxy_core(state, merchant_context, req)
        },
        &auth::V2ApiKeyAuth {
            is_connected_allowed: false,
            is_platform_allowed: false,
        },
        api_locking::LockAction::NotApplicable,
    ))
    .await
}


// Struct: KakaoPayRedirection
// File: crates/hyperswitch_domain_models/src/payment_method_data.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct KakaoPayRedirection


// Implementation: impl api::Payment for for Phonepe
// File: crates/hyperswitch_connectors/src/connectors/phonepe.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::Payment for for Phonepe


// Struct: TokenizedCard
// File: crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct TokenizedCard


// Implementation: impl MandateUpdate
// File: crates/diesel_models/src/mandate.rs
// Module: diesel_models
// Methods: 1 total (1 public)

impl MandateUpdate