// File: crates/router/src/core/payments/conditional_configs.rs

use api_models::{conditional_configs::DecisionManagerRecord, routing};
use common_utils::ext_traits::StringExt;
use error_stack::ResultExt;
use euclid::backend::{self, inputs as dsl_inputs, EuclidBackend};
use router_env::{instrument, tracing};
use storage_impl::redis::cache::{self, DECISION_MANAGER_CACHE};

use super::routing::make_dsl_input;
#[cfg(feature = "v2")]
use crate::{core::errors::RouterResult, types::domain};
use crate::{
    core::{errors, errors::ConditionalConfigError as ConfigError, routing as core_routing},
    routes,
};
pub type ConditionalConfigResult<O> = errors::CustomResult<O, ConfigError>;

#[instrument(skip_all)]
#[cfg(feature = "v1")]
pub async fn perform_decision_management(
    state: &routes::SessionState,
    algorithm_ref: routing::RoutingAlgorithmRef,
    merchant_id: &common_utils::id_type::MerchantId,
    payment_data: &core_routing::PaymentsDslInput<'_>,
) -> ConditionalConfigResult<common_types::payments::ConditionalConfigs> {
    let algorithm_id = if let Some(id) = algorithm_ref.config_algo_id {
        id
    } else {
        return Ok(common_types::payments::ConditionalConfigs::default());
    };
    let db = &*state.store;

    let key = merchant_id.get_dsl_config();

    let find_key_from_db = || async {
        let config = db.find_config_by_key(&algorithm_id).await?;

        let rec: DecisionManagerRecord = config
            .config
            .parse_struct("Program")
            .change_context(errors::StorageError::DeserializationFailed)
            .attach_printable("Error parsing routing algorithm from configs")?;

        backend::VirInterpreterBackend::with_program(rec.program)
            .change_context(errors::StorageError::ValueNotFound("Program".to_string()))
            .attach_printable("Error initializing DSL interpreter backend")
    };

    let interpreter = cache::get_or_populate_in_memory(
        db.get_cache_store().as_ref(),
        &key,
        find_key_from_db,
        &DECISION_MANAGER_CACHE,
    )
    .await
    .change_context(ConfigError::DslCachePoisoned)?;

    let backend_input =
        make_dsl_input(payment_data).change_context(ConfigError::InputConstructionError)?;

    execute_dsl_and_get_conditional_config(backend_input, &interpreter)
}

#[cfg(feature = "v2")]
pub fn perform_decision_management(
    record: common_types::payments::DecisionManagerRecord,
    payment_data: &core_routing::PaymentsDslInput<'_>,
) -> RouterResult<common_types::payments::ConditionalConfigs> {
    let interpreter = backend::VirInterpreterBackend::with_program(record.program)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error initializing DSL interpreter backend")?;

    let backend_input = make_dsl_input(payment_data)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error constructing DSL input")?;
    execute_dsl_and_get_conditional_config(backend_input, &interpreter)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error executing DSL")
}

pub fn execute_dsl_and_get_conditional_config(
    backend_input: dsl_inputs::BackendInput,
    interpreter: &backend::VirInterpreterBackend<common_types::payments::ConditionalConfigs>,
) -> ConditionalConfigResult<common_types::payments::ConditionalConfigs> {
    let routing_output = interpreter
        .execute(backend_input)
        .map(|out| out.connector_selection)
        .change_context(ConfigError::DslExecutionError)?;
    Ok(routing_output)
}


// File: crates/router/src/core/payments/vault_session.rs

use std::{fmt::Debug, str::FromStr};

pub use common_enums::enums::CallConnectorAction;
use common_utils::id_type;
use error_stack::{report, ResultExt};
pub use hyperswitch_domain_models::{
    mandates::MandateData,
    payment_address::PaymentAddress,
    payments::{HeaderPayload, PaymentIntentData},
    router_data::{PaymentMethodToken, RouterData},
    router_data_v2::{flow_common_types::VaultConnectorFlowData, RouterDataV2},
    router_flow_types::ExternalVaultCreateFlow,
    router_request_types::CustomerDetails,
    types::{VaultRouterData, VaultRouterDataV2},
};
use hyperswitch_interfaces::{
    api::Connector as ConnectorTrait,
    connector_integration_v2::{ConnectorIntegrationV2, ConnectorV2},
};
use masking::ExposeInterface;
use router_env::{env::Env, instrument, tracing};

use crate::{
    core::{
        errors::{self, utils::StorageErrorExt, RouterResult},
        payments::{
            self as payments_core, call_multiple_connectors_service, customers,
            flows::{ConstructFlowSpecificData, Feature},
            helpers, helpers as payment_helpers, operations,
            operations::{BoxedOperation, Operation},
            transformers, OperationSessionGetters, OperationSessionSetters,
        },
        utils as core_utils,
    },
    db::errors::ConnectorErrorExt,
    errors::RouterResponse,
    routes::{app::ReqState, SessionState},
    services::{self, connector_integration_interface::RouterDataConversion},
    types::{
        self as router_types,
        api::{self, enums as api_enums, ConnectorCommon},
        domain, storage,
    },
    utils::{OptionExt, ValueExt},
};

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
pub async fn populate_vault_session_details<F, RouterDReq, ApiRequest, D>(
    state: &SessionState,
    req_state: ReqState,
    customer: &Option<domain::Customer>,
    platform: &domain::Platform,
    operation: &BoxedOperation<'_, F, ApiRequest, D>,
    profile: &domain::Profile,
    payment_data: &mut D,
    header_payload: HeaderPayload,
) -> RouterResult<()>
where
    F: Send + Clone + Sync,
    RouterDReq: Send + Sync,

    // To create connector flow specific interface data
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
    D: ConstructFlowSpecificData<F, RouterDReq, router_types::PaymentsResponseData>,
    RouterData<F, RouterDReq, router_types::PaymentsResponseData>: Feature<F, RouterDReq> + Send,
    // To construct connector flow specific api
    dyn api::Connector:
        services::api::ConnectorIntegration<F, RouterDReq, router_types::PaymentsResponseData>,
{
    let is_external_vault_sdk_enabled = profile.is_vault_sdk_enabled();

    if is_external_vault_sdk_enabled {
        let external_vault_source = profile
            .external_vault_connector_details
            .as_ref()
            .map(|details| &details.vault_connector_id);

        let merchant_connector_account =
            domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new(
                helpers::get_merchant_connector_account_v2(
                    state,
                    platform.get_processor().get_key_store(),
                    external_vault_source,
                )
                .await?,
            ));

        let updated_customer = call_create_connector_customer_if_required(
            state,
            customer,
            platform,
            &merchant_connector_account,
            payment_data,
        )
        .await?;

        if let Some((customer, updated_customer)) = customer.clone().zip(updated_customer) {
            let db = &*state.store;
            let customer_id = customer.get_id().clone();
            let customer_merchant_id = customer.merchant_id.clone();

            let _updated_customer = db
                .update_customer_by_global_id(
                    &customer_id,
                    customer,
                    updated_customer,
                    platform.get_processor().get_key_store(),
                    platform.get_processor().get_account().storage_scheme,
                )
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to update customer during Vault session")?;
        };

        let vault_session_details = generate_vault_session_details(
            state,
            platform,
            &merchant_connector_account,
            payment_data.get_connector_customer_id(),
        )
        .await?;

        payment_data.set_vault_session_details(vault_session_details);
    }
    Ok(())
}

#[cfg(feature = "v2")]
pub async fn call_create_connector_customer_if_required<F, Req, D>(
    state: &SessionState,
    customer: &Option<domain::Customer>,
    platform: &domain::Platform,
    merchant_connector_account_type: &domain::MerchantConnectorAccountTypeDetails,
    payment_data: &mut D,
) -> RouterResult<Option<storage::CustomerUpdate>>
where
    F: Send + Clone + Sync,
    Req: Send + Sync,

    // To create connector flow specific interface data
    D: OperationSessionGetters<F> + OperationSessionSetters<F> + Send + Sync + Clone,
    D: ConstructFlowSpecificData<F, Req, router_types::PaymentsResponseData>,
    RouterData<F, Req, router_types::PaymentsResponseData>: Feature<F, Req> + Send,

    // To construct connector flow specific api
    dyn api::Connector:
        services::api::ConnectorIntegration<F, Req, router_types::PaymentsResponseData>,
{
    let db_merchant_connector_account =
        merchant_connector_account_type.get_inner_db_merchant_connector_account();

    match db_merchant_connector_account {
        Some(merchant_connector_account) => {
            let connector_name = merchant_connector_account.get_connector_name_as_string();
            let merchant_connector_id = merchant_connector_account.get_id();

            let connector = api::ConnectorData::get_connector_by_name(
                &state.conf.connectors,
                &connector_name,
                api::GetToken::Connector,
                Some(merchant_connector_id.clone()),
            )?;

            let (should_call_connector, existing_connector_customer_id) =
                customers::should_call_connector_create_customer(
                    &connector,
                    customer,
                    payment_data.get_payment_attempt(),
                    merchant_connector_account_type,
                );

            if should_call_connector {
                // Create customer at connector and update the customer table to store this data
                let router_data = payment_data
                    .construct_router_data(
                        state,
                        connector.connector.id(),
                        platform,
                        customer,
                        merchant_connector_account_type,
                        None,
                        None,
                    )
                    .await?;

                let connector_customer_id = router_data
                    .create_connector_customer(state, &connector)
                    .await?;

                let customer_update = customers::update_connector_customer_in_customers(
                    merchant_connector_account_type,
                    customer.as_ref(),
                    connector_customer_id.clone(),
                )
                .await;

                payment_data.set_connector_customer_id(connector_customer_id);
                Ok(customer_update)
            } else {
                // Customer already created in previous calls use the same value, no need to update
                payment_data.set_connector_customer_id(
                    existing_connector_customer_id.map(ToOwned::to_owned),
                );
                Ok(None)
            }
        }
        None => {
            router_env::logger::error!(
                "Merchant connector account is missing, cannot create customer for vault session"
            );
            Err(errors::ApiErrorResponse::InternalServerError.into())
        }
    }
}

#[cfg(feature = "v2")]
pub async fn generate_vault_session_details(
    state: &SessionState,
    platform: &domain::Platform,
    merchant_connector_account_type: &domain::MerchantConnectorAccountTypeDetails,
    connector_customer_id: Option<String>,
) -> RouterResult<Option<api::VaultSessionDetails>> {
    let connector_name = merchant_connector_account_type
        .get_connector_name()
        .map(|name| name.to_string())
        .ok_or(errors::ApiErrorResponse::InternalServerError)?; // should not panic since we should always have a connector name
    let connector = api_enums::VaultConnectors::from_str(&connector_name)
        .change_context(errors::ApiErrorResponse::InternalServerError)?;
    let connector_auth_type: router_types::ConnectorAuthType = merchant_connector_account_type
        .get_connector_account_details()
        .map_err(|err| {
            err.change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to parse connector auth type")
        })?;

    match (connector, connector_auth_type) {
        // create session for vgs vault
        (
            api_enums::VaultConnectors::Vgs,
            router_types::ConnectorAuthType::SignatureKey { api_secret, .. },
        ) => {
            let sdk_env = match state.conf.env {
                Env::Sandbox | Env::Development | Env::Integ => "sandbox",
                Env::Production => "live",
            }
            .to_string();
            Ok(Some(api::VaultSessionDetails::Vgs(
                api::VgsSessionDetails {
                    external_vault_id: api_secret,
                    sdk_env,
                },
            )))
        }
        // create session for hyperswitch vault
        (
            api_enums::VaultConnectors::HyperswitchVault,
            router_types::ConnectorAuthType::SignatureKey {
                key1, api_secret, ..
            },
        ) => {
            generate_hyperswitch_vault_session_details(
                state,
                platform,
                merchant_connector_account_type,
                connector_customer_id,
                connector_name,
                key1,
                api_secret,
            )
            .await
        }
        _ => {
            router_env::logger::warn!(
                "External vault session creation is not supported for connector: {}",
                connector_name
            );
            Ok(None)
        }
    }
}

async fn generate_hyperswitch_vault_session_details(
    state: &SessionState,
    platform: &domain::Platform,
    merchant_connector_account_type: &domain::MerchantConnectorAccountTypeDetails,
    connector_customer_id: Option<String>,
    connector_name: String,
    vault_publishable_key: masking::Secret<String>,
    vault_profile_id: masking::Secret<String>,
) -> RouterResult<Option<api::VaultSessionDetails>> {
    let connector_response = call_external_vault_create(
        state,
        platform,
        connector_name,
        merchant_connector_account_type,
        connector_customer_id,
    )
    .await?;

    match connector_response.response {
        Ok(router_types::VaultResponseData::ExternalVaultCreateResponse {
            session_id,
            client_secret,
        }) => Ok(Some(api::VaultSessionDetails::HyperswitchVault(
            api::HyperswitchVaultSessionDetails {
                payment_method_session_id: session_id,
                client_secret,
                publishable_key: vault_publishable_key,
                profile_id: vault_profile_id,
            },
        ))),
        Ok(_) => {
            router_env::logger::warn!("Unexpected response from external vault create API");
            Err(errors::ApiErrorResponse::InternalServerError.into())
        }
        Err(err) => {
            router_env::logger::error!(error_response_from_external_vault_create=?err);
            Err(errors::ApiErrorResponse::InternalServerError.into())
        }
    }
}

#[cfg(feature = "v2")]
async fn call_external_vault_create(
    state: &SessionState,
    platform: &domain::Platform,
    connector_name: String,
    merchant_connector_account_type: &domain::MerchantConnectorAccountTypeDetails,
    connector_customer_id: Option<String>,
) -> RouterResult<VaultRouterData<ExternalVaultCreateFlow>>
where
    dyn ConnectorTrait + Sync: services::api::ConnectorIntegration<
        ExternalVaultCreateFlow,
        router_types::VaultRequestData,
        router_types::VaultResponseData,
    >,
    dyn ConnectorV2 + Sync: ConnectorIntegrationV2<
        ExternalVaultCreateFlow,
        VaultConnectorFlowData,
        router_types::VaultRequestData,
        router_types::VaultResponseData,
    >,
{
    let connector_data: api::ConnectorData = api::ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        &connector_name,
        api::GetToken::Connector,
        merchant_connector_account_type.get_mca_id(),
    )?;
    let merchant_connector_account = match &merchant_connector_account_type {
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(mca) => {
            Ok(mca.as_ref())
        }
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => {
            Err(report!(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("MerchantConnectorDetails not supported for vault operations"))
        }
    }?;

    let mut router_data = core_utils::construct_vault_router_data(
        state,
        platform.get_processor().get_account().get_id(),
        merchant_connector_account,
        None,
        None,
        connector_customer_id,
        None,
    )
    .await?;

    let mut old_router_data = VaultConnectorFlowData::to_old_router_data(router_data)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable(
            "Cannot construct router data for making the external vault create api call",
        )?;

    let connector_integration: services::BoxedVaultConnectorIntegrationInterface<
        ExternalVaultCreateFlow,
        router_types::VaultRequestData,
        router_types::VaultResponseData,
    > = connector_data.connector.get_connector_integration();
    services::execute_connector_processing_step(
        state,
        connector_integration,
        &old_router_data,
        CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_vault_failed_response()
}


// File: crates/router/src/core/payments/gateway.rs

pub mod access_token_gateway;
pub mod authorize_gateway;
pub mod context;
pub mod create_customer_gateway;
pub mod create_order_gateway;
pub mod payment_method_token_create_gateway;
pub mod psync_gateway;
pub mod session_token_gateway;
use std::sync;

use hyperswitch_domain_models::router_flow_types::payments;

pub static GRANULAR_GATEWAY_SUPPORTED_FLOWS: sync::LazyLock<Vec<&'static str>> =
    sync::LazyLock::new(|| vec![std::any::type_name::<payments::PSync>()]);


// File: crates/router/src/core/payments/access_token.rs

use std::fmt::Debug;

use common_utils::ext_traits::AsyncExt;
use error_stack::ResultExt;
use hyperswitch_interfaces::api::{ConnectorAccessTokenSuffix, ConnectorSpecifications};

use crate::{
    consts,
    core::{
        errors::{self, RouterResult},
        payments,
    },
    routes::{metrics, SessionState},
    services::{self, logger},
    types::{self, api as api_types, domain},
};

/// Get cached access token for UCS flows - only reads from cache, never generates
pub async fn get_cached_access_token_for_ucs(
    state: &SessionState,
    connector: &api_types::ConnectorData,
    platform: &domain::Platform,
    payment_method: common_enums::PaymentMethod,
    creds_identifier: Option<&str>,
) -> RouterResult<Option<types::AccessToken>> {
    if connector
        .connector_name
        .supports_access_token(payment_method)
    {
        let merchant_id = platform.get_processor().get_account().get_id();
        let store = &*state.store;

        let merchant_connector_id_or_connector_name = connector
            .merchant_connector_id
            .clone()
            .map(|mca_id| mca_id.get_string_repr().to_string())
            .or(creds_identifier.map(|id| id.to_string()))
            .unwrap_or(connector.connector_name.to_string());

        let key = common_utils::access_token::get_default_access_token_key(
            merchant_id,
            merchant_connector_id_or_connector_name,
        );

        let cached_access_token = store
            .get_access_token(key)
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("DB error when accessing the access token")?;

        if let Some(access_token) = cached_access_token {
            router_env::logger::info!(
                "Cached access token found for UCS flow - merchant_id: {:?}, connector: {} with expiry of: {} seconds",
                platform.get_processor().get_account().get_id(),
                connector.connector_name,
                access_token.expires
            );
            metrics::ACCESS_TOKEN_CACHE_HIT.add(
                1,
                router_env::metric_attributes!(("connector", connector.connector_name.to_string())),
            );
            Ok(Some(access_token))
        } else {
            router_env::logger::info!(
                "No cached access token found for UCS flow - UCS will generate internally - merchant_id: {:?}, connector: {}",
                platform.get_processor().get_account().get_id(),
                connector.connector_name
            );
            metrics::ACCESS_TOKEN_CACHE_MISS.add(
                1,
                router_env::metric_attributes!(("connector", connector.connector_name.to_string())),
            );
            Ok(None)
        }
    } else {
        Ok(None)
    }
}

/// After we get the access token, check if there was an error and if the flow should proceed further
/// Returns bool
/// true - Everything is well, continue with the flow
/// false - There was an error, cannot proceed further
pub fn update_router_data_with_access_token_result<F, Req, Res>(
    add_access_token_result: &types::AddAccessTokenResult,
    router_data: &mut types::RouterData<F, Req, Res>,
    call_connector_action: &payments::CallConnectorAction,
) -> bool {
    // Update router data with access token or error only if it will be calling connector
    let should_update_router_data = matches!(
        (
            add_access_token_result.connector_supports_access_token,
            call_connector_action
        ),
        (true, payments::CallConnectorAction::Trigger)
    );

    if should_update_router_data {
        match add_access_token_result.access_token_result.as_ref() {
            Ok(access_token) => {
                router_data.access_token.clone_from(access_token);
                true
            }
            Err(connector_error) => {
                router_data.response = Err(connector_error.clone());
                false
            }
        }
    } else {
        true
    }
}

pub async fn add_access_token<
    F: Clone + 'static,
    Req: Debug + Clone + 'static,
    Res: Debug + Clone + 'static,
>(
    state: &SessionState,
    connector: &api_types::ConnectorData,
    router_data: &types::RouterData<F, Req, Res>,
    creds_identifier: Option<&str>,
) -> RouterResult<types::AddAccessTokenResult> {
    if connector
        .connector_name
        .supports_access_token(router_data.payment_method)
    {
        let merchant_id = &router_data.merchant_id;
        let store = &*state.store;

        // `merchant_connector_id` may not be present in the below cases
        // - when straight through routing is used without passing the `merchant_connector_id`
        // - when creds identifier is passed
        //
        // In these cases fallback to `connector_name`.
        // We cannot use multiple merchant connector account in these cases
        let merchant_connector_id_or_connector_name = connector
            .merchant_connector_id
            .clone()
            .map(|mca_id| mca_id.get_string_repr().to_string())
            .or(creds_identifier.map(|id| id.to_string()))
            .unwrap_or(connector.connector_name.to_string());

        let key = connector
            .connector
            .get_access_token_key(router_data, merchant_connector_id_or_connector_name.clone())
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable(format!(
                "Failed to get access token key for connector: {:?}",
                connector.connector_name
            ))?;

        let old_access_token = store
            .get_access_token(key.clone())
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("DB error when accessing the access token")?;

        let res = match old_access_token {
            Some(access_token) => {
                router_env::logger::debug!(
                    "Access token found in redis for merchant_id: {:?}, payment_id: {:?}, connector: {} which has expiry of: {} seconds",
                    merchant_id,
                    router_data.payment_id,
                    connector.connector_name,
                    access_token.expires
                );
                metrics::ACCESS_TOKEN_CACHE_HIT.add(
                    1,
                    router_env::metric_attributes!((
                        "connector",
                        connector.connector_name.to_string()
                    )),
                );
                Ok(Some(access_token))
            }
            None => {
                metrics::ACCESS_TOKEN_CACHE_MISS.add(
                    1,
                    router_env::metric_attributes!((
                        "connector",
                        connector.connector_name.to_string()
                    )),
                );

                let authentication_token =
                    execute_authentication_token(state, connector, router_data).await?;

                let cloned_router_data = router_data.clone();

                let refresh_token_request_data = types::AccessTokenRequestData::try_from((
                    router_data.connector_auth_type.clone(),
                    authentication_token,
                ))
                .attach_printable(
                    "Could not create access token request, invalid connector account credentials",
                )?;

                let refresh_token_response_data: Result<types::AccessToken, types::ErrorResponse> =
                    Err(types::ErrorResponse::default());
                let refresh_token_router_data = payments::helpers::router_data_type_conversion::<
                    _,
                    api_types::AccessTokenAuth,
                    _,
                    _,
                    _,
                    _,
                >(
                    cloned_router_data,
                    refresh_token_request_data,
                    refresh_token_response_data,
                );
                refresh_connector_auth(state, connector, &refresh_token_router_data)
                    .await?
                    .async_map(|access_token| async move {
                        let store = &*state.store;

                        // The expiry should be adjusted for network delays from the connector
                        // The access token might not have been expired when request is sent
                        // But once it reaches the connector, it might expire because of the network delay
                        // Subtract few seconds from the expiry in order to account for these network delays
                        // This will reduce the expiry time by `REDUCE_ACCESS_TOKEN_EXPIRY_TIME` seconds
                        let modified_access_token_with_expiry = types::AccessToken {
                            expires: access_token
                                .expires
                                .saturating_sub(consts::REDUCE_ACCESS_TOKEN_EXPIRY_TIME.into()),
                            ..access_token
                        };

                        logger::debug!(
                            access_token_expiry_after_modification =
                                modified_access_token_with_expiry.expires
                        );

                        if let Err(access_token_set_error) = store
                            .set_access_token(
                                key.clone(),
                                modified_access_token_with_expiry.clone(),
                            )
                            .await
                            .change_context(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable("DB error when setting the access token")
                        {
                            // If we are not able to set the access token in redis, the error should just be logged and proceed with the payment
                            // Payments should not fail, once the access token is successfully created
                            // The next request will create new access token, if required
                            logger::error!(access_token_set_error=?access_token_set_error);
                        }
                        Some(modified_access_token_with_expiry)
                    })
                    .await
            }
        };

        Ok(types::AddAccessTokenResult {
            access_token_result: res,
            connector_supports_access_token: true,
        })
    } else {
        Ok(types::AddAccessTokenResult {
            access_token_result: Err(types::ErrorResponse::default()),
            connector_supports_access_token: false,
        })
    }
}

pub async fn refresh_connector_auth(
    state: &SessionState,
    connector: &api_types::ConnectorData,
    router_data: &types::RouterData<
        api_types::AccessTokenAuth,
        types::AccessTokenRequestData,
        types::AccessToken,
    >,
) -> RouterResult<Result<types::AccessToken, types::ErrorResponse>> {
    let connector_integration: services::BoxedAccessTokenConnectorIntegrationInterface<
        api_types::AccessTokenAuth,
        types::AccessTokenRequestData,
        types::AccessToken,
    > = connector.connector.get_connector_integration();

    let access_token_router_data_result = services::execute_connector_processing_step(
        state,
        connector_integration,
        router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await;

    let access_token_router_data = match access_token_router_data_result {
        Ok(router_data) => Ok(router_data.response),
        Err(connector_error) => {
            // If we receive a timeout error from the connector, then
            // the error has to be handled gracefully by updating the payment status to failed.
            // further payment flow will not be continued
            if connector_error.current_context().is_connector_timeout() {
                let error_response = types::ErrorResponse {
                    code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(),
                    message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(),
                    reason: Some(consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string()),
                    status_code: 504,
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                };

                Ok(Err(error_response))
            } else {
                Err(connector_error
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Could not refresh access token"))
            }
        }
    }?;

    metrics::ACCESS_TOKEN_CREATION.add(
        1,
        router_env::metric_attributes!(("connector", connector.connector_name.to_string())),
    );
    Ok(access_token_router_data)
}

pub async fn execute_authentication_token<
    F: Clone + 'static,
    Req: Debug + Clone + 'static,
    Res: Debug + Clone + 'static,
>(
    state: &SessionState,
    connector: &api_types::ConnectorData,
    router_data: &types::RouterData<F, Req, Res>,
) -> RouterResult<Option<types::AccessTokenAuthenticationResponse>> {
    let should_create_authentication_token = connector
        .connector
        .authentication_token_for_token_creation();

    if !should_create_authentication_token {
        return Ok(None);
    }

    let authentication_token_request_data = types::AccessTokenAuthenticationRequestData::try_from(
        router_data.connector_auth_type.clone(),
    )
    .attach_printable(
        "Could not create authentication token request, invalid connector account credentials",
    )?;

    let authentication_token_response_data: Result<
        types::AccessTokenAuthenticationResponse,
        types::ErrorResponse,
    > = Err(types::ErrorResponse::default());

    let auth_token_router_data = payments::helpers::router_data_type_conversion::<
        _,
        api_types::AccessTokenAuthentication,
        _,
        _,
        _,
        _,
    >(
        router_data.clone(),
        authentication_token_request_data,
        authentication_token_response_data,
    );

    let connector_integration: services::BoxedAuthenticationTokenConnectorIntegrationInterface<
        api_types::AccessTokenAuthentication,
        types::AccessTokenAuthenticationRequestData,
        types::AccessTokenAuthenticationResponse,
    > = connector.connector.get_connector_integration();

    let auth_token_router_data_result = services::execute_connector_processing_step(
        state,
        connector_integration,
        &auth_token_router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await;

    let auth_token_result = match auth_token_router_data_result {
        Ok(router_data) => router_data.response,
        Err(connector_error) => {
            // Handle timeout errors
            if connector_error.current_context().is_connector_timeout() {
                let error_response = types::ErrorResponse {
                    code: consts::REQUEST_TIMEOUT_ERROR_CODE.to_string(),
                    message: consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string(),
                    reason: Some(consts::REQUEST_TIMEOUT_ERROR_MESSAGE.to_string()),
                    status_code: 504,
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                };
                Err(error_response)
            } else {
                return Err(connector_error
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Could not get authentication token"));
            }
        }
    };

    let authentication_token = auth_token_result
        .map_err(|_error| errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get authentication token")?;

    Ok(Some(authentication_token))
}