// File: crates/euclid_wasm/src/lib.rs
// Module: euclid_wasm
// Public functions: 29

#![allow(non_upper_case_globals)]
mod types;
mod utils;
use std::{
    collections::{HashMap, HashSet},
    str::FromStr,
    sync::OnceLock,
};

use api_models::{
    enums as api_model_enums, routing::ConnectorSelection,
    surcharge_decision_configs::SurchargeDecisionConfigs,
};
use common_enums::RoutableConnectors;
use common_types::three_ds_decision_rule_engine::ThreeDSDecisionRule;
use connector_configs::{
    common_config::{ConnectorApiIntegrationPayload, DashboardRequestPayload},
    connector,
};
use currency_conversion::{
    conversion::convert as convert_currency, types as currency_conversion_types,
};
use euclid::{
    backend::{inputs, interpreter::InterpreterBackend, EuclidBackend},
    dssa::{self, analyzer, graph::CgraphExt, state_machine},
    frontend::{
        ast,
        dir::{self, enums as dir_enums, EuclidDirFilter},
    },
};
use strum::{EnumMessage, EnumProperty, VariantNames};
use wasm_bindgen::prelude::*;

use crate::utils::JsResultExt;
type JsResult = Result<JsValue, JsValue>;
use api_models::payment_methods::CountryCodeWithName;
#[cfg(feature = "payouts")]
use common_enums::PayoutStatus;
use common_enums::{
    CountryAlpha2, DisputeStatus, EventClass, EventType, IntentStatus, MandateStatus,
    MerchantCategoryCode, MerchantCategoryCodeWithName, RefundStatus,
};
use strum::IntoEnumIterator;

struct SeedData {
    cgraph: hyperswitch_constraint_graph::ConstraintGraph<dir::DirValue>,
    connectors: Vec<ast::ConnectorChoice>,
}

static SEED_DATA: OnceLock<SeedData> = OnceLock::new();
static SEED_FOREX: OnceLock<currency_conversion_types::ExchangeRates> = OnceLock::new();

/// This function can be used by the frontend to educate wasm about the forex rates data.
/// The input argument is a struct fields base_currency and conversion where later is all the conversions associated with the base_currency
/// to all different currencies present.
#[wasm_bindgen(js_name = setForexData)]
pub fn seed_forex(forex: JsValue) -> JsResult {
    let forex: currency_conversion_types::ExchangeRates = serde_wasm_bindgen::from_value(forex)?;
    SEED_FOREX
        .set(forex)
        .map_err(|_| "Forex has already been seeded".to_string())
        .err_to_js()?;

    Ok(JsValue::NULL)
}

/// This function can be used to perform currency_conversion on the input amount, from_currency,
/// to_currency which are all expected to be one of currencies we already have in our Currency
/// enum.
#[wasm_bindgen(js_name = convertCurrency)]
pub fn convert_forex_value(amount: i64, from_currency: JsValue, to_currency: JsValue) -> JsResult {
    let forex_data = SEED_FOREX
        .get()
        .ok_or("Forex Data not seeded")
        .err_to_js()?;
    let from_currency: common_enums::Currency = serde_wasm_bindgen::from_value(from_currency)?;
    let to_currency: common_enums::Currency = serde_wasm_bindgen::from_value(to_currency)?;
    let converted_amount = convert_currency(forex_data, from_currency, to_currency, amount)
        .map_err(|_| "conversion not possible for provided values")
        .err_to_js()?;

    Ok(serde_wasm_bindgen::to_value(&converted_amount)?)
}

/// This function can be used by the frontend to get all the two letter country codes
/// along with their country names.
#[wasm_bindgen(js_name=getTwoLetterCountryCode)]
pub fn get_two_letter_country_code() -> JsResult {
    let country_code_with_name = CountryAlpha2::iter()
        .map(|country_code| CountryCodeWithName {
            code: country_code,
            name: common_enums::Country::from_alpha2(country_code),
        })
        .collect::<Vec<_>>();

    Ok(serde_wasm_bindgen::to_value(&country_code_with_name)?)
}

/// This function can be used by the frontend to get all the merchant category codes
/// along with their names.
#[wasm_bindgen(js_name=getMerchantCategoryCodeWithName)]
pub fn get_merchant_category_code_with_name() -> JsResult {
    let merchant_category_codes_with_name = MerchantCategoryCode::iter()
        .map(|mcc_value| MerchantCategoryCodeWithName {
            code: mcc_value,
            name: mcc_value.to_merchant_category_name(),
        })
        .collect::<Vec<_>>();

    Ok(serde_wasm_bindgen::to_value(
        &merchant_category_codes_with_name,
    )?)
}

/// This function can be used by the frontend to provide the WASM with information about
/// all the merchant's connector accounts. The input argument is a vector of all the merchant's
/// connector accounts from the API.
#[cfg(feature = "v1")]
#[wasm_bindgen(js_name = seedKnowledgeGraph)]
pub fn seed_knowledge_graph(mcas: JsValue) -> JsResult {
    let mcas: Vec<api_models::admin::MerchantConnectorResponse> =
        serde_wasm_bindgen::from_value(mcas)?;
    let connectors: Vec<ast::ConnectorChoice> = mcas
        .iter()
        .map(|mca| {
            Ok::<_, strum::ParseError>(ast::ConnectorChoice {
                connector: RoutableConnectors::from_str(&mca.connector_name)?,
            })
        })
        .collect::<Result<_, _>>()
        .map_err(|_| "invalid connector name received")
        .err_to_js()?;
    let pm_filter = kgraph_utils::types::PaymentMethodFilters(HashMap::new());
    let config = kgraph_utils::types::CountryCurrencyFilter {
        connector_configs: HashMap::new(),
        default_configs: Some(pm_filter),
    };
    let mca_graph = kgraph_utils::mca::make_mca_graph(mcas, &config).err_to_js()?;
    let analysis_graph = hyperswitch_constraint_graph::ConstraintGraph::combine(
        &mca_graph,
        &dssa::truth::ANALYSIS_GRAPH,
    )
    .err_to_js()?;

    SEED_DATA
        .set(SeedData {
            cgraph: analysis_graph,
            connectors,
        })
        .map_err(|_| "Knowledge Graph has been already seeded".to_string())
        .err_to_js()?;

    Ok(JsValue::NULL)
}

/// This function allows the frontend to get all the merchant's configured
/// connectors that are valid for a rule based on the conditions specified in
/// the rule
#[wasm_bindgen(js_name = getValidConnectorsForRule)]
pub fn get_valid_connectors_for_rule(rule: JsValue) -> JsResult {
    let seed_data = SEED_DATA.get().ok_or("Data not seeded").err_to_js()?;

    let rule: ast::Rule<ConnectorSelection> = serde_wasm_bindgen::from_value(rule)?;
    let dir_rule = ast::lowering::lower_rule(rule).err_to_js()?;
    let mut valid_connectors: Vec<(ast::ConnectorChoice, dir::DirValue)> = seed_data
        .connectors
        .iter()
        .cloned()
        .map(|choice| (choice.clone(), dir::DirValue::Connector(Box::new(choice))))
        .collect();
    let mut invalid_connectors: HashSet<ast::ConnectorChoice> = HashSet::new();

    let mut ctx_manager = state_machine::RuleContextManager::new(&dir_rule, &[]);

    let dummy_meta = HashMap::new();

    // For every conjunctive context in the Rule, verify validity of all still-valid connectors
    // using the knowledge graph
    while let Some(ctx) = ctx_manager.advance_mut().err_to_js()? {
        // Standalone conjunctive context analysis to ensure the context itself is valid before
        // checking it against merchant's connectors
        seed_data
            .cgraph
            .perform_context_analysis(
                ctx,
                &mut hyperswitch_constraint_graph::Memoization::new(),
                None,
            )
            .err_to_js()?;

        // Update conjunctive context and run analysis on all of merchant's connectors.
        for (conn, choice) in &valid_connectors {
            if invalid_connectors.contains(conn) {
                continue;
            }

            let ctx_val = dssa::types::ContextValue::assertion(choice, &dummy_meta);
            ctx.push(ctx_val);
            let analysis_result = seed_data.cgraph.perform_context_analysis(
                ctx,
                &mut hyperswitch_constraint_graph::Memoization::new(),
                None,
            );
            if analysis_result.is_err() {
                invalid_connectors.insert(conn.clone());
            }
            ctx.pop();
        }
    }

    valid_connectors.retain(|(k, _)| !invalid_connectors.contains(k));

    let valid_connectors: Vec<ast::ConnectorChoice> =
        valid_connectors.into_iter().map(|c| c.0).collect();

    Ok(serde_wasm_bindgen::to_value(&valid_connectors)?)
}

#[wasm_bindgen(js_name = analyzeProgram)]
pub fn analyze_program(js_program: JsValue) -> JsResult {
    let program: ast::Program<ConnectorSelection> = serde_wasm_bindgen::from_value(js_program)?;
    analyzer::analyze(program, SEED_DATA.get().map(|sd| &sd.cgraph)).err_to_js()?;
    Ok(JsValue::NULL)
}

#[wasm_bindgen(js_name = runProgram)]
pub fn run_program(program: JsValue, input: JsValue) -> JsResult {
    let program: ast::Program<ConnectorSelection> = serde_wasm_bindgen::from_value(program)?;
    let input: inputs::BackendInput = serde_wasm_bindgen::from_value(input)?;

    let backend = InterpreterBackend::with_program(program).err_to_js()?;

    let res: euclid::backend::BackendOutput<ConnectorSelection> =
        backend.execute(input).err_to_js()?;

    Ok(serde_wasm_bindgen::to_value(&res)?)
}

#[wasm_bindgen(js_name = getAllConnectors)]
pub fn get_all_connectors() -> JsResult {
    Ok(serde_wasm_bindgen::to_value(RoutableConnectors::VARIANTS)?)
}

#[wasm_bindgen(js_name = getAllKeys)]
pub fn get_all_keys() -> JsResult {
    let excluded_keys = [
        "Connector",
        // 3DS Decision Rule Keys should not be included in the payument routing keys
        "issuer_name",
        "issuer_country",
        "customer_device_platform",
        "customer_device_type",
        "customer_device_display_size",
        "acquirer_country",
        "acquirer_fraud_rate",
    ];

    let keys: Vec<&'static str> = dir::DirKeyKind::VARIANTS
        .iter()
        .copied()
        .filter(|s| !excluded_keys.contains(s))
        .collect();
    Ok(serde_wasm_bindgen::to_value(&keys)?)
}

#[wasm_bindgen(js_name = getKeyType)]
pub fn get_key_type(key: &str) -> Result<String, String> {
    let key = dir::DirKeyKind::from_str(key).map_err(|_| "Invalid key received".to_string())?;
    let key_str = key.get_type().to_string();
    Ok(key_str)
}

#[wasm_bindgen(js_name = getThreeDsKeys)]
pub fn get_three_ds_keys() -> JsResult {
    let keys = <common_types::payments::ConditionalConfigs as EuclidDirFilter>::ALLOWED;
    Ok(serde_wasm_bindgen::to_value(keys)?)
}

#[wasm_bindgen(js_name= getSurchargeKeys)]
pub fn get_surcharge_keys() -> JsResult {
    let keys = <SurchargeDecisionConfigs as EuclidDirFilter>::ALLOWED;
    Ok(serde_wasm_bindgen::to_value(keys)?)
}

#[wasm_bindgen(js_name= getThreeDsDecisionRuleKeys)]
pub fn get_three_ds_decision_rule_keys() -> JsResult {
    let keys = <ThreeDSDecisionRule as EuclidDirFilter>::ALLOWED;
    Ok(serde_wasm_bindgen::to_value(keys)?)
}

#[wasm_bindgen(js_name=parseToString)]
pub fn parser(val: String) -> String {
    ron_parser::my_parse(val)
}

#[wasm_bindgen(js_name = getVariantValues)]
pub fn get_variant_values(key: &str) -> Result<JsValue, JsValue> {
    let key = dir::DirKeyKind::from_str(key).map_err(|_| "Invalid key received".to_string())?;

    let variants: &[&str] = match key {
        dir::DirKeyKind::PaymentMethod => dir_enums::PaymentMethod::VARIANTS,
        dir::DirKeyKind::CardType => dir_enums::CardType::VARIANTS,
        dir::DirKeyKind::CardNetwork => dir_enums::CardNetwork::VARIANTS,
        dir::DirKeyKind::PayLaterType => dir_enums::PayLaterType::VARIANTS,
        dir::DirKeyKind::WalletType => dir_enums::WalletType::VARIANTS,
        dir::DirKeyKind::BankRedirectType => dir_enums::BankRedirectType::VARIANTS,
        dir::DirKeyKind::CryptoType => dir_enums::CryptoType::VARIANTS,
        dir::DirKeyKind::RewardType => dir_enums::RewardType::VARIANTS,
        dir::DirKeyKind::AuthenticationType => dir_enums::AuthenticationType::VARIANTS,
        dir::DirKeyKind::CaptureMethod => dir_enums::CaptureMethod::VARIANTS,
        dir::DirKeyKind::PaymentCurrency => dir_enums::PaymentCurrency::VARIANTS,
        dir::DirKeyKind::BusinessCountry => dir_enums::Country::VARIANTS,
        dir::DirKeyKind::BillingCountry => dir_enums::Country::VARIANTS,
        dir::DirKeyKind::BankTransferType => dir_enums::BankTransferType::VARIANTS,
        dir::DirKeyKind::UpiType => dir_enums::UpiType::VARIANTS,
        dir::DirKeyKind::SetupFutureUsage => dir_enums::SetupFutureUsage::VARIANTS,
        dir::DirKeyKind::PaymentType => dir_enums::PaymentType::VARIANTS,
        dir::DirKeyKind::MandateType => dir_enums::MandateType::VARIANTS,
        dir::DirKeyKind::MandateAcceptanceType => dir_enums::MandateAcceptanceType::VARIANTS,
        dir::DirKeyKind::CardRedirectType => dir_enums::CardRedirectType::VARIANTS,
        dir::DirKeyKind::GiftCardType => dir_enums::GiftCardType::VARIANTS,
        dir::DirKeyKind::VoucherType => dir_enums::VoucherType::VARIANTS,
        dir::DirKeyKind::BankDebitType => dir_enums::BankDebitType::VARIANTS,
        dir::DirKeyKind::RealTimePaymentType => dir_enums::RealTimePaymentType::VARIANTS,
        dir::DirKeyKind::OpenBankingType => dir_enums::OpenBankingType::VARIANTS,
        dir::DirKeyKind::MobilePaymentType => dir_enums::MobilePaymentType::VARIANTS,
        dir::DirKeyKind::IssuerCountry => dir_enums::Country::VARIANTS,
        dir::DirKeyKind::AcquirerCountry => dir_enums::Country::VARIANTS,
        dir::DirKeyKind::CustomerDeviceType => dir_enums::CustomerDeviceType::VARIANTS,
        dir::DirKeyKind::CustomerDevicePlatform => dir_enums::CustomerDevicePlatform::VARIANTS,
        dir::DirKeyKind::CustomerDeviceDisplaySize => {
            dir_enums::CustomerDeviceDisplaySize::VARIANTS
        }

        dir::DirKeyKind::PaymentAmount
        | dir::DirKeyKind::Connector
        | dir::DirKeyKind::CardBin
        | dir::DirKeyKind::BusinessLabel
        | dir::DirKeyKind::MetaData
        | dir::DirKeyKind::IssuerName
        | dir::DirKeyKind::AcquirerFraudRate => Err("Key does not have variants".to_string())?,
    };

    Ok(serde_wasm_bindgen::to_value(variants)?)
}

#[wasm_bindgen(js_name = addTwo)]
pub fn add_two(n1: i64, n2: i64) -> i64 {
    n1 + n2
}

#[wasm_bindgen(js_name = getDescriptionCategory)]
pub fn get_description_category() -> JsResult {
    let keys = dir::DirKeyKind::VARIANTS
        .iter()
        .copied()
        .filter(|s| s != &"Connector")
        .collect::<Vec<&'static str>>();
    let mut category: HashMap<Option<&str>, Vec<types::Details<'_>>> = HashMap::new();
    for key in keys {
        let dir_key =
            dir::DirKeyKind::from_str(key).map_err(|_| "Invalid key received".to_string())?;
        let details = types::Details {
            description: dir_key.get_detailed_message(),
            kind: dir_key.clone(),
        };
        category
            .entry(dir_key.get_str("Category"))
            .and_modify(|val| val.push(details.clone()))
            .or_insert(vec![details]);
    }

    Ok(serde_wasm_bindgen::to_value(&category)?)
}

#[wasm_bindgen(js_name = getConnectorConfig)]
pub fn get_connector_config(key: &str) -> JsResult {
    let key = api_model_enums::Connector::from_str(key)
        .map_err(|_| "Invalid key received".to_string())?;
    let res = connector::ConnectorConfig::get_connector_config(key)?;
    Ok(serde_wasm_bindgen::to_value(&res)?)
}

#[cfg(feature = "payouts")]
#[wasm_bindgen(js_name = getPayoutConnectorConfig)]
pub fn get_payout_connector_config(key: &str) -> JsResult {
    let key = api_model_enums::PayoutConnectors::from_str(key)
        .map_err(|_| "Invalid key received".to_string())?;
    let res = connector::ConnectorConfig::get_payout_connector_config(key)?;
    Ok(serde_wasm_bindgen::to_value(&res)?)
}

#[wasm_bindgen(js_name = getAuthenticationConnectorConfig)]
pub fn get_authentication_connector_config(key: &str) -> JsResult {
    let key = api_model_enums::AuthenticationConnectors::from_str(key)
        .map_err(|_| "Invalid key received".to_string())?;
    let res = connector::ConnectorConfig::get_authentication_connector_config(key)?;
    Ok(serde_wasm_bindgen::to_value(&res)?)
}

#[wasm_bindgen(js_name = getTaxProcessorConfig)]
pub fn get_tax_processor_config(key: &str) -> JsResult {
    let key = api_model_enums::TaxConnectors::from_str(key)
        .map_err(|_| "Invalid key received".to_string())?;
    let res = connector::ConnectorConfig::get_tax_processor_config(key)?;
    Ok(serde_wasm_bindgen::to_value(&res)?)
}

#[wasm_bindgen(js_name = getPMAuthenticationProcessorConfig)]
pub fn get_pm_authentication_processor_config(key: &str) -> JsResult {
    let key: api_model_enums::PmAuthConnectors = api_model_enums::PmAuthConnectors::from_str(key)
        .map_err(|_| "Invalid key received".to_string())?;
    let res = connector::ConnectorConfig::get_pm_authentication_processor_config(key)?;
    Ok(serde_wasm_bindgen::to_value(&res)?)
}

#[wasm_bindgen(js_name = getRequestPayload)]
pub fn get_request_payload(input: JsValue, response: JsValue) -> JsResult {
    let input: DashboardRequestPayload = serde_wasm_bindgen::from_value(input)?;
    let api_response: ConnectorApiIntegrationPayload = serde_wasm_bindgen::from_value(response)?;
    let result = DashboardRequestPayload::create_connector_request(input, api_response);
    Ok(serde_wasm_bindgen::to_value(&result)?)
}

#[wasm_bindgen(js_name = getResponsePayload)]
pub fn get_response_payload(input: JsValue) -> JsResult {
    let input: ConnectorApiIntegrationPayload = serde_wasm_bindgen::from_value(input)?;
    let result = ConnectorApiIntegrationPayload::get_transformed_response_payload(input);
    Ok(serde_wasm_bindgen::to_value(&result)?)
}

#[cfg(feature = "payouts")]
#[wasm_bindgen(js_name = getAllPayoutKeys)]
pub fn get_all_payout_keys() -> JsResult {
    let keys: Vec<&'static str> = dir::PayoutDirKeyKind::VARIANTS.to_vec();
    Ok(serde_wasm_bindgen::to_value(&keys)?)
}

#[cfg(feature = "payouts")]
#[wasm_bindgen(js_name = getPayoutVariantValues)]
pub fn get_payout_variant_values(key: &str) -> Result<JsValue, JsValue> {
    let key =
        dir::PayoutDirKeyKind::from_str(key).map_err(|_| "Invalid key received".to_string())?;

    let variants: &[&str] = match key {
        dir::PayoutDirKeyKind::BusinessCountry => dir_enums::BusinessCountry::VARIANTS,
        dir::PayoutDirKeyKind::BillingCountry => dir_enums::BillingCountry::VARIANTS,
        dir::PayoutDirKeyKind::PayoutCurrency => dir_enums::PaymentCurrency::VARIANTS,
        dir::PayoutDirKeyKind::PayoutType => dir_enums::PayoutType::VARIANTS,
        dir::PayoutDirKeyKind::WalletType => dir_enums::PayoutWalletType::VARIANTS,
        dir::PayoutDirKeyKind::BankTransferType => dir_enums::PayoutBankTransferType::VARIANTS,

        dir::PayoutDirKeyKind::PayoutAmount | dir::PayoutDirKeyKind::BusinessLabel => {
            Err("Key does not have variants".to_string())?
        }
    };

    Ok(serde_wasm_bindgen::to_value(variants)?)
}

#[cfg(feature = "payouts")]
#[wasm_bindgen(js_name = getPayoutDescriptionCategory)]
pub fn get_payout_description_category() -> JsResult {
    let keys = dir::PayoutDirKeyKind::VARIANTS.to_vec();
    let mut category: HashMap<Option<&str>, Vec<types::PayoutDetails<'_>>> = HashMap::new();
    for key in keys {
        let dir_key =
            dir::PayoutDirKeyKind::from_str(key).map_err(|_| "Invalid key received".to_string())?;
        let details = types::PayoutDetails {
            description: dir_key.get_detailed_message(),
            kind: dir_key.clone(),
        };
        category
            .entry(dir_key.get_str("Category"))
            .and_modify(|val| val.push(details.clone()))
            .or_insert(vec![details]);
    }

    Ok(serde_wasm_bindgen::to_value(&category)?)
}

#[wasm_bindgen(js_name = getValidWebhookStatus)]
pub fn get_valid_webhook_status(key: &str) -> JsResult {
    let event_class = EventClass::from_str(key)
        .map_err(|_| "Invalid webhook event type received".to_string())
        .err_to_js()?;

    match event_class {
        EventClass::Payments => {
            let statuses: Vec<IntentStatus> = IntentStatus::iter()
                .filter(|intent_status| Into::<Option<EventType>>::into(*intent_status).is_some())
                .collect();
            Ok(serde_wasm_bindgen::to_value(&statuses)?)
        }
        EventClass::Refunds => {
            let statuses: Vec<RefundStatus> = RefundStatus::iter()
                .filter(|status| Into::<Option<EventType>>::into(*status).is_some())
                .collect();
            Ok(serde_wasm_bindgen::to_value(&statuses)?)
        }
        EventClass::Disputes => {
            let statuses: Vec<DisputeStatus> = DisputeStatus::iter().collect();
            Ok(serde_wasm_bindgen::to_value(&statuses)?)
        }
        EventClass::Mandates => {
            let statuses: Vec<MandateStatus> = MandateStatus::iter()
                .filter(|status| Into::<Option<EventType>>::into(*status).is_some())
                .collect();
            Ok(serde_wasm_bindgen::to_value(&statuses)?)
        }
        #[cfg(feature = "payouts")]
        EventClass::Payouts => {
            let statuses: Vec<PayoutStatus> = PayoutStatus::iter()
                .filter(|status| Into::<Option<EventType>>::into(*status).is_some())
                .collect();
            Ok(serde_wasm_bindgen::to_value(&statuses)?)
        }
    }
}


// Struct: PaymentMethodsEnabled
// File: crates/api_models/src/admin.rs
// Module: api_models
// Implementations: 1

pub struct PaymentMethodsEnabled


// File: crates/analytics/src/sdk_events/metrics/sdk_initiated_count.rs
// Module: analytics

use std::collections::HashSet;

use api_models::analytics::{
    sdk_events::{
        SdkEventDimensions, SdkEventFilters, SdkEventMetricsBucketIdentifier, SdkEventNames,
    },
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

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

#[async_trait::async_trait]
impl<T> super::SdkEventMetric<T> for SdkInitiatedCount
where
    T: AnalyticsDataSource + super::SdkEventMetricAnalytics,
    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: &[SdkEventDimensions],
        publishable_key: &str,
        filters: &SdkEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(SdkEventMetricsBucketIdentifier, SdkEventMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::SdkEventsAnalytics);
        let dimensions = dimensions.to_vec();

        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()?;

        if let Some(granularity) = granularity {
            query_builder
                .add_granularity_in_mins(granularity)
                .switch()?;
        }

        filters.set_filter_clause(&mut query_builder).switch()?;

        query_builder
            .add_filter_clause("merchant_id", publishable_key)
            .switch()?;

        query_builder
            .add_bool_filter_clause("first_event", 1)
            .switch()?;

        query_builder
            .add_filter_clause("event_name", SdkEventNames::OrcaElementsCalled)
            .switch()?;

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .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.as_ref() {
            query_builder
                .add_group_by_clause("time_bucket")
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<SdkEventMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    SdkEventMetricsBucketIdentifier::new(
                        i.payment_method.clone(),
                        i.platform.clone(),
                        i.browser_name.clone(),
                        i.source.clone(),
                        i.component.clone(),
                        i.payment_experience.clone(),
                        i.time_bucket.clone(),
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(SdkEventMetricsBucketIdentifier, SdkEventMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// Implementation: impl api::RefundExecute for for Xendit
// File: crates/hyperswitch_connectors/src/connectors/xendit.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::RefundExecute for for Xendit


// Struct: GrpcClients
// File: crates/external_services/src/grpc_client.rs
// Module: external_services
// Implementations: 0

pub struct GrpcClients


// Struct: PaymentMethod
// File: crates/hyperswitch_connectors/src/connectors/recurly/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PaymentMethod


// Function: payouts_confirm_core
// File: crates/router/src/core/payouts.rs
// Module: router

pub fn payouts_confirm_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: payouts::PayoutCreateRequest,
) -> RouterResponse<payouts::PayoutCreateResponse>


// Function: merchant_account_kv_status
// File: crates/openapi/src/routes/merchant_account.rs
// Module: openapi

pub fn merchant_account_kv_status()


// Struct: Analytics
// File: crates/router/src/analytics.rs
// Module: router
// Implementations: 1

pub struct Analytics


// Implementation: impl RefundMetricAccumulator for for SuccessRateAccumulator
// File: crates/analytics/src/refunds/accumulator.rs
// Module: analytics
// Methods: 2 total (0 public)

impl RefundMetricAccumulator for for SuccessRateAccumulator


// Implementation: impl RefundExecute for for Wise
// File: crates/hyperswitch_connectors/src/connectors/wise.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl RefundExecute for for Wise


// Implementation: impl api::PaymentToken for for Worldline
// File: crates/hyperswitch_connectors/src/connectors/worldline.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentToken for for Worldline


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

pub use diesel_models::types::OrderDetailsWithAmount;

use crate::{
    router_data::{AccessToken, AccessTokenAuthenticationResponse, RouterData},
    router_data_v2::{self, RouterDataV2},
    router_flow_types::{
        mandate_revoke::MandateRevoke,
        revenue_recovery::InvoiceRecordBack,
        subscriptions::{
            GetSubscriptionEstimate, GetSubscriptionPlanPrices, GetSubscriptionPlans,
            SubscriptionCreate,
        },
        AccessTokenAuth, AccessTokenAuthentication, Authenticate, AuthenticationConfirmation,
        Authorize, AuthorizeSessionToken, BillingConnectorInvoiceSync,
        BillingConnectorPaymentsSync, CalculateTax, Capture, CompleteAuthorize,
        CreateConnectorCustomer, CreateOrder, Execute, ExternalVaultProxy, GiftCardBalanceCheck,
        IncrementalAuthorization, PSync, PaymentMethodToken, PostAuthenticate, PostCaptureVoid,
        PostSessionTokens, PreAuthenticate, PreProcessing, RSync, SdkSessionUpdate, Session,
        SetupMandate, UpdateMetadata, VerifyWebhookSource, Void,
    },
    router_request_types::{
        revenue_recovery::{
            BillingConnectorInvoiceSyncRequest, BillingConnectorPaymentsSyncRequest,
            InvoiceRecordBackRequest,
        },
        subscriptions::{
            GetSubscriptionEstimateRequest, GetSubscriptionPlanPricesRequest,
            GetSubscriptionPlansRequest, SubscriptionCreateRequest,
        },
        unified_authentication_service::{
            UasAuthenticationRequestData, UasAuthenticationResponseData,
            UasConfirmationRequestData, UasPostAuthenticationRequestData,
            UasPreAuthenticationRequestData,
        },
        AccessTokenAuthenticationRequestData, AccessTokenRequestData, AuthorizeSessionTokenData,
        CompleteAuthorizeData, ConnectorCustomerData, CreateOrderRequestData,
        ExternalVaultProxyPaymentsData, GiftCardBalanceCheckRequestData, MandateRevokeRequestData,
        PaymentMethodTokenizationData, PaymentsAuthenticateData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCancelPostCaptureData, PaymentsCaptureData,
        PaymentsIncrementalAuthorizationData, PaymentsPostAuthenticateData,
        PaymentsPostSessionTokensData, PaymentsPreAuthenticateData, PaymentsPreProcessingData,
        PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData,
        PaymentsUpdateMetadataData, RefundsData, SdkPaymentsSessionUpdateData,
        SetupMandateRequestData, VaultRequestData, VerifyWebhookSourceRequestData,
    },
    router_response_types::{
        revenue_recovery::{
            BillingConnectorInvoiceSyncResponse, BillingConnectorPaymentsSyncResponse,
            InvoiceRecordBackResponse,
        },
        subscriptions::{
            GetSubscriptionEstimateResponse, GetSubscriptionPlanPricesResponse,
            GetSubscriptionPlansResponse, SubscriptionCreateResponse,
        },
        GiftCardBalanceCheckResponseData, MandateRevokeResponseData, PaymentsResponseData,
        RefundsResponseData, TaxCalculationResponseData, VaultResponseData,
        VerifyWebhookSourceResponseData,
    },
};
#[cfg(feature = "payouts")]
pub use crate::{router_request_types::PayoutsData, router_response_types::PayoutsResponseData};

pub type PaymentsAuthorizeRouterData =
    RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>;
pub type ExternalVaultProxyPaymentsRouterData =
    RouterData<ExternalVaultProxy, ExternalVaultProxyPaymentsData, PaymentsResponseData>;
pub type PaymentsAuthorizeSessionTokenRouterData =
    RouterData<AuthorizeSessionToken, AuthorizeSessionTokenData, PaymentsResponseData>;
pub type PaymentsPreProcessingRouterData =
    RouterData<PreProcessing, PaymentsPreProcessingData, PaymentsResponseData>;
pub type PaymentsPreAuthenticateRouterData =
    RouterData<PreAuthenticate, PaymentsPreAuthenticateData, PaymentsResponseData>;
pub type PaymentsAuthenticateRouterData =
    RouterData<Authenticate, PaymentsAuthenticateData, PaymentsResponseData>;
pub type PaymentsPostAuthenticateRouterData =
    RouterData<PostAuthenticate, PaymentsPostAuthenticateData, PaymentsResponseData>;
pub type PaymentsSyncRouterData = RouterData<PSync, PaymentsSyncData, PaymentsResponseData>;
pub type PaymentsCaptureRouterData = RouterData<Capture, PaymentsCaptureData, PaymentsResponseData>;
pub type PaymentsCancelRouterData = RouterData<Void, PaymentsCancelData, PaymentsResponseData>;
pub type PaymentsCancelPostCaptureRouterData =
    RouterData<PostCaptureVoid, PaymentsCancelPostCaptureData, PaymentsResponseData>;
pub type SetupMandateRouterData =
    RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>;
pub type RefundsRouterData<F> = RouterData<F, RefundsData, RefundsResponseData>;
pub type RefundExecuteRouterData = RouterData<Execute, RefundsData, RefundsResponseData>;
pub type RefundSyncRouterData = RouterData<RSync, RefundsData, RefundsResponseData>;
pub type TokenizationRouterData =
    RouterData<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>;
pub type ConnectorCustomerRouterData =
    RouterData<CreateConnectorCustomer, ConnectorCustomerData, PaymentsResponseData>;
pub type PaymentsCompleteAuthorizeRouterData =
    RouterData<CompleteAuthorize, CompleteAuthorizeData, PaymentsResponseData>;
pub type PaymentsTaxCalculationRouterData =
    RouterData<CalculateTax, PaymentsTaxCalculationData, TaxCalculationResponseData>;
pub type AccessTokenAuthenticationRouterData = RouterData<
    AccessTokenAuthentication,
    AccessTokenAuthenticationRequestData,
    AccessTokenAuthenticationResponse,
>;
pub type PaymentsGiftCardBalanceCheckRouterData = RouterData<
    GiftCardBalanceCheck,
    GiftCardBalanceCheckRequestData,
    GiftCardBalanceCheckResponseData,
>;
pub type RefreshTokenRouterData = RouterData<AccessTokenAuth, AccessTokenRequestData, AccessToken>;
pub type PaymentsPostSessionTokensRouterData =
    RouterData<PostSessionTokens, PaymentsPostSessionTokensData, PaymentsResponseData>;
pub type PaymentsSessionRouterData = RouterData<Session, PaymentsSessionData, PaymentsResponseData>;
pub type PaymentsUpdateMetadataRouterData =
    RouterData<UpdateMetadata, PaymentsUpdateMetadataData, PaymentsResponseData>;

pub type CreateOrderRouterData =
    RouterData<CreateOrder, CreateOrderRequestData, PaymentsResponseData>;
pub type UasPostAuthenticationRouterData =
    RouterData<PostAuthenticate, UasPostAuthenticationRequestData, UasAuthenticationResponseData>;
pub type UasPreAuthenticationRouterData =
    RouterData<PreAuthenticate, UasPreAuthenticationRequestData, UasAuthenticationResponseData>;

pub type UasAuthenticationConfirmationRouterData = RouterData<
    AuthenticationConfirmation,
    UasConfirmationRequestData,
    UasAuthenticationResponseData,
>;

pub type MandateRevokeRouterData =
    RouterData<MandateRevoke, MandateRevokeRequestData, MandateRevokeResponseData>;
pub type PaymentsIncrementalAuthorizationRouterData = RouterData<
    IncrementalAuthorization,
    PaymentsIncrementalAuthorizationData,
    PaymentsResponseData,
>;
pub type SdkSessionUpdateRouterData =
    RouterData<SdkSessionUpdate, SdkPaymentsSessionUpdateData, PaymentsResponseData>;

pub type VerifyWebhookSourceRouterData = RouterData<
    VerifyWebhookSource,
    VerifyWebhookSourceRequestData,
    VerifyWebhookSourceResponseData,
>;

#[cfg(feature = "payouts")]
pub type PayoutsRouterData<F> = RouterData<F, PayoutsData, PayoutsResponseData>;

pub type InvoiceRecordBackRouterData =
    RouterData<InvoiceRecordBack, InvoiceRecordBackRequest, InvoiceRecordBackResponse>;

pub type GetSubscriptionPlansRouterData =
    RouterData<GetSubscriptionPlans, GetSubscriptionPlansRequest, GetSubscriptionPlansResponse>;

pub type GetSubscriptionEstimateRouterData = RouterData<
    GetSubscriptionEstimate,
    GetSubscriptionEstimateRequest,
    GetSubscriptionEstimateResponse,
>;

pub type UasAuthenticationRouterData =
    RouterData<Authenticate, UasAuthenticationRequestData, UasAuthenticationResponseData>;

pub type BillingConnectorPaymentsSyncRouterData = RouterData<
    BillingConnectorPaymentsSync,
    BillingConnectorPaymentsSyncRequest,
    BillingConnectorPaymentsSyncResponse,
>;

pub type BillingConnectorInvoiceSyncRouterData = RouterData<
    BillingConnectorInvoiceSync,
    BillingConnectorInvoiceSyncRequest,
    BillingConnectorInvoiceSyncResponse,
>;

pub type BillingConnectorInvoiceSyncRouterDataV2 = RouterDataV2<
    BillingConnectorInvoiceSync,
    router_data_v2::flow_common_types::BillingConnectorInvoiceSyncFlowData,
    BillingConnectorInvoiceSyncRequest,
    BillingConnectorInvoiceSyncResponse,
>;

pub type BillingConnectorPaymentsSyncRouterDataV2 = RouterDataV2<
    BillingConnectorPaymentsSync,
    router_data_v2::flow_common_types::BillingConnectorPaymentsSyncFlowData,
    BillingConnectorPaymentsSyncRequest,
    BillingConnectorPaymentsSyncResponse,
>;

pub type InvoiceRecordBackRouterDataV2 = RouterDataV2<
    InvoiceRecordBack,
    router_data_v2::flow_common_types::InvoiceRecordBackData,
    InvoiceRecordBackRequest,
    InvoiceRecordBackResponse,
>;

pub type GetSubscriptionPlanPricesRouterData = RouterData<
    GetSubscriptionPlanPrices,
    GetSubscriptionPlanPricesRequest,
    GetSubscriptionPlanPricesResponse,
>;

pub type VaultRouterData<F> = RouterData<F, VaultRequestData, VaultResponseData>;

pub type VaultRouterDataV2<F> = RouterDataV2<
    F,
    router_data_v2::flow_common_types::VaultConnectorFlowData,
    VaultRequestData,
    VaultResponseData,
>;

pub type ExternalVaultProxyPaymentsRouterDataV2 = RouterDataV2<
    ExternalVaultProxy,
    router_data_v2::flow_common_types::ExternalVaultProxyFlowData,
    ExternalVaultProxyPaymentsData,
    PaymentsResponseData,
>;

pub type SubscriptionCreateRouterData =
    RouterData<SubscriptionCreate, SubscriptionCreateRequest, SubscriptionCreateResponse>;


// Struct: PaymentAttempt
// File: crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
// Module: hyperswitch_domain_models
// Implementations: 7
// Traits: ForeignIDRef, ForeignIDRef, behaviour::Conversion, behaviour::Conversion

pub struct PaymentAttempt

ForeignIDRef ForeignIDRef behaviour::Conversion behaviour::Conversion


// Struct: RiskifiedPaymentsResponse
// File: crates/hyperswitch_connectors/src/connectors/riskified/transformers/api.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct RiskifiedPaymentsResponse


// Function: update_process_status_by_ids
// File: crates/diesel_models/src/query/process_tracker.rs
// Module: diesel_models

pub fn update_process_status_by_ids(
        conn: &PgPooledConn,
        task_ids: Vec<String>,
        task_update: ProcessTrackerUpdate,
    ) -> StorageResult<usize>


// Struct: CardAndNetworkTokenData
// File: crates/hyperswitch_domain_models/src/payments.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct CardAndNetworkTokenData


// Trait: SerializableSecret
// File: crates/masking/src/serde.rs
// Module: masking

pub trait SerializableSecret: Serialize


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

pub fn is_preprocessing_required_for_wallets(connector_name: String) -> bool


// Implementation: impl super::KafkaMessage for for KafkaFraudCheckEvent
// File: crates/router/src/services/kafka/fraud_check_event.rs
// Module: router
// Methods: 2 total (0 public)

impl super::KafkaMessage for for KafkaFraudCheckEvent


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

impl Responder


// Function: from_payments_request_and_payment_attempt
// File: crates/hyperswitch_domain_models/src/payments/payment_attempt.rs
// Module: hyperswitch_domain_models

pub fn from_payments_request_and_payment_attempt(
        payments_request: &api_models::payments::PaymentsRequest,
        payment_attempt: Option<&PaymentAttempt>,
    ) -> Option<Self>


// Struct: SingleUseTokenKey
// File: crates/hyperswitch_domain_models/src/payment_method_data.rs
// Module: hyperswitch_domain_models
// Implementations: 1

pub struct SingleUseTokenKey


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

impl ConnectorValidation for for Bitpay


// Function: handle_unified_connector_service_response_for_payment_authorize
// File: crates/router/src/core/unified_connector_service.rs
// Module: router

pub fn handle_unified_connector_service_response_for_payment_authorize(
    response: PaymentServiceAuthorizeResponse,
) -> UnifiedConnectorServiceResult


// File: crates/drainer/src/types.rs
// Module: drainer
// Public functions: 1
// Public structs: 1

use std::collections::HashMap;

use common_utils::errors;
use error_stack::ResultExt;
use serde::{de::value::MapDeserializer, Deserialize, Serialize};

use crate::{
    kv,
    utils::{deserialize_db_op, deserialize_i64},
};

#[derive(Deserialize, Serialize)]
pub struct StreamData {
    pub request_id: String,
    pub global_id: String,
    #[serde(deserialize_with = "deserialize_db_op")]
    pub typed_sql: kv::DBOperation,
    #[serde(deserialize_with = "deserialize_i64")]
    pub pushed_at: i64,
}

impl StreamData {
    pub fn from_hashmap(
        hashmap: HashMap<String, String>,
    ) -> errors::CustomResult<Self, errors::ParsingError> {
        let iter = MapDeserializer::<
            '_,
            std::collections::hash_map::IntoIter<String, String>,
            serde_json::error::Error,
        >::new(hashmap.into_iter());

        Self::deserialize(iter)
            .change_context(errors::ParsingError::StructParseFailure("StreamData"))
    }
}


// Struct: Archipel
// File: crates/hyperswitch_connectors/src/connectors/archipel.rs
// Module: hyperswitch_connectors
// Implementations: 18
// Traits: api::PaymentAuthorize, api::PaymentSync, api::PaymentVoid, api::PaymentCapture, api::MandateSetup, api::ConnectorAccessToken, api::PaymentToken, api::PaymentSession, api::Refund, api::RefundExecute, api::RefundSync, api::Payment, api::PaymentIncrementalAuthorization, ConnectorCommon, ConnectorValidation, IncomingWebhook, ConnectorSpecifications

pub struct Archipel

api::PaymentAuthorize api::PaymentSync api::PaymentVoid api::PaymentCapture api::MandateSetup api::ConnectorAccessToken api::PaymentToken api::PaymentSession api::Refund api::RefundExecute api::RefundSync api::Payment api::PaymentIncrementalAuthorization ConnectorCommon ConnectorValidation IncomingWebhook ConnectorSpecifications


// Struct: ErrorResult
// File: crates/api_models/src/analytics/payment_intents.rs
// Module: api_models
// Implementations: 0

pub struct ErrorResult


// Struct: CreateOrder
// File: crates/hyperswitch_domain_models/src/router_flow_types/payments.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct CreateOrder


// Implementation: impl SmtpServer
// File: crates/external_services/src/email/smtp.rs
// Module: external_services
// Methods: 3 total (1 public)

impl SmtpServer


// Implementation: impl Hash for for UserRole
// File: crates/diesel_models/src/user_role.rs
// Module: diesel_models
// Methods: 1 total (0 public)

impl Hash for for UserRole


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

impl Responder


// Function: new_token
// File: crates/router/src/services/authentication.rs
// Module: router

pub fn new_token(
        user_id: String,
        merchant_id: id_type::MerchantId,
        settings: &Settings,
        org_id: id_type::OrganizationId,
        profile_id: id_type::ProfileId,
        tenant_id: Option<id_type::TenantId>,
        role_info: authorization::roles::RoleInfo,
    ) -> UserResult<String>


// Struct: PaymentToFrmData
// File: crates/router/src/core/fraud_check/types.rs
// Module: router
// Implementations: 0

pub struct PaymentToFrmData


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

impl api::PaymentSession for for Itaubank


// Function: update_target_count
// File: crates/api_models/src/routing.rs
// Module: api_models

pub fn update_target_count(&mut self, new: &Self)


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

pub struct DeleteTokenizeByTokenRequest


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

pub struct DeutschebankReversalRequest


// File: crates/storage_impl/src/connection.rs
// Module: storage_impl
// Public functions: 3

use bb8::PooledConnection;
use common_utils::errors;
use diesel::PgConnection;
use error_stack::ResultExt;

pub type PgPool = bb8::Pool<async_bb8_diesel::ConnectionManager<PgConnection>>;

pub type PgPooledConn = async_bb8_diesel::Connection<PgConnection>;

/// Creates a Redis connection pool for the specified Redis settings
/// # Panics
///
/// Panics if failed to create a redis pool
#[allow(clippy::expect_used)]
pub async fn redis_connection(
    redis: &redis_interface::RedisSettings,
) -> redis_interface::RedisConnectionPool {
    redis_interface::RedisConnectionPool::new(redis)
        .await
        .expect("Failed to create Redis Connection Pool")
}

pub async fn pg_connection_read<T: crate::DatabaseStore>(
    store: &T,
) -> errors::CustomResult<
    PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>>,
    crate::errors::StorageError,
> {
    // If only OLAP is enabled get replica pool.
    #[cfg(all(feature = "olap", not(feature = "oltp")))]
    let pool = store.get_replica_pool();

    // If either one of these are true we need to get master pool.
    //  1. Only OLTP is enabled.
    //  2. Both OLAP and OLTP is enabled.
    //  3. Both OLAP and OLTP is disabled.
    #[cfg(any(
        all(not(feature = "olap"), feature = "oltp"),
        all(feature = "olap", feature = "oltp"),
        all(not(feature = "olap"), not(feature = "oltp"))
    ))]
    let pool = store.get_master_pool();

    pool.get()
        .await
        .change_context(crate::errors::StorageError::DatabaseConnectionError)
}

pub async fn pg_connection_write<T: crate::DatabaseStore>(
    store: &T,
) -> errors::CustomResult<
    PooledConnection<'_, async_bb8_diesel::ConnectionManager<PgConnection>>,
    crate::errors::StorageError,
> {
    // Since all writes should happen to master DB only choose master DB.
    let pool = store.get_master_pool();

    pool.get()
        .await
        .change_context(crate::errors::StorageError::DatabaseConnectionError)
}


// File: crates/diesel_models/src/address.rs
// Module: diesel_models
// Public functions: 1
// Public structs: 4

use common_utils::{crypto, encryption::Encryption};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;

use crate::{enums, schema::address};

#[derive(Clone, Debug, Insertable, Serialize, Deserialize, router_derive::DebugAsDisplay)]
#[diesel(table_name = address)]
pub struct AddressNew {
    pub address_id: String,
    pub city: Option<String>,
    pub country: Option<enums::CountryAlpha2>,
    pub line1: Option<Encryption>,
    pub line2: Option<Encryption>,
    pub line3: Option<Encryption>,
    pub state: Option<Encryption>,
    pub zip: Option<Encryption>,
    pub first_name: Option<Encryption>,
    pub last_name: Option<Encryption>,
    pub phone_number: Option<Encryption>,
    pub country_code: Option<String>,
    pub customer_id: Option<common_utils::id_type::CustomerId>,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    pub created_at: PrimitiveDateTime,
    pub modified_at: PrimitiveDateTime,
    pub updated_by: String,
    pub email: Option<Encryption>,
    pub origin_zip: Option<Encryption>,
}

#[derive(Clone, Debug, Queryable, Identifiable, Selectable, Serialize, Deserialize)]
#[diesel(table_name = address, primary_key(address_id), check_for_backend(diesel::pg::Pg))]
pub struct Address {
    pub address_id: String,
    pub city: Option<String>,
    pub country: Option<enums::CountryAlpha2>,
    pub line1: Option<Encryption>,
    pub line2: Option<Encryption>,
    pub line3: Option<Encryption>,
    pub state: Option<Encryption>,
    pub zip: Option<Encryption>,
    pub first_name: Option<Encryption>,
    pub last_name: Option<Encryption>,
    pub phone_number: Option<Encryption>,
    pub country_code: Option<String>,
    pub created_at: PrimitiveDateTime,
    pub modified_at: PrimitiveDateTime,
    pub customer_id: Option<common_utils::id_type::CustomerId>,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    pub updated_by: String,
    pub email: Option<Encryption>,
    pub origin_zip: Option<Encryption>,
}

#[derive(Clone)]
// Intermediate struct to convert HashMap to Address
pub struct EncryptableAddress {
    pub line1: crypto::OptionalEncryptableSecretString,
    pub line2: crypto::OptionalEncryptableSecretString,
    pub line3: crypto::OptionalEncryptableSecretString,
    pub state: crypto::OptionalEncryptableSecretString,
    pub zip: crypto::OptionalEncryptableSecretString,
    pub first_name: crypto::OptionalEncryptableSecretString,
    pub last_name: crypto::OptionalEncryptableSecretString,
    pub phone_number: crypto::OptionalEncryptableSecretString,
    pub email: crypto::OptionalEncryptableEmail,
    pub origin_zip: crypto::OptionalEncryptableSecretString,
}

#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay, Serialize, Deserialize)]
#[diesel(table_name = address)]
pub struct AddressUpdateInternal {
    pub city: Option<String>,
    pub country: Option<enums::CountryAlpha2>,
    pub line1: Option<Encryption>,
    pub line2: Option<Encryption>,
    pub line3: Option<Encryption>,
    pub state: Option<Encryption>,
    pub zip: Option<Encryption>,
    pub first_name: Option<Encryption>,
    pub last_name: Option<Encryption>,
    pub phone_number: Option<Encryption>,
    pub country_code: Option<String>,
    pub modified_at: PrimitiveDateTime,
    pub updated_by: String,
    pub email: Option<Encryption>,
    pub origin_zip: Option<Encryption>,
}

impl AddressUpdateInternal {
    pub fn create_address(self, source: Address) -> Address {
        Address {
            city: self.city,
            country: self.country,
            line1: self.line1,
            line2: self.line2,
            line3: self.line3,
            state: self.state,
            zip: self.zip,
            first_name: self.first_name,
            last_name: self.last_name,
            phone_number: self.phone_number,
            country_code: self.country_code,
            modified_at: self.modified_at,
            updated_by: self.updated_by,
            origin_zip: self.origin_zip,
            ..source
        }
    }
}


    }

    // Verify the signature of the token
    fn verify_signature(
        &self,
        encrypted_data: &EncryptedData,
    ) -> CustomResult<(), errors::GooglePayDecryptionError> {
        // check the protocol version
        if encrypted_data.protocol_version != GooglePayProtocolVersion::EcProtocolVersion2 {
            return Err(errors::GooglePayDecryptionError::InvalidProtocolVersion.into());
        }

        // verify the intermediate signing key
        self.verify_intermediate_signing_key(encrypted_data)?;
        // validate and fetch the signed key
        let signed_key = self.validate_signed_key(&encrypted_data.intermediate_signing_key)?;
        // verify the signature of the token
        self.verify_message_signature(encrypted_data, &signed_key)
    }

    // Verify the intermediate signing key
    fn verify_intermediate_signing_key(
        &self,
        encrypted_data: &EncryptedData,
    ) -> CustomResult<(), errors::GooglePayDecryptionError> {
        let mut signatrues: Vec<openssl::ecdsa::EcdsaSig> = Vec::new();

        // decode and parse the signatures
        for signature in encrypted_data.intermediate_signing_key.signatures.iter() {
            let signature = BASE64_ENGINE
                .decode(signature.peek())
                .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?;
            let ecdsa_signature = openssl::ecdsa::EcdsaSig::from_der(&signature)
                .change_context(errors::GooglePayDecryptionError::EcdsaSignatureParsingFailed)?;
            signatrues.push(ecdsa_signature);
        }

        // get the sender id i.e. Google
        let sender_id = String::from_utf8(consts::SENDER_ID.to_vec())
            .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?;

        // construct the signed data
        let signed_data = self.construct_signed_data_for_intermediate_signing_key_verification(
            &sender_id,
            consts::PROTOCOL,
            encrypted_data.intermediate_signing_key.signed_key.peek(),
        )?;

        // check if any of the signatures are valid for any of the root signing keys
        for key in self.root_signing_keys.iter() {
            // decode and create public key
            let public_key = self
                .load_public_key(key.key_value.peek())
                .change_context(errors::GooglePayDecryptionError::DerivingPublicKeyFailed)?;
            // fetch the ec key from public key
            let ec_key = public_key
                .ec_key()
                .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?;

            // hash the signed data
            let message_hash = openssl::sha::sha256(&signed_data);

            // verify if any of the signatures is valid against the given key
            for signature in signatrues.iter() {
                let result = signature.verify(&message_hash, &ec_key).change_context(
                    errors::GooglePayDecryptionError::SignatureVerificationFailed,
                )?;

                if result {
                    return Ok(());
                }
            }
        }

        Err(errors::GooglePayDecryptionError::InvalidIntermediateSignature.into())
    }

    // Construct signed data for intermediate signing key verification
    fn construct_signed_data_for_intermediate_signing_key_verification(
        &self,
        sender_id: &str,
        protocol_version: &str,
        signed_key: &str,
    ) -> CustomResult<Vec<u8>, errors::GooglePayDecryptionError> {
        let length_of_sender_id = u32::try_from(sender_id.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;
        let length_of_protocol_version = u32::try_from(protocol_version.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;
        let length_of_signed_key = u32::try_from(signed_key.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;

        let mut signed_data: Vec<u8> = Vec::new();
        signed_data.append(&mut get_little_endian_format(length_of_sender_id));
        signed_data.append(&mut sender_id.as_bytes().to_vec());
        signed_data.append(&mut get_little_endian_format(length_of_protocol_version));
        signed_data.append(&mut protocol_version.as_bytes().to_vec());
        signed_data.append(&mut get_little_endian_format(length_of_signed_key));
        signed_data.append(&mut signed_key.as_bytes().to_vec());

        Ok(signed_data)
    }

    // Validate and parse signed key
    fn validate_signed_key(
        &self,
        intermediate_signing_key: &IntermediateSigningKey,
    ) -> CustomResult<GooglePaySignedKey, errors::GooglePayDecryptionError> {
        let signed_key: GooglePaySignedKey = intermediate_signing_key
            .signed_key
            .clone()
            .expose()
            .parse_struct("GooglePaySignedKey")
            .change_context(errors::GooglePayDecryptionError::SignedKeyParsingFailure)?;
        if !matches!(
            check_expiration_date_is_valid(&signed_key.key_expiration),
            Ok(true)
        ) {
            return Err(errors::GooglePayDecryptionError::SignedKeyExpired)?;
        }
        Ok(signed_key)
    }

    // Verify the signed message
    fn verify_message_signature(
        &self,
        encrypted_data: &EncryptedData,
        signed_key: &GooglePaySignedKey,
    ) -> CustomResult<(), errors::GooglePayDecryptionError> {
        // create a public key from the intermediate signing key
        let public_key = self.load_public_key(signed_key.key_value.peek())?;
        // base64 decode the signature
        let signature = BASE64_ENGINE
            .decode(&encrypted_data.signature)
            .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?;

        // parse the signature using ECDSA
        let ecdsa_signature = openssl::ecdsa::EcdsaSig::from_der(&signature)
            .change_context(errors::GooglePayDecryptionError::EcdsaSignatureFailed)?;

        // get the EC key from the public key
        let ec_key = public_key
            .ec_key()
            .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?;

        // get the sender id i.e. Google
        let sender_id = String::from_utf8(consts::SENDER_ID.to_vec())
            .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?;

        // serialize the signed message to string
        let signed_message = serde_json::to_string(&encrypted_data.signed_message)
            .change_context(errors::GooglePayDecryptionError::SignedKeyParsingFailure)?;

        // construct the signed data
        let signed_data = self.construct_signed_data_for_signature_verification(
            &sender_id,
            consts::PROTOCOL,
            &signed_message,
        )?;

        // hash the signed data
        let message_hash = openssl::sha::sha256(&signed_data);

        // verify the signature
        let result = ecdsa_signature
            .verify(&message_hash, &ec_key)
            .change_context(errors::GooglePayDecryptionError::SignatureVerificationFailed)?;

        if result {
            Ok(())
        } else {
            Err(errors::GooglePayDecryptionError::InvalidSignature)?
        }
    }

    // Fetch the public key
    fn load_public_key(
        &self,
        key: &str,
    ) -> CustomResult<PKey<openssl::pkey::Public>, errors::GooglePayDecryptionError> {
        // decode the base64 string
        let der_data = BASE64_ENGINE
            .decode(key)
            .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?;

        // parse the DER-encoded data as an EC public key
        let ec_key = openssl::ec::EcKey::public_key_from_der(&der_data)
            .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?;

        // wrap the EC key in a PKey (a more general-purpose public key type in OpenSSL)
        let public_key = PKey::from_ec_key(ec_key)
            .change_context(errors::GooglePayDecryptionError::DerivingPublicKeyFailed)?;

        Ok(public_key)
    }

    // Construct signed data for signature verification
    fn construct_signed_data_for_signature_verification(
        &self,
        sender_id: &str,
        protocol_version: &str,
        signed_key: &str,
    ) -> CustomResult<Vec<u8>, errors::GooglePayDecryptionError> {
        let recipient_id = self.recipient_id.clone().expose();
        let length_of_sender_id = u32::try_from(sender_id.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;
        let length_of_recipient_id = u32::try_from(recipient_id.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;
        let length_of_protocol_version = u32::try_from(protocol_version.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;
        let length_of_signed_key = u32::try_from(signed_key.len())
            .change_context(errors::GooglePayDecryptionError::ParsingFailed)?;

        let mut signed_data: Vec<u8> = Vec::new();
        signed_data.append(&mut get_little_endian_format(length_of_sender_id));
        signed_data.append(&mut sender_id.as_bytes().to_vec());
        signed_data.append(&mut get_little_endian_format(length_of_recipient_id));
        signed_data.append(&mut recipient_id.as_bytes().to_vec());
        signed_data.append(&mut get_little_endian_format(length_of_protocol_version));
        signed_data.append(&mut protocol_version.as_bytes().to_vec());
        signed_data.append(&mut get_little_endian_format(length_of_signed_key));
        signed_data.append(&mut signed_key.as_bytes().to_vec());

        Ok(signed_data)
    }

    // Derive a shared key using ECDH
    fn get_shared_key(
        &self,
        ephemeral_public_key_bytes: &[u8],
    ) -> CustomResult<Vec<u8>, errors::GooglePayDecryptionError> {
        let group = openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1)
            .change_context(errors::GooglePayDecryptionError::DerivingEcGroupFailed)?;

        let mut big_num_context = openssl::bn::BigNumContext::new()
            .change_context(errors::GooglePayDecryptionError::BigNumAllocationFailed)?;

        let ec_key = openssl::ec::EcPoint::from_bytes(
            &group,
            ephemeral_public_key_bytes,
            &mut big_num_context,
        )
        .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?;

        // create an ephemeral public key from the given bytes
        let ephemeral_public_key = openssl::ec::EcKey::from_public_key(&group, &ec_key)
            .change_context(errors::GooglePayDecryptionError::DerivingPublicKeyFailed)?;

        // wrap the public key in a PKey
        let ephemeral_pkey = PKey::from_ec_key(ephemeral_public_key)
            .change_context(errors::GooglePayDecryptionError::DerivingPublicKeyFailed)?;

        // perform ECDH to derive the shared key
        let mut deriver = Deriver::new(&self.private_key)
            .change_context(errors::GooglePayDecryptionError::DerivingSharedSecretKeyFailed)?;

        deriver
            .set_peer(&ephemeral_pkey)
            .change_context(errors::GooglePayDecryptionError::DerivingSharedSecretKeyFailed)?;

        let shared_key = deriver
            .derive_to_vec()
            .change_context(errors::GooglePayDecryptionError::DerivingSharedSecretKeyFailed)?;

        Ok(shared_key)
    }

    // Derive symmetric key and MAC key using HKDF
    fn derive_key(
        &self,
        ephemeral_public_key_bytes: &[u8],
        shared_key: &[u8],
    ) -> CustomResult<Vec<u8>, errors::GooglePayDecryptionError> {
        // concatenate ephemeral public key and shared key
        let input_key_material = [ephemeral_public_key_bytes, shared_key].concat();

        // initialize HKDF with SHA-256 as the hash function
        // Salt is not provided as per the Google Pay documentation
        // https://developers.google.com/pay/api/android/guides/resources/payment-data-cryptography#encrypt-spec
        let hkdf: ::hkdf::Hkdf<sha2::Sha256> = ::hkdf::Hkdf::new(None, &input_key_material);

        // derive 64 bytes for the output key (symmetric encryption + MAC key)
        let mut output_key = vec![0u8; 64];
        hkdf.expand(consts::SENDER_ID, &mut output_key)
            .map_err(|err| {
                logger::error!(
                "Failed to derive the shared ephemeral key for Google Pay decryption flow: {:?}",
                err
            );
                report!(errors::GooglePayDecryptionError::DerivingSharedEphemeralKeyFailed)
            })?;

        Ok(output_key)
    }

    // Verify the Hmac key
    // https://developers.google.com/pay/api/android/guides/resources/payment-data-cryptography#encrypt-spec
    fn verify_hmac(
        &self,
        mac_key: &[u8],
        tag: &[u8],
        encrypted_message: &[u8],
    ) -> CustomResult<(), errors::GooglePayDecryptionError> {
        let hmac_key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, mac_key);
        ring::hmac::verify(&hmac_key, encrypted_message, tag)
            .change_context(errors::GooglePayDecryptionError::HmacVerificationFailed)
    }

    // Method to decrypt the AES-GCM encrypted message
    fn decrypt_message(
        &self,
        symmetric_key: &[u8],
        encrypted_message: &[u8],
    ) -> CustomResult<Vec<u8>, errors::GooglePayDecryptionError> {
        //initialization vector IV is typically used in AES-GCM (Galois/Counter Mode) encryption for randomizing the encryption process.
        // zero iv is being passed as specified in Google Pay documentation
        // https://developers.google.com/pay/api/android/guides/resources/payment-data-cryptography#decrypt-token
        let iv = [0u8; 16];

        // extract the tag from the end of the encrypted message
        let tag = encrypted_message
            .get(encrypted_message.len() - 16..)
            .ok_or(errors::GooglePayDecryptionError::ParsingTagError)?;

        // decrypt the message using AES-256-CTR
        let cipher = Cipher::aes_256_ctr();
        let decrypted_data = decrypt_aead(
            cipher,
            symmetric_key,
            Some(&iv),
            &[],
            encrypted_message,
            tag,
        )
        .change_context(errors::GooglePayDecryptionError::DecryptionFailed)?;

        Ok(decrypted_data)
    }
}

pub fn decrypt_paze_token(
    paze_wallet_data: PazeWalletData,
    paze_private_key: masking::Secret<String>,
    paze_private_key_passphrase: masking::Secret<String>,
) -> CustomResult<serde_json::Value, errors::PazeDecryptionError> {
    let decoded_paze_private_key = BASE64_ENGINE
        .decode(paze_private_key.expose().as_bytes())
        .change_context(errors::PazeDecryptionError::Base64DecodingFailed)?;
    let decrypted_private_key = openssl::rsa::Rsa::private_key_from_pem_passphrase(
        decoded_paze_private_key.as_slice(),
        paze_private_key_passphrase.expose().as_bytes(),
    )
    .change_context(errors::PazeDecryptionError::CertificateParsingFailed)?;
    let decrypted_private_key_pem = String::from_utf8(
        decrypted_private_key
            .private_key_to_pem()
            .change_context(errors::PazeDecryptionError::CertificateParsingFailed)?,
    )
    .change_context(errors::PazeDecryptionError::CertificateParsingFailed)?;
    let decrypter = jwe::RSA_OAEP_256
        .decrypter_from_pem(decrypted_private_key_pem)
        .change_context(errors::PazeDecryptionError::CertificateParsingFailed)?;

    let paze_complete_response: Vec<&str> = paze_wallet_data
        .complete_response
        .peek()
        .split('.')
        .collect();
    let encrypted_jwe_key = paze_complete_response
        .get(1)
        .ok_or(errors::PazeDecryptionError::DecryptionFailed)?
        .to_string();
    let decoded_jwe_key = base64::engine::general_purpose::URL_SAFE_NO_PAD
        .decode(encrypted_jwe_key)
        .change_context(errors::PazeDecryptionError::Base64DecodingFailed)?;
    let jws_body: JwsBody = serde_json::from_slice(&decoded_jwe_key)
        .change_context(errors::PazeDecryptionError::DecryptionFailed)?;

    let (deserialized_payload, _deserialized_header) =
        jwe::deserialize_compact(jws_body.secured_payload.peek(), &decrypter)
            .change_context(errors::PazeDecryptionError::DecryptionFailed)?;
    let encoded_secured_payload_element = String::from_utf8(deserialized_payload)
        .change_context(errors::PazeDecryptionError::DecryptionFailed)?
        .split('.')
        .collect::<Vec<&str>>()
        .get(1)
        .ok_or(errors::PazeDecryptionError::DecryptionFailed)?
        .to_string();
    let decoded_secured_payload_element = base64::engine::general_purpose::URL_SAFE_NO_PAD
        .decode(encoded_secured_payload_element)
        .change_context(errors::PazeDecryptionError::Base64DecodingFailed)?;
    let parsed_decrypted: serde_json::Value =
        serde_json::from_slice(&decoded_secured_payload_element)
            .change_context(errors::PazeDecryptionError::DecryptionFailed)?;
    Ok(parsed_decrypted)
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JwsBody {
    pub payload_id: String,
    pub session_id: String,
    pub secured_payload: masking::Secret<String>,
}

pub fn get_key_params_for_surcharge_details(
    payment_method_data: &domain::PaymentMethodData,
) -> Option<(
    common_enums::PaymentMethod,
    common_enums::PaymentMethodType,
    Option<common_enums::CardNetwork>,
)> {
    match payment_method_data {
        domain::PaymentMethodData::Card(card) => {
            // surcharge generated will always be same for credit as well as debit
            // since surcharge conditions cannot be defined on card_type
            Some((
                common_enums::PaymentMethod::Card,
                common_enums::PaymentMethodType::Credit,
                card.card_network.clone(),
            ))
        }
        domain::PaymentMethodData::CardRedirect(card_redirect_data) => Some((
            common_enums::PaymentMethod::CardRedirect,
            card_redirect_data.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::Wallet(wallet) => Some((
            common_enums::PaymentMethod::Wallet,
            wallet.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::PayLater(pay_later) => Some((
            common_enums::PaymentMethod::PayLater,
            pay_later.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::BankRedirect(bank_redirect) => Some((
            common_enums::PaymentMethod::BankRedirect,
            bank_redirect.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::BankDebit(bank_debit) => Some((
            common_enums::PaymentMethod::BankDebit,
            bank_debit.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::BankTransfer(bank_transfer) => Some((
            common_enums::PaymentMethod::BankTransfer,
            bank_transfer.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::Crypto(crypto) => Some((
            common_enums::PaymentMethod::Crypto,
            crypto.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::MandatePayment => None,
        domain::PaymentMethodData::Reward => None,
        domain::PaymentMethodData::RealTimePayment(real_time_payment) => Some((
            common_enums::PaymentMethod::RealTimePayment,
            real_time_payment.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::Upi(upi_data) => Some((
            common_enums::PaymentMethod::Upi,
            upi_data.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::Voucher(voucher) => Some((
            common_enums::PaymentMethod::Voucher,
            voucher.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::GiftCard(gift_card) => Some((
            common_enums::PaymentMethod::GiftCard,
            gift_card.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::OpenBanking(ob_data) => Some((
            common_enums::PaymentMethod::OpenBanking,
            ob_data.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::MobilePayment(mobile_payment) => Some((
            common_enums::PaymentMethod::MobilePayment,
            mobile_payment.get_payment_method_type(),
            None,
        )),
        domain::PaymentMethodData::CardToken(_)
        | domain::PaymentMethodData::NetworkToken(_)
        | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => None,
    }
}

pub fn validate_payment_link_request(
    request: &api::PaymentsRequest,
) -> Result<(), errors::ApiErrorResponse> {
    #[cfg(feature = "v1")]
    if request.confirm == Some(true) {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "cannot confirm a payment while creating a payment link".to_string(),
        });
    }

    if request.return_url.is_none() {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "return_url must be sent while creating a payment link".to_string(),
        });
    }
    Ok(())
}

pub async fn get_gsm_record(
    state: &SessionState,
    error_code: Option<String>,
    error_message: Option<String>,
    connector_name: String,
    flow: String,
) -> Option<hyperswitch_domain_models::gsm::GatewayStatusMap> {
    let get_gsm = || async {
        state.store.find_gsm_rule(
                connector_name.clone(),
                flow.clone(),
                "sub_flow".to_string(),
                error_code.clone().unwrap_or_default(), // TODO: make changes in connector to get a mandatory code in case of success or error response
                error_message.clone().unwrap_or_default(),
            )
            .await
            .map_err(|err| {
                if err.current_context().is_db_not_found() {
                    logger::warn!(
                        "GSM miss for connector - {}, flow - {}, error_code - {:?}, error_message - {:?}",
                        connector_name,
                        flow,
                        error_code,
                        error_message
                    );
                    metrics::AUTO_RETRY_GSM_MISS_COUNT.add( 1, &[]);
                } else {
                    metrics::AUTO_RETRY_GSM_FETCH_FAILURE_COUNT.add( 1, &[]);
                };
                err.change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("failed to fetch decision from gsm")
            })
    };
    get_gsm()
        .await
        .inspect_err(|err| {
            // warn log should suffice here because we are not propagating this error
            logger::warn!(get_gsm_decision_fetch_error=?err, "error fetching gsm decision");
        })
        .ok()
}

pub async fn get_unified_translation(
    state: &SessionState,
    unified_code: String,
    unified_message: String,
    locale: String,
) -> Option<String> {
    let get_unified_translation = || async {
        state.store.find_translation(
                unified_code.clone(),
                unified_message.clone(),
                locale.clone(),
            )
            .await
            .map_err(|err| {
                if err.current_context().is_db_not_found() {
                    logger::warn!(
                        "Translation missing for unified_code - {:?}, unified_message - {:?}, locale - {:?}",
                        unified_code,
                        unified_message,
                        locale
                    );
                }
                err.change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("failed to fetch translation from unified_translations")
            })
    };
    get_unified_translation()
        .await
        .inspect_err(|err| {
            // warn log should suffice here because we are not propagating this error
            logger::warn!(get_translation_error=?err, "error fetching unified translations");
        })
        .ok()
}
pub fn validate_order_details_amount(
    order_details: Vec<api_models::payments::OrderDetailsWithAmount>,
    amount: MinorUnit,
    should_validate: bool,
) -> Result<(), errors::ApiErrorResponse> {
    if should_validate {
        let total_order_details_amount: MinorUnit = order_details
            .iter()
            .map(|order| order.amount * order.quantity)
            .sum();

        if total_order_details_amount != amount {
            Err(errors::ApiErrorResponse::InvalidRequestData {
                message: "Total sum of order details doesn't match amount in payment request"
                    .to_string(),
            })
        } else {
            Ok(())
        }
    } else {
        Ok(())
    }
}

// This function validates the client secret expiry set by the merchant in the request
pub fn validate_session_expiry(session_expiry: u32) -> Result<(), errors::ApiErrorResponse> {
    if !(consts::MIN_SESSION_EXPIRY..=consts::MAX_SESSION_EXPIRY).contains(&session_expiry) {
        Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "session_expiry should be between 60(1 min) to 7890000(3 months).".to_string(),
        })
    } else {
        Ok(())
    }
}

pub fn get_recipient_id_for_open_banking(
    merchant_data: &AdditionalMerchantData,
) -> Result<Option<String>, errors::ApiErrorResponse> {
    match merchant_data {
        AdditionalMerchantData::OpenBankingRecipientData(data) => match data {
            MerchantRecipientData::ConnectorRecipientId(id) => Ok(Some(id.peek().clone())),
            MerchantRecipientData::AccountData(acc_data) => {
                let connector_recipient_id = match acc_data {
                    MerchantAccountData::Bacs {
                        connector_recipient_id,
                        ..
                    }
                    | MerchantAccountData::Iban {
                        connector_recipient_id,
                        ..
                    }
                    | MerchantAccountData::FasterPayments {
                        connector_recipient_id,
                        ..
                    }
                    | MerchantAccountData::Sepa {
                        connector_recipient_id,
                        ..
                    }
                    | MerchantAccountData::SepaInstant {
                        connector_recipient_id,
                        ..
                    }
                    | MerchantAccountData::Elixir {
                        connector_recipient_id,
                        ..
                    }
                    | MerchantAccountData::Bankgiro {
                        connector_recipient_id,
                        ..
                    }
                    | MerchantAccountData::Plusgiro {
                        connector_recipient_id,
                        ..
                    } => connector_recipient_id,
                };

                match connector_recipient_id {
                    Some(RecipientIdType::ConnectorId(id)) => Ok(Some(id.peek().clone())),
                    Some(RecipientIdType::LockerId(id)) => Ok(Some(id.peek().clone())),
                    _ => Err(errors::ApiErrorResponse::InvalidConnectorConfiguration {
                        config: "recipient_id".to_string(),
                    }),
                }
            }
            _ => Err(errors::ApiErrorResponse::InvalidConnectorConfiguration {
                config: "recipient_id".to_string(),
            }),
        },
    }
}
// This function validates the intent fulfillment time expiry set by the merchant in the request
pub fn validate_intent_fulfillment_expiry(
    intent_fulfillment_time: u32,
) -> Result<(), errors::ApiErrorResponse> {
    if !(consts::MIN_INTENT_FULFILLMENT_EXPIRY..=consts::MAX_INTENT_FULFILLMENT_EXPIRY)
        .contains(&intent_fulfillment_time)
    {
        Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "intent_fulfillment_time should be between 60(1 min) to 1800(30 mins)."
                .to_string(),
        })
    } else {
        Ok(())
    }
}

pub fn add_connector_response_to_additional_payment_data(
    additional_payment_data: api_models::payments::AdditionalPaymentData,
    connector_response_payment_method_data: AdditionalPaymentMethodConnectorResponse,
) -> api_models::payments::AdditionalPaymentData {
    match (
        &additional_payment_data,
        connector_response_payment_method_data,
    ) {
        (
            api_models::payments::AdditionalPaymentData::Card(additional_card_data),
            AdditionalPaymentMethodConnectorResponse::Card {
                authentication_data,
                payment_checks,
                ..
            },
        ) => api_models::payments::AdditionalPaymentData::Card(Box::new(
            api_models::payments::AdditionalCardInfo {
                payment_checks,
                authentication_data,
                ..*additional_card_data.clone()
            },
        )),
        (
            api_models::payments::AdditionalPaymentData::PayLater { .. },
            AdditionalPaymentMethodConnectorResponse::PayLater {
                klarna_sdk: Some(KlarnaSdkResponse { payment_type }),
            },
        ) => api_models::payments::AdditionalPaymentData::PayLater {
            klarna_sdk: Some(api_models::payments::KlarnaSdkPaymentMethod { payment_type }),
        },

        _ => additional_payment_data,
    }
}

pub fn update_additional_payment_data_with_connector_response_pm_data(
    additional_payment_data: Option<serde_json::Value>,
    connector_response_pm_data: Option<AdditionalPaymentMethodConnectorResponse>,
) -> RouterResult<Option<serde_json::Value>> {
    let parsed_additional_payment_method_data = additional_payment_data
        .as_ref()
        .map(|payment_method_data| {
            payment_method_data
                .clone()
                .parse_value::<api_models::payments::AdditionalPaymentData>(
                    "additional_payment_method_data",
                )
        })
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("unable to parse value into additional_payment_method_data")?;

    let additional_payment_method_data = parsed_additional_payment_method_data
        .zip(connector_response_pm_data)
        .map(|(additional_pm_data, connector_response_pm_data)| {
            add_connector_response_to_additional_payment_data(
                additional_pm_data,
                connector_response_pm_data,
            )
        });

    additional_payment_method_data
        .as_ref()
        .map(Encode::encode_to_value)
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to encode additional pm data")
}

#[cfg(feature = "v2")]
pub async fn get_payment_method_details_from_payment_token(
    state: &SessionState,
    payment_attempt: &PaymentAttempt,
    payment_intent: &PaymentIntent,
    key_store: &domain::MerchantKeyStore,
    storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<Option<(domain::PaymentMethodData, enums::PaymentMethod)>> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn get_payment_method_details_from_payment_token(
    state: &SessionState,
    payment_attempt: &PaymentAttempt,
    payment_intent: &PaymentIntent,
    key_store: &domain::MerchantKeyStore,
    storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<Option<(domain::PaymentMethodData, enums::PaymentMethod)>> {
    let hyperswitch_token = if let Some(token) = payment_attempt.payment_token.clone() {
        let redis_conn = state
            .store
            .get_redis_conn()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to get redis connection")?;
        let key = format!(
            "pm_token_{}_{}_hyperswitch",
            token,
            payment_attempt
                .payment_method
                .to_owned()
                .get_required_value("payment_method")?,
        );
        let token_data_string = redis_conn
            .get_key::<Option<String>>(&key.into())
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to fetch the token from redis")?
            .ok_or(error_stack::Report::new(
                errors::ApiErrorResponse::UnprocessableEntity {
                    message: "Token is invalid or expired".to_owned(),
                },
            ))?;
        let token_data_result = token_data_string
            .clone()
            .parse_struct("PaymentTokenData")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("failed to deserialize hyperswitch token data");
        let token_data = match token_data_result {
            Ok(data) => data,
            Err(e) => {
                // The purpose of this logic is backwards compatibility to support tokens
                // in redis that might be following the old format.
                if token_data_string.starts_with('{') {
                    return Err(e);
                } else {
                    storage::PaymentTokenData::temporary_generic(token_data_string)
                }
            }
        };
        Some(token_data)
    } else {
        None
    };
    let token = hyperswitch_token
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("missing hyperswitch_token")?;
    match token {
        storage::PaymentTokenData::TemporaryGeneric(generic_token) => {
            retrieve_payment_method_with_temporary_token(
                state,
                &generic_token.token,
                payment_intent,
                payment_attempt,
                key_store,
                None,
            )
            .await
        }

        storage::PaymentTokenData::Temporary(generic_token) => {
            retrieve_payment_method_with_temporary_token(
                state,
                &generic_token.token,
                payment_intent,
                payment_attempt,
                key_store,
                None,
            )
            .await
        }

        storage::PaymentTokenData::Permanent(card_token) => {
            retrieve_card_with_permanent_token_for_external_authentication(
                state,
                &card_token.token,
                payment_intent,
                None,
                key_store,
                storage_scheme,
            )
            .await
            .map(|card| Some((card, enums::PaymentMethod::Card)))
        }

        storage::PaymentTokenData::PermanentCard(card_token) => {
            retrieve_card_with_permanent_token_for_external_authentication(
                state,
                &card_token.token,
                payment_intent,
                None,
                key_store,
                storage_scheme,
            )
            .await
            .map(|card| Some((card, enums::PaymentMethod::Card)))
        }

        storage::PaymentTokenData::AuthBankDebit(auth_token) => {
            retrieve_payment_method_from_auth_service(
                state,
                key_store,
                &auth_token,
                payment_intent,
                &None,
            )
            .await
        }

        storage::PaymentTokenData::WalletToken(_) => Ok(None),
    }
}

// This function validates the  mandate_data with its setup_future_usage
pub fn validate_mandate_data_and_future_usage(
    setup_future_usages: Option<api_enums::FutureUsage>,
    mandate_details_present: bool,
) -> Result<(), errors::ApiErrorResponse> {
    if mandate_details_present
        && (Some(api_enums::FutureUsage::OnSession) == setup_future_usages
            || setup_future_usages.is_none())
    {
        Err(errors::ApiErrorResponse::PreconditionFailed {
            message: "`setup_future_usage` must be `off_session` for mandates".into(),
        })
    } else {
        Ok(())
    }
}

#[derive(Debug, Clone)]
pub enum UnifiedAuthenticationServiceFlow {
    ClickToPayInitiate,
    ExternalAuthenticationInitiate {
        acquirer_details: Option<authentication::types::AcquirerDetails>,
        card: Box<hyperswitch_domain_models::payment_method_data::Card>,
        token: String,
    },
    ExternalAuthenticationPostAuthenticate {
        authentication_id: id_type::AuthenticationId,
    },
}

#[cfg(feature = "v1")]
pub async fn decide_action_for_unified_authentication_service<F: Clone>(
    state: &SessionState,
    key_store: &domain::MerchantKeyStore,
    business_profile: &domain::Profile,
    payment_data: &mut PaymentData<F>,
    connector_call_type: &api::ConnectorCallType,
    mandate_type: Option<api_models::payments::MandateTransactionType>,
) -> RouterResult<Option<UnifiedAuthenticationServiceFlow>> {
    let external_authentication_flow = get_payment_external_authentication_flow_during_confirm(
        state,
        key_store,
        business_profile,
        payment_data,
        connector_call_type,
        mandate_type,
    )
    .await?;
    Ok(match external_authentication_flow {
        Some(PaymentExternalAuthenticationFlow::PreAuthenticationFlow {
            acquirer_details,
            card,
            token,
        }) => Some(
            UnifiedAuthenticationServiceFlow::ExternalAuthenticationInitiate {
                acquirer_details,
                card,
                token,
            },
        ),
        Some(PaymentExternalAuthenticationFlow::PostAuthenticationFlow { authentication_id }) => {
            Some(
                UnifiedAuthenticationServiceFlow::ExternalAuthenticationPostAuthenticate {
                    authentication_id,
                },
            )
        }
        None => {
            if let Some(payment_method) = payment_data.payment_attempt.payment_method {
                if payment_method == storage_enums::PaymentMethod::Card
                    && business_profile.is_click_to_pay_enabled
                    && payment_data.service_details.is_some()
                {
                    Some(UnifiedAuthenticationServiceFlow::ClickToPayInitiate)
                } else {
                    None
                }
            } else {
                logger::info!(
                    payment_method=?payment_data.payment_attempt.payment_method,
                    click_to_pay_enabled=?business_profile.is_click_to_pay_enabled,
                    "skipping unified authentication service call since payment conditions are not satisfied"
                );
                None
            }
        }
    })
}

pub enum PaymentExternalAuthenticationFlow {
    PreAuthenticationFlow {
        acquirer_details: Option<authentication::types::AcquirerDetails>,
        card: Box<hyperswitch_domain_models::payment_method_data::Card>,
        token: String,
    },


// Implementation: impl FilteredPaymentMethodsEnabled
// File: crates/router/src/core/payments/payment_methods.rs
// Module: router
// Methods: 2 total (0 public)

impl FilteredPaymentMethodsEnabled


// Function: fetch_routing_algo
// File: crates/router/src/core/routing.rs
// Module: router

pub fn fetch_routing_algo(
        merchant_id: &common_utils::id_type::MerchantId,
        algorithm_id: &common_utils::id_type::RoutingId,
        db: &dyn StorageInterface,
    ) -> RouterResult<Self>


// Struct: OrganizationId
// File: crates/api_models/src/organization.rs
// Module: api_models
// Implementations: 0

pub struct OrganizationId


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

impl ConnectorValidation for for Taxjar