// File: crates/router/src/core/routing/helpers.rs
// Module: router
// Public functions: 32
// Public structs: 5

//! Analysis for usage of all helper functions for use case of routing
//!
//! Functions that are used to perform the retrieval of merchant's
//! routing dict, configs, defaults
use std::fmt::Debug;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use std::str::FromStr;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use std::sync::Arc;

#[cfg(feature = "v1")]
use api_models::open_router;
use api_models::routing as routing_types;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use common_utils::ext_traits::ValueExt;
use common_utils::{ext_traits::Encode, id_type, types::keymanager::KeyManagerState};
use diesel_models::configs;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use diesel_models::dynamic_routing_stats::{DynamicRoutingStatsNew, DynamicRoutingStatsUpdate};
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use diesel_models::routing_algorithm;
use error_stack::ResultExt;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use external_services::grpc_client::dynamic_routing::{
    contract_routing_client::ContractBasedDynamicRouting,
    elimination_based_client::EliminationBasedRouting,
    success_rate_client::SuccessBasedDynamicRouting,
};
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use hyperswitch_domain_models::api::ApplicationResponse;
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use hyperswitch_interfaces::events::routing_api_logs as routing_events;
#[cfg(feature = "v1")]
use router_env::logger;
#[cfg(feature = "v1")]
use router_env::{instrument, tracing};
use rustc_hash::FxHashSet;
use storage_impl::redis::cache;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use storage_impl::redis::cache::Cacheable;

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use crate::db::errors::StorageErrorExt;
#[cfg(feature = "v2")]
use crate::types::domain::MerchantConnectorAccount;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use crate::types::transformers::ForeignFrom;
use crate::{
    core::errors::{self, RouterResult},
    db::StorageInterface,
    routes::SessionState,
    types::{domain, storage},
    utils::StringExt,
};
#[cfg(feature = "v1")]
use crate::{
    core::payments::{
        routing::utils::{self as routing_utils, DecisionEngineApiHandler},
        OperationSessionGetters, OperationSessionSetters,
    },
    services,
};
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use crate::{
    core::{metrics as core_metrics, routing},
    routes::app::SessionStateInfo,
    types::transformers::ForeignInto,
};
pub const SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
    "Success rate based dynamic routing algorithm";
pub const ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
    "Elimination based dynamic routing algorithm";
pub const CONTRACT_BASED_DYNAMIC_ROUTING_ALGORITHM: &str =
    "Contract based dynamic routing algorithm";

pub const DECISION_ENGINE_RULE_CREATE_ENDPOINT: &str = "rule/create";
pub const DECISION_ENGINE_RULE_UPDATE_ENDPOINT: &str = "rule/update";
pub const DECISION_ENGINE_RULE_GET_ENDPOINT: &str = "rule/get";
pub const DECISION_ENGINE_RULE_DELETE_ENDPOINT: &str = "rule/delete";
pub const DECISION_ENGINE_MERCHANT_BASE_ENDPOINT: &str = "merchant-account";
pub const DECISION_ENGINE_MERCHANT_CREATE_ENDPOINT: &str = "merchant-account/create";

/// Provides us with all the configured configs of the Merchant in the ascending time configured
/// manner and chooses the first of them
pub async fn get_merchant_default_config(
    db: &dyn StorageInterface,
    // Cannot make this as merchant id domain type because, we are passing profile id also here
    merchant_id: &str,
    transaction_type: &storage::enums::TransactionType,
) -> RouterResult<Vec<routing_types::RoutableConnectorChoice>> {
    let key = get_default_config_key(merchant_id, transaction_type);
    let maybe_config = db.find_config_by_key(&key).await;

    match maybe_config {
        Ok(config) => config
            .config
            .parse_struct("Vec<RoutableConnectors>")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Merchant default config has invalid structure"),

        Err(e) if e.current_context().is_db_not_found() => {
            let new_config_conns = Vec::<routing_types::RoutableConnectorChoice>::new();
            let serialized = new_config_conns
                .encode_to_string_of_json()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "Error while creating and serializing new merchant default config",
                )?;

            let new_config = configs::ConfigNew {
                key,
                config: serialized,
            };

            db.insert_config(new_config)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error inserting new default routing config into DB")?;

            Ok(new_config_conns)
        }

        Err(e) => Err(e)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error fetching default config for merchant"),
    }
}

/// Merchant's already created config can be updated and this change will be reflected
/// in DB as well for the particular updated config
pub async fn update_merchant_default_config(
    db: &dyn StorageInterface,
    merchant_id: &str,
    connectors: Vec<routing_types::RoutableConnectorChoice>,
    transaction_type: &storage::enums::TransactionType,
) -> RouterResult<()> {
    let key = get_default_config_key(merchant_id, transaction_type);
    let config_str = connectors
        .encode_to_string_of_json()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to serialize merchant default routing config during update")?;

    let config_update = configs::ConfigUpdate::Update {
        config: Some(config_str),
    };

    db.update_config_by_key(&key, config_update)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error updating the default routing config in DB")?;

    Ok(())
}

pub async fn update_merchant_routing_dictionary(
    db: &dyn StorageInterface,
    merchant_id: &str,
    dictionary: routing_types::RoutingDictionary,
) -> RouterResult<()> {
    let key = get_routing_dictionary_key(merchant_id);
    let dictionary_str = dictionary
        .encode_to_string_of_json()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to serialize routing dictionary during update")?;

    let config_update = configs::ConfigUpdate::Update {
        config: Some(dictionary_str),
    };

    db.update_config_by_key(&key, config_update)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error saving routing dictionary to DB")?;

    Ok(())
}

/// This will help make one of all configured algorithms to be in active state for a particular
/// merchant
#[cfg(feature = "v1")]
pub async fn update_merchant_active_algorithm_ref(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    config_key: cache::CacheKind<'_>,
    algorithm_id: routing_types::RoutingAlgorithmRef,
) -> RouterResult<()> {
    let ref_value = algorithm_id
        .encode_to_value()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed converting routing algorithm ref to json value")?;

    let merchant_account_update = storage::MerchantAccountUpdate::Update {
        merchant_name: None,
        merchant_details: None,
        return_url: None,
        webhook_details: None,
        sub_merchants_enabled: None,
        parent_merchant_id: None,
        enable_payment_response_hash: None,
        payment_response_hash_key: None,
        redirect_to_merchant_with_http_post: None,
        publishable_key: None,
        locker_id: None,
        metadata: None,
        routing_algorithm: Some(ref_value),
        primary_business_details: None,
        intent_fulfillment_time: None,
        frm_routing_algorithm: None,
        payout_routing_algorithm: None,
        default_profile: None,
        payment_link_config: None,
        pm_collect_link_config: None,
    };

    let db = &*state.store;
    db.update_specific_fields_in_merchant(
        &state.into(),
        &key_store.merchant_id,
        merchant_account_update,
        key_store,
    )
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to update routing algorithm ref in merchant account")?;

    cache::redact_from_redis_and_publish(db.get_cache_store().as_ref(), [config_key])
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to invalidate the config cache")?;

    Ok(())
}

#[cfg(feature = "v1")]
pub async fn update_profile_active_algorithm_ref(
    db: &dyn StorageInterface,
    key_manager_state: &KeyManagerState,
    merchant_key_store: &domain::MerchantKeyStore,
    current_business_profile: domain::Profile,
    algorithm_id: routing_types::RoutingAlgorithmRef,
    transaction_type: &storage::enums::TransactionType,
) -> RouterResult<()> {
    let ref_val = algorithm_id
        .encode_to_value()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to convert routing ref to value")?;

    let merchant_id = current_business_profile.merchant_id.clone();

    let profile_id = current_business_profile.get_id().to_owned();

    let (routing_algorithm, payout_routing_algorithm, three_ds_decision_rule_algorithm) =
        match transaction_type {
            storage::enums::TransactionType::Payment => (Some(ref_val), None, None),
            #[cfg(feature = "payouts")]
            storage::enums::TransactionType::Payout => (None, Some(ref_val), None),
            storage::enums::TransactionType::ThreeDsAuthentication => (None, None, Some(ref_val)),
        };

    let business_profile_update = domain::ProfileUpdate::RoutingAlgorithmUpdate {
        routing_algorithm,
        payout_routing_algorithm,
        three_ds_decision_rule_algorithm,
    };

    db.update_profile_by_profile_id(
        key_manager_state,
        merchant_key_store,
        current_business_profile,
        business_profile_update,
    )
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to update routing algorithm ref in business profile")?;

    // Invalidate the routing cache for Payments and Payouts transaction types
    if !transaction_type.is_three_ds_authentication() {
        let routing_cache_key = cache::CacheKind::Routing(
            format!(
                "routing_config_{}_{}",
                merchant_id.get_string_repr(),
                profile_id.get_string_repr(),
            )
            .into(),
        );

        cache::redact_from_redis_and_publish(db.get_cache_store().as_ref(), [routing_cache_key])
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to invalidate routing cache")?;
    }
    Ok(())
}

#[cfg(feature = "v1")]
pub async fn update_business_profile_active_dynamic_algorithm_ref(
    db: &dyn StorageInterface,
    key_manager_state: &KeyManagerState,
    merchant_key_store: &domain::MerchantKeyStore,
    current_business_profile: domain::Profile,
    dynamic_routing_algorithm_ref: routing_types::DynamicRoutingAlgorithmRef,
) -> RouterResult<()> {
    let ref_val = dynamic_routing_algorithm_ref
        .encode_to_value()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to convert dynamic routing ref to value")?;
    let business_profile_update = domain::ProfileUpdate::DynamicRoutingAlgorithmUpdate {
        dynamic_routing_algorithm: Some(ref_val),
    };
    db.update_profile_by_profile_id(
        key_manager_state,
        merchant_key_store,
        current_business_profile,
        business_profile_update,
    )
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to update dynamic routing algorithm ref in business profile")?;
    Ok(())
}

#[cfg(feature = "v2")]
#[derive(Clone, Debug)]
pub struct RoutingAlgorithmHelpers<'h> {
    pub name_mca_id_set: ConnectNameAndMCAIdForProfile<'h>,
    pub name_set: ConnectNameForProfile<'h>,
    pub routing_algorithm: &'h routing_types::StaticRoutingAlgorithm,
}

#[cfg(feature = "v1")]
pub enum RoutingDecisionData {
    DebitRouting(DebitRoutingDecisionData),
}
#[cfg(feature = "v1")]
pub struct DebitRoutingDecisionData {
    pub card_network: common_enums::enums::CardNetwork,
    pub debit_routing_result: Option<open_router::DebitRoutingOutput>,
}
#[cfg(feature = "v1")]
impl RoutingDecisionData {
    pub fn apply_routing_decision<F, D>(&self, payment_data: &mut D)
    where
        F: Send + Clone,
        D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
    {
        match self {
            Self::DebitRouting(data) => data.apply_debit_routing_decision(payment_data),
        }
    }

    pub fn get_debit_routing_decision_data(
        network: common_enums::enums::CardNetwork,
        debit_routing_result: Option<open_router::DebitRoutingOutput>,
    ) -> Self {
        Self::DebitRouting(DebitRoutingDecisionData {
            card_network: network,
            debit_routing_result,
        })
    }
}
#[cfg(feature = "v1")]
impl DebitRoutingDecisionData {
    pub fn apply_debit_routing_decision<F, D>(&self, payment_data: &mut D)
    where
        F: Send + Clone,
        D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
    {
        payment_data.set_card_network(self.card_network.clone());
        self.debit_routing_result
            .as_ref()
            .map(|data| payment_data.set_co_badged_card_data(data));
    }
}
#[derive(Clone, Debug)]
pub struct ConnectNameAndMCAIdForProfile<'a>(
    pub  FxHashSet<(
        &'a common_enums::connector_enums::Connector,
        id_type::MerchantConnectorAccountId,
    )>,
);
#[derive(Clone, Debug)]
pub struct ConnectNameForProfile<'a>(pub FxHashSet<&'a common_enums::connector_enums::Connector>);

#[cfg(feature = "v2")]
impl RoutingAlgorithmHelpers<'_> {
    fn connector_choice(
        &self,
        choice: &routing_types::RoutableConnectorChoice,
    ) -> RouterResult<()> {
        if let Some(ref mca_id) = choice.merchant_connector_id {
            let connector_choice = common_enums::connector_enums::Connector::from(choice.connector);
            error_stack::ensure!(
                self.name_mca_id_set.0.contains(&(&connector_choice, mca_id.clone())),
                errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "connector with name '{connector_choice}' and merchant connector account id '{mca_id:?}' not found for the given profile",
                    )
                }
            );
        } else {
            let connector_choice = common_enums::connector_enums::Connector::from(choice.connector);
            error_stack::ensure!(
                self.name_set.0.contains(&connector_choice),
                errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "connector with name '{connector_choice}' not found for the given profile",
                    )
                }
            );
        };
        Ok(())
    }

    pub fn validate_connectors_in_routing_config(&self) -> RouterResult<()> {
        match self.routing_algorithm {
            routing_types::StaticRoutingAlgorithm::Single(choice) => {
                self.connector_choice(choice)?;
            }

            routing_types::StaticRoutingAlgorithm::Priority(list) => {
                for choice in list {
                    self.connector_choice(choice)?;
                }
            }

            routing_types::StaticRoutingAlgorithm::VolumeSplit(splits) => {
                for split in splits {
                    self.connector_choice(&split.connector)?;
                }
            }

            routing_types::StaticRoutingAlgorithm::Advanced(program) => {
                let check_connector_selection =
                    |selection: &routing_types::ConnectorSelection| -> RouterResult<()> {
                        match selection {
                            routing_types::ConnectorSelection::VolumeSplit(splits) => {
                                for split in splits {
                                    self.connector_choice(&split.connector)?;
                                }
                            }

                            routing_types::ConnectorSelection::Priority(list) => {
                                for choice in list {
                                    self.connector_choice(choice)?;
                                }
                            }
                        }

                        Ok(())
                    };

                check_connector_selection(&program.default_selection)?;

                for rule in &program.rules {
                    check_connector_selection(&rule.connector_selection)?;
                }
            }

            routing_types::StaticRoutingAlgorithm::ThreeDsDecisionRule(_) => {
                return Err(errors::ApiErrorResponse::InternalServerError).attach_printable(
                    "Invalid routing algorithm three_ds decision rule received",
                )?;
            }
        }

        Ok(())
    }
}

#[cfg(feature = "v1")]
pub async fn validate_connectors_in_routing_config(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    merchant_id: &id_type::MerchantId,
    profile_id: &id_type::ProfileId,
    routing_algorithm: &routing_types::StaticRoutingAlgorithm,
) -> RouterResult<()> {
    let all_mcas = state
        .store
        .find_merchant_connector_account_by_merchant_id_and_disabled_list(
            &state.into(),
            merchant_id,
            true,
            key_store,
        )
        .await
        .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
            id: merchant_id.get_string_repr().to_owned(),
        })?;
    let name_mca_id_set = all_mcas
        .iter()
        .filter(|mca| mca.profile_id == *profile_id)
        .map(|mca| (&mca.connector_name, mca.get_id()))
        .collect::<FxHashSet<_>>();

    let name_set = all_mcas
        .iter()
        .filter(|mca| mca.profile_id == *profile_id)
        .map(|mca| &mca.connector_name)
        .collect::<FxHashSet<_>>();

    let connector_choice = |choice: &routing_types::RoutableConnectorChoice| {
        if let Some(ref mca_id) = choice.merchant_connector_id {
            error_stack::ensure!(
                name_mca_id_set.contains(&(&choice.connector.to_string(), mca_id.clone())),
                errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "connector with name '{}' and merchant connector account id '{:?}' not found for the given profile",
                        choice.connector,
                        mca_id,
                    )
                }
            );
        } else {
            error_stack::ensure!(
                name_set.contains(&choice.connector.to_string()),
                errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "connector with name '{}' not found for the given profile",
                        choice.connector,
                    )
                }
            );
        }

        Ok(())
    };

    match routing_algorithm {
        routing_types::StaticRoutingAlgorithm::Single(choice) => {
            connector_choice(choice)?;
        }

        routing_types::StaticRoutingAlgorithm::Priority(list) => {
            for choice in list {
                connector_choice(choice)?;
            }
        }

        routing_types::StaticRoutingAlgorithm::VolumeSplit(splits) => {
            for split in splits {
                connector_choice(&split.connector)?;
            }
        }

        routing_types::StaticRoutingAlgorithm::Advanced(program) => {
            let check_connector_selection =
                |selection: &routing_types::ConnectorSelection| -> RouterResult<()> {
                    match selection {
                        routing_types::ConnectorSelection::VolumeSplit(splits) => {
                            for split in splits {
                                connector_choice(&split.connector)?;
                            }
                        }

                        routing_types::ConnectorSelection::Priority(list) => {
                            for choice in list {
                                connector_choice(choice)?;
                            }
                        }
                    }

                    Ok(())
                };

            check_connector_selection(&program.default_selection)?;

            for rule in &program.rules {
                check_connector_selection(&rule.connector_selection)?;
            }
        }

        routing_types::StaticRoutingAlgorithm::ThreeDsDecisionRule(_) => {
            Err(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Invalid routing algorithm three_ds decision rule received")?
        }
    }

    Ok(())
}

/// Provides the identifier for the specific merchant's routing_dictionary_key
#[inline(always)]
pub fn get_routing_dictionary_key(merchant_id: &str) -> String {
    format!("routing_dict_{merchant_id}")
}

/// Provides the identifier for the specific merchant's default_config
#[inline(always)]
pub fn get_default_config_key(
    merchant_id: &str,
    transaction_type: &storage::enums::TransactionType,
) -> String {
    match transaction_type {
        storage::enums::TransactionType::Payment => format!("routing_default_{merchant_id}"),
        #[cfg(feature = "payouts")]
        storage::enums::TransactionType::Payout => format!("routing_default_po_{merchant_id}"),
        storage::enums::TransactionType::ThreeDsAuthentication => {
            format!("three_ds_authentication_{merchant_id}")
        }
    }
}

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[async_trait::async_trait]
pub trait DynamicRoutingCache {
    async fn get_cached_dynamic_routing_config_for_profile(
        state: &SessionState,
        key: &str,
    ) -> Option<Arc<Self>>;

    async fn refresh_dynamic_routing_cache<T, F, Fut>(
        state: &SessionState,
        key: &str,
        func: F,
    ) -> RouterResult<T>
    where
        F: FnOnce() -> Fut + Send,
        T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone,
        Fut: futures::Future<Output = errors::CustomResult<T, errors::StorageError>> + Send;
}

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[async_trait::async_trait]
impl DynamicRoutingCache for routing_types::SuccessBasedRoutingConfig {
    async fn get_cached_dynamic_routing_config_for_profile(
        state: &SessionState,
        key: &str,
    ) -> Option<Arc<Self>> {
        cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE
            .get_val::<Arc<Self>>(cache::CacheKey {
                key: key.to_string(),
                prefix: state.tenant.redis_key_prefix.clone(),
            })
            .await
    }

    async fn refresh_dynamic_routing_cache<T, F, Fut>(
        state: &SessionState,
        key: &str,
        func: F,
    ) -> RouterResult<T>
    where
        F: FnOnce() -> Fut + Send,
        T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone,
        Fut: futures::Future<Output = errors::CustomResult<T, errors::StorageError>> + Send,
    {
        cache::get_or_populate_in_memory(
            state.store.get_cache_store().as_ref(),
            key,
            func,
            &cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("unable to populate SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE")
    }
}

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[async_trait::async_trait]
impl DynamicRoutingCache for routing_types::ContractBasedRoutingConfig {
    async fn get_cached_dynamic_routing_config_for_profile(
        state: &SessionState,
        key: &str,
    ) -> Option<Arc<Self>> {
        cache::CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE
            .get_val::<Arc<Self>>(cache::CacheKey {
                key: key.to_string(),
                prefix: state.tenant.redis_key_prefix.clone(),
            })
            .await
    }

    async fn refresh_dynamic_routing_cache<T, F, Fut>(
        state: &SessionState,
        key: &str,
        func: F,
    ) -> RouterResult<T>
    where
        F: FnOnce() -> Fut + Send,
        T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone,
        Fut: futures::Future<Output = errors::CustomResult<T, errors::StorageError>> + Send,
    {
        cache::get_or_populate_in_memory(
            state.store.get_cache_store().as_ref(),
            key,
            func,
            &cache::CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("unable to populate CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE")
    }
}

#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
#[async_trait::async_trait]
impl DynamicRoutingCache for routing_types::EliminationRoutingConfig {
    async fn get_cached_dynamic_routing_config_for_profile(
        state: &SessionState,
        key: &str,
    ) -> Option<Arc<Self>> {
        cache::ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE
            .get_val::<Arc<Self>>(cache::CacheKey {
                key: key.to_string(),
                prefix: state.tenant.redis_key_prefix.clone(),
            })
            .await
    }

    async fn refresh_dynamic_routing_cache<T, F, Fut>(
        state: &SessionState,
        key: &str,
        func: F,
    ) -> RouterResult<T>
    where
        F: FnOnce() -> Fut + Send,
        T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone,
        Fut: futures::Future<Output = errors::CustomResult<T, errors::StorageError>> + Send,
    {
        cache::get_or_populate_in_memory(
            state.store.get_cache_store().as_ref(),
            key,
            func,
            &cache::ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("unable to populate ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE")
    }
}

/// Cfetch dynamic routing configs
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
#[instrument(skip_all)]
pub async fn fetch_dynamic_routing_configs<T>(
    state: &SessionState,
    profile_id: &id_type::ProfileId,
    routing_id: id_type::RoutingId,
) -> RouterResult<T>
where
    T: serde::de::DeserializeOwned
        + Clone
        + DynamicRoutingCache
        + Cacheable
        + serde::Serialize
        + Debug,
{
    let key = format!(
        "{}_{}",
        profile_id.get_string_repr(),
        routing_id.get_string_repr()
    );

    if let Some(config) =
        T::get_cached_dynamic_routing_config_for_profile(state, key.as_str()).await
    {
        Ok(config.as_ref().clone())
    } else {
        let func = || async {
            let routing_algorithm = state
                .store
                .find_routing_algorithm_by_profile_id_algorithm_id(profile_id, &routing_id)
                .await
                .change_context(errors::StorageError::ValueNotFound(
                    "RoutingAlgorithm".to_string(),
                ))
                .attach_printable("unable to retrieve routing_algorithm for profile from db")?;

            let dynamic_routing_config = routing_algorithm
                .algorithm_data
                .parse_value::<T>("dynamic_routing_config")
                .change_context(errors::StorageError::DeserializationFailed)
                .attach_printable("unable to parse dynamic_routing_config")?;

            Ok(dynamic_routing_config)
        };

        let dynamic_routing_config =
            T::refresh_dynamic_routing_cache(state, key.as_str(), func).await?;

        Ok(dynamic_routing_config)
    }
}

#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
#[instrument(skip_all)]
pub async fn update_gateway_score_helper_with_open_router(
    state: &SessionState,
    payment_attempt: &storage::PaymentAttempt,
    profile_id: &id_type::ProfileId,
    dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef,
) -> RouterResult<()> {
    let is_success_rate_routing_enabled =
        dynamic_routing_algo_ref.is_success_rate_routing_enabled();
    let is_elimination_enabled = dynamic_routing_algo_ref.is_elimination_enabled();

    if is_success_rate_routing_enabled || is_elimination_enabled {
        let payment_connector = &payment_attempt.connector.clone().ok_or(
            errors::ApiErrorResponse::GenericNotFoundError {
                message: "unable to derive payment connector from payment attempt".to_string(),
            },
        )?;

        let routable_connector = routing_types::RoutableConnectorChoice {
            choice_kind: api_models::routing::RoutableChoiceKind::FullStruct,
            connector: common_enums::RoutableConnectors::from_str(payment_connector.as_str())
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("unable to infer routable_connector from connector")?,
            merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
        };

        logger::debug!(
            "performing update-gateway-score for gateway with id {} in open_router for profile: {}",
            routable_connector,
            profile_id.get_string_repr()
        );
        routing::payments_routing::update_gateway_score_with_open_router(
            state,
            routable_connector.clone(),
            profile_id,
            &payment_attempt.merchant_id,
            &payment_attempt.payment_id,
            payment_attempt.status,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("failed to update gateway score in open_router service")?;
    }

    Ok(())
}

/// metrics for success based dynamic routing
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
#[instrument(skip_all)]
pub async fn push_metrics_with_update_window_for_success_based_routing(
    state: &SessionState,
    payment_attempt: &storage::PaymentAttempt,
    routable_connectors: Vec<routing_types::RoutableConnectorChoice>,
    profile_id: &id_type::ProfileId,
    dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef,
    dynamic_routing_config_params_interpolator: DynamicRoutingConfigParamsInterpolator,
) -> RouterResult<()> {
    if let Some(success_based_algo_ref) = dynamic_routing_algo_ref.success_based_algorithm {
        if success_based_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None {
            let client = &state
                .grpc_client
                .dynamic_routing
                .as_ref()
                .ok_or(errors::ApiErrorResponse::GenericNotFoundError {
                    message: "dynamic routing gRPC client not found".to_string(),
                })?
                .success_rate_client;

            let payment_connector = &payment_attempt.connector.clone().ok_or(
                errors::ApiErrorResponse::GenericNotFoundError {
                    message: "unable to derive payment connector from payment attempt".to_string(),
                },
            )?;

            let routable_connector = routing_types::RoutableConnectorChoice {
                choice_kind: api_models::routing::RoutableChoiceKind::FullStruct,
                connector: common_enums::RoutableConnectors::from_str(payment_connector.as_str())
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("unable to infer routable_connector from connector")?,
                merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
            };

            let payment_status_attribute =
                get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status);

            let success_based_routing_configs = fetch_dynamic_routing_configs::<
                routing_types::SuccessBasedRoutingConfig,
            >(
                state,
                profile_id,
                success_based_algo_ref
                    .algorithm_id_with_timestamp
                    .algorithm_id
                    .ok_or(errors::ApiErrorResponse::GenericNotFoundError {
                        message: "success_rate algorithm_id not found".to_string(),
                    })
                    .attach_printable(
                        "success_based_routing_algorithm_id not found in business_profile",
                    )?,
            )
            .await
            .change_context(errors::ApiErrorResponse::GenericNotFoundError {
                message: "success_rate based dynamic routing configs not found".to_string(),
            })
            .attach_printable("unable to retrieve success_rate based dynamic routing configs")?;

            let success_based_routing_config_params = dynamic_routing_config_params_interpolator
                .get_string_val(
                    success_based_routing_configs
                        .params
                        .as_ref()
                        .ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError)
                        .change_context(errors::ApiErrorResponse::InternalServerError)?,
                );

            let success_based_connectors = client
                .calculate_entity_and_global_success_rate(
                    profile_id.get_string_repr().into(),
                    success_based_routing_configs.clone(),
                    success_based_routing_config_params.clone(),
                    routable_connectors.clone(),
                    state.get_grpc_headers(),
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "unable to calculate/fetch success rate from dynamic routing service",
                )?;

            let first_merchant_success_based_connector = &success_based_connectors
                .entity_scores_with_labels
                .first()
                .ok_or(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "unable to fetch the first connector from list of connectors obtained from dynamic routing service",
                )?;

            let (first_merchant_success_based_connector_label, _) = first_merchant_success_based_connector.label
                .split_once(':')
                .ok_or(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(format!(
                    "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service",
                    first_merchant_success_based_connector.label
                ))?;

            let first_global_success_based_connector = &success_based_connectors
                .global_scores_with_labels
                .first()
                .ok_or(errors::ApiErrorResponse::InternalServerError)
                .attach_printable(
                    "unable to fetch the first global connector from list of connectors obtained from dynamic routing service",
                )?;

            let outcome = get_dynamic_routing_based_metrics_outcome_for_payment(
                payment_status_attribute,
                payment_connector.to_string(),
                first_merchant_success_based_connector_label.to_string(),
            );

            core_metrics::DYNAMIC_SUCCESS_BASED_ROUTING.add(
                1,
                router_env::metric_attributes!(
                    (
                        "tenant",
                        state.tenant.tenant_id.get_string_repr().to_owned(),
                    ),
                    (
                        "merchant_profile_id",
                        format!(
                            "{}:{}",
                            payment_attempt.merchant_id.get_string_repr(),
                            payment_attempt.profile_id.get_string_repr()
                        ),
                    ),
                    (
                        "merchant_specific_success_based_routing_connector",
                        first_merchant_success_based_connector_label.to_string(),
                    ),
                    (
                        "merchant_specific_success_based_routing_connector_score",
                        first_merchant_success_based_connector.score.to_string(),
                    ),
                    (
                        "global_success_based_routing_connector",
                        first_global_success_based_connector.label.to_string(),
                    ),
                    (
                        "global_success_based_routing_connector_score",
                        first_global_success_based_connector.score.to_string(),
                    ),
                    ("payment_connector", payment_connector.to_string()),
                    (
                        "currency",
                        payment_attempt
                            .currency
                            .map_or_else(|| "None".to_string(), |currency| currency.to_string()),
                    ),
                    (
                        "payment_method",
                        payment_attempt.payment_method.map_or_else(
                            || "None".to_string(),
                            |payment_method| payment_method.to_string(),
                        ),
                    ),
                    (
                        "payment_method_type",
                        payment_attempt.payment_method_type.map_or_else(
                            || "None".to_string(),
                            |payment_method_type| payment_method_type.to_string(),
                        ),
                    ),
                    (
                        "capture_method",
                        payment_attempt.capture_method.map_or_else(
                            || "None".to_string(),
                            |capture_method| capture_method.to_string(),
                        ),
                    ),
                    (
                        "authentication_type",
                        payment_attempt.authentication_type.map_or_else(
                            || "None".to_string(),
                            |authentication_type| authentication_type.to_string(),
                        ),
                    ),
                    ("payment_status", payment_attempt.status.to_string()),
                    ("conclusive_classification", outcome.to_string()),
                ),
            );
            logger::debug!("successfully pushed success_based_routing metrics");

            let duplicate_stats = state
                .store
                .find_dynamic_routing_stats_optional_by_attempt_id_merchant_id(
                    payment_attempt.attempt_id.clone(),
                    &payment_attempt.merchant_id.to_owned(),
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to fetch dynamic_routing_stats entry")?;

            if duplicate_stats.is_some() {
                let dynamic_routing_update = DynamicRoutingStatsUpdate {
                    amount: payment_attempt.get_total_amount(),
                    success_based_routing_connector: first_merchant_success_based_connector_label
                        .to_string(),
                    payment_connector: payment_connector.to_string(),
                    payment_method_type: payment_attempt.payment_method_type,
                    currency: payment_attempt.currency,
                    payment_method: payment_attempt.payment_method,
                    capture_method: payment_attempt.capture_method,
                    authentication_type: payment_attempt.authentication_type,
                    payment_status: payment_attempt.status,
                    conclusive_classification: outcome,
                    global_success_based_connector: Some(
                        first_global_success_based_connector.label.to_string(),
                    ),
                };

                state
                    .store
                    .update_dynamic_routing_stats(
                        payment_attempt.attempt_id.clone(),
                        &payment_attempt.merchant_id.to_owned(),
                        dynamic_routing_update,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Unable to update dynamic routing stats to db")?;
            } else {
                let dynamic_routing_stats = DynamicRoutingStatsNew {
                    payment_id: payment_attempt.payment_id.to_owned(),
                    attempt_id: payment_attempt.attempt_id.clone(),
                    merchant_id: payment_attempt.merchant_id.to_owned(),
                    profile_id: payment_attempt.profile_id.to_owned(),
                    amount: payment_attempt.get_total_amount(),
                    success_based_routing_connector: first_merchant_success_based_connector_label
                        .to_string(),
                    payment_connector: payment_connector.to_string(),
                    payment_method_type: payment_attempt.payment_method_type,


// Struct: Chat
// File: crates/router/src/routes/app.rs
// Module: router
// Implementations: 1

pub struct Chat


// Struct: PayboxAuthType
// File: crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PayboxAuthType


// Implementation: impl MerchantConnectorAccountNew
// File: crates/diesel_models/src/query/merchant_connector_account.rs
// Module: diesel_models
// Methods: 1 total (0 public)

impl MerchantConnectorAccountNew


// Implementation: impl api::PaymentVoid for for Bamboraapac
// File: crates/hyperswitch_connectors/src/connectors/bamboraapac.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentVoid for for Bamboraapac


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

impl api::PaymentAuthorize for for Digitalvirgo


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

impl ConnectorValidation for for Santander


// Implementation: impl api::ConnectorMandateRevoke for for Cybersource
// File: crates/hyperswitch_connectors/src/connectors/cybersource.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::ConnectorMandateRevoke for for Cybersource


// Implementation: impl SecretStateContainer
// File: crates/hyperswitch_interfaces/src/secrets_interface/secret_state.rs
// Module: hyperswitch_interfaces
// Methods: 2 total (2 public)

impl SecretStateContainer


// Function: build
// File: crates/router/src/core/payment_methods/tokenize/payment_method_executor.rs
// Module: router

pub fn build(self) -> api::CardNetworkTokenizeResponse


// Struct: EcommerceCardPaymentOnlyConfirmationData
// File: crates/hyperswitch_connectors/src/connectors/peachpayments/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct EcommerceCardPaymentOnlyConfirmationData


// Function: tokenize_card_api
// File: crates/openapi/src/routes/payment_method.rs
// Module: openapi

pub fn tokenize_card_api()


// Implementation: impl crate::events::ApiEventMetric for for GlobalCustomerId
// File: crates/common_utils/src/id_type/global_id/customer.rs
// Module: common_utils
// Methods: 1 total (0 public)

impl crate::events::ApiEventMetric for for GlobalCustomerId


// Implementation: impl api::PaymentAuthorize for for Helcim
// File: crates/hyperswitch_connectors/src/connectors/helcim.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentAuthorize for for Helcim


// Function: routing_unlink_config
// File: crates/router/src/routes/routing.rs
// Module: router

pub fn routing_unlink_config(
    state: web::Data<AppState>,
    req: HttpRequest,
    path: web::Path<common_utils::id_type::ProfileId>,
    transaction_type: &enums::TransactionType,
) -> impl Responder


// Function: find_by_lineage
// File: crates/diesel_models/src/query/user/theme.rs
// Module: diesel_models

pub fn find_by_lineage(
        conn: &PgPooledConn,
        lineage: ThemeLineage,
    ) -> StorageResult<Self>


// Implementation: impl api::PaymentCapture for for Aci
// File: crates/hyperswitch_connectors/src/connectors/aci.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentCapture for for Aci


// Implementation: impl DBOperation
// File: crates/diesel_models/src/kv.rs
// Module: diesel_models
// Methods: 2 total (2 public)

impl DBOperation


// Struct: NovalnetPaymentsResponse
// File: crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct NovalnetPaymentsResponse


// Struct: CustomerId
// File: crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct CustomerId


// Struct: PaymentMethodMigrateResponse
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Implementations: 0

pub struct PaymentMethodMigrateResponse


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

impl webhooks::IncomingWebhook for for Fiserv


// Struct: KlarnaSessionResponse
// File: crates/hyperswitch_connectors/src/connectors/klarna/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct KlarnaSessionResponse


// Struct: ActivePaymentsMetricsAccumulator
// File: crates/analytics/src/active_payments/accumulator.rs
// Module: analytics
// Implementations: 1

pub struct ActivePaymentsMetricsAccumulator


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

pub struct TrustpayRefundRequestBankRedirect


// Function: new
// File: crates/hyperswitch_connectors/src/connectors/bankofamerica.rs
// Module: hyperswitch_connectors

pub fn new() -> &'static Self


// File: crates/test_utils/tests/connectors/checkout_wh_ui.rs
// Module: test_utils

use thirtyfour::{prelude::*, WebDriver};

use crate::{selenium::*, tester};

struct CheckoutSeleniumTest;

impl SeleniumTest for CheckoutSeleniumTest {
    fn get_connector_name(&self) -> String {
        "checkout".to_string()
    }
}

async fn should_make_webhook(web_driver: WebDriver) -> Result<(), WebDriverError> {
    let conn = CheckoutSeleniumTest {};
    conn.make_webhook_test(
        web_driver,
        &format!("{CHECKOUT_BASE_URL}/saved/18"),
        vec![
            Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))),
            Event::Trigger(Trigger::Sleep(8)),
            Event::Assert(Assert::IsPresent("Google")),
            Event::Assert(Assert::ContainsAny(
                Selector::QueryParamStr,
                vec!["status=succeeded", "status=processing"],
            )),
        ],
        10,
        "succeeded",
    )
    .await?;
    Ok(())
}

#[test]
fn should_make_webhook_test() {
    tester!(should_make_webhook);
}


// Struct: TransactionDetails
// File: crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct TransactionDetails


// File: crates/hyperswitch_domain_models/src/transformers.rs
// Module: hyperswitch_domain_models

pub trait ForeignFrom<F> {
    fn foreign_from(from: F) -> Self;
}

pub trait ForeignTryFrom<F>: Sized {
    type Error;

    fn foreign_try_from(from: F) -> Result<Self, Self::Error>;
}


// Implementation: impl PaymentCapture for for Signifyd
// File: crates/hyperswitch_connectors/src/connectors/signifyd.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl PaymentCapture for for Signifyd


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

pub fn add_token_for_payment_method(
    router_data: &mut types::RouterData<
        api::PaymentMethodToken,
        types::PaymentMethodTokenizationData,
        types::PaymentsResponseData,
    >,
    payment_method_data_request: types::PaymentMethodTokenizationData,
    state: SessionState,
    merchant_connector_account_details: &hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccount,
) -> RouterResult<types::PspTokenResult>