use std::collections::HashMap;

use common_enums::PaymentMethodType;
#[cfg(any(feature = "v1", feature = "v2"))]
use common_utils::encryption::Encryption;
use common_utils::{
    crypto::{DecodeMessage, EncodeMessage, GcmAes256},
    ext_traits::{BytesExt, Encode},
    generate_id_with_default_len, id_type,
    pii::Email,
    request,
};
use error_stack::{report, ResultExt};
#[cfg(feature = "v2")]
use hyperswitch_domain_models::router_flow_types::{
    ExternalVaultDeleteFlow, ExternalVaultRetrieveFlow,
};
use hyperswitch_domain_models::{
    router_data_v2::flow_common_types::VaultConnectorFlowData, types::VaultRouterData,
};
use hyperswitch_masking::PeekInterface;
#[cfg(feature = "v2")]
use payment_methods::controller::DeleteCardResp;
use router_env::{instrument, tracing};
use scheduler::{types::process_data, utils as process_tracker_utils};

#[cfg(feature = "payouts")]
use crate::types::api::payouts;
use crate::{
    consts,
    core::{
        errors::{self, ConnectorErrorExt, CustomResult, RouterResult},
        payment_methods::{access_token, transformers as pm_transforms},
        payments, utils as core_utils,
    },
    db, headers, logger,
    routes::{self, metrics},
    services::{self, connector_integration_interface::RouterDataConversion},
    settings,
    types::{
        self, api, domain, payment_methods as pm_types,
        storage::{self, enums},
    },
    utils::{ConnectorResponseExt, StringExt},
};
// `pm_cards` and `OptionExt` are imported for v2 via the larger v2-only `use` block below; the v1
// build needs them for `retrieve_and_delete_cvc_from_payment_token` (self-hosted vault CVC retrieval).
#[cfg(feature = "v1")]
use crate::{core::payment_methods::cards as pm_cards, utils::ext_traits::OptionExt};
#[cfg(feature = "v2")]
use crate::{
    core::{
        configs::dimension_state,
        errors::StorageErrorExt,
        payment_methods::{
            cards as pm_cards, utils, utils as payment_method_utils, LockerOperations, LockerType,
        },
        payments::{self as payments_core, helpers as payment_helpers},
        utils::create_encrypted_data,
    },
    utils::ext_traits::OptionExt,
};

const VAULT_SERVICE_NAME: &str = "CARD";

pub struct SupplementaryVaultData {
    pub customer_id: Option<id_type::CustomerId>,
    pub payment_method_id: Option<String>,
}

pub trait Vaultable: Sized {
    fn get_value1(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError>;
    fn get_value2(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        Ok(String::new())
    }
    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError>;
}

impl Vaultable for domain::Card {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = domain::TokenizedCardValue1 {
            card_number: self.card_number.peek().clone(),
            exp_year: self.card_exp_year.peek().clone(),
            exp_month: self.card_exp_month.peek().clone(),
            nickname: self.nick_name.as_ref().map(|name| name.peek().clone()),
            card_last_four: None,
            card_token: None,
            card_holder_name: self.card_holder_name.clone(),
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode card value1")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = domain::TokenizedCardValue2 {
            card_security_code: Some(self.card_cvc.peek().clone()),
            card_fingerprint: None,
            external_id: None,
            customer_id,
            payment_method_id: None,
        };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode card value2")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: domain::TokenizedCardValue1 = value1
            .parse_struct("TokenizedCardValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into card value1")?;

        let value2: domain::TokenizedCardValue2 = value2
            .parse_struct("TokenizedCardValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into card value2")?;

        let card = Self {
            card_number: cards::CardNumber::try_from(value1.card_number)
                .change_context(errors::VaultError::ResponseDeserializationFailed)
                .attach_printable("Invalid card number format from the mock locker")?,
            card_exp_month: value1.exp_month.into(),
            card_exp_year: value1.exp_year.into(),
            card_cvc: value2.card_security_code.unwrap_or_default().into(),
            card_issuer: None,
            card_network: None,
            bank_code: None,
            card_issuing_country: None,
            card_issuing_country_code: None,
            card_type: None,
            nick_name: value1.nickname.map(hyperswitch_masking::Secret::new),
            card_holder_name: value1.card_holder_name,
            co_badged_card_data: None,
        };

        let supp_data = SupplementaryVaultData {
            customer_id: value2.customer_id,
            payment_method_id: value2.payment_method_id,
        };

        Ok((card, supp_data))
    }
}

impl Vaultable for domain::BankTransferData {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = domain::TokenizedBankTransferValue1 {
            data: self.to_owned(),
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode bank transfer data")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = domain::TokenizedBankTransferValue2 { customer_id };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode bank transfer supplementary data")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: domain::TokenizedBankTransferValue1 = value1
            .parse_struct("TokenizedBankTransferValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into bank transfer data")?;

        let value2: domain::TokenizedBankTransferValue2 = value2
            .parse_struct("TokenizedBankTransferValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into supplementary bank transfer data")?;

        let bank_transfer_data = value1.data;

        let supp_data = SupplementaryVaultData {
            customer_id: value2.customer_id,
            payment_method_id: None,
        };

        Ok((bank_transfer_data, supp_data))
    }
}

impl Vaultable for domain::WalletData {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = domain::TokenizedWalletValue1 {
            data: self.to_owned(),
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode wallet data value1")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = domain::TokenizedWalletValue2 { customer_id };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode wallet data value2")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: domain::TokenizedWalletValue1 = value1
            .parse_struct("TokenizedWalletValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into wallet data value1")?;

        let value2: domain::TokenizedWalletValue2 = value2
            .parse_struct("TokenizedWalletValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into wallet data value2")?;

        let wallet = value1.data;

        let supp_data = SupplementaryVaultData {
            customer_id: value2.customer_id,
            payment_method_id: None,
        };

        Ok((wallet, supp_data))
    }
}

impl Vaultable for domain::BankRedirectData {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = domain::TokenizedBankRedirectValue1 {
            data: self.to_owned(),
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode bank redirect data")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = domain::TokenizedBankRedirectValue2 { customer_id };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode bank redirect supplementary data")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: domain::TokenizedBankRedirectValue1 = value1
            .parse_struct("TokenizedBankRedirectValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into bank redirect data")?;

        let value2: domain::TokenizedBankRedirectValue2 = value2
            .parse_struct("TokenizedBankRedirectValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into supplementary bank redirect data")?;

        let bank_transfer_data = value1.data;

        let supp_data = SupplementaryVaultData {
            customer_id: value2.customer_id,
            payment_method_id: None,
        };

        Ok((bank_transfer_data, supp_data))
    }
}

impl Vaultable for domain::BankDebitData {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = domain::TokenizedBankDebitValue1 {
            data: self.to_owned(),
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode bank debit data")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = domain::TokenizedBankDebitValue2 { customer_id };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode bank debit supplementary data")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: domain::TokenizedBankDebitValue1 = value1
            .parse_struct("TokenizedBankDebitValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into bank debit data")?;

        let value2: domain::TokenizedBankDebitValue2 = value2
            .parse_struct("TokenizedBankDebitValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into supplementary bank debit data")?;

        let bank_transfer_data = value1.data;

        let supp_data = SupplementaryVaultData {
            customer_id: value2.customer_id,
            payment_method_id: None,
        };

        Ok((bank_transfer_data, supp_data))
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
pub enum VaultPaymentMethod {
    Card(String),
    Wallet(String),
    BankTransfer(String),
    BankRedirect(String),
    BankDebit(String),
}

impl Vaultable for domain::PaymentMethodData {
    fn get_value1(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = match self {
            Self::Card(card) => VaultPaymentMethod::Card(card.get_value1(customer_id)?),
            Self::Wallet(wallet) => VaultPaymentMethod::Wallet(wallet.get_value1(customer_id)?),
            Self::BankTransfer(bank_transfer) => {
                VaultPaymentMethod::BankTransfer(bank_transfer.get_value1(customer_id)?)
            }
            Self::BankRedirect(bank_redirect) => {
                VaultPaymentMethod::BankRedirect(bank_redirect.get_value1(customer_id)?)
            }
            Self::BankDebit(bank_debit) => {
                VaultPaymentMethod::BankDebit(bank_debit.get_value1(customer_id)?)
            }
            _ => Err(errors::VaultError::PaymentMethodNotSupported)
                .attach_printable("Payment method not supported")?,
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode payment method value1")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = match self {
            Self::Card(card) => VaultPaymentMethod::Card(card.get_value2(customer_id)?),
            Self::Wallet(wallet) => VaultPaymentMethod::Wallet(wallet.get_value2(customer_id)?),
            Self::BankTransfer(bank_transfer) => {
                VaultPaymentMethod::BankTransfer(bank_transfer.get_value2(customer_id)?)
            }
            Self::BankRedirect(bank_redirect) => {
                VaultPaymentMethod::BankRedirect(bank_redirect.get_value2(customer_id)?)
            }
            Self::BankDebit(bank_debit) => {
                VaultPaymentMethod::BankDebit(bank_debit.get_value2(customer_id)?)
            }
            _ => Err(errors::VaultError::PaymentMethodNotSupported)
                .attach_printable("Payment method not supported")?,
        };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode payment method value2")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: VaultPaymentMethod = value1
            .parse_struct("PaymentMethodValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into payment method value 1")?;

        let value2: VaultPaymentMethod = value2
            .parse_struct("PaymentMethodValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into payment method value 2")?;

        match (value1, value2) {
            (VaultPaymentMethod::Card(mvalue1), VaultPaymentMethod::Card(mvalue2)) => {
                let (card, supp_data) = domain::Card::from_values(mvalue1, mvalue2)?;
                Ok((Self::Card(card), supp_data))
            }
            (VaultPaymentMethod::Wallet(mvalue1), VaultPaymentMethod::Wallet(mvalue2)) => {
                let (wallet, supp_data) = domain::WalletData::from_values(mvalue1, mvalue2)?;
                Ok((Self::Wallet(wallet), supp_data))
            }
            (
                VaultPaymentMethod::BankTransfer(mvalue1),
                VaultPaymentMethod::BankTransfer(mvalue2),
            ) => {
                let (bank_transfer, supp_data) =
                    domain::BankTransferData::from_values(mvalue1, mvalue2)?;
                Ok((Self::BankTransfer(Box::new(bank_transfer)), supp_data))
            }
            (
                VaultPaymentMethod::BankRedirect(mvalue1),
                VaultPaymentMethod::BankRedirect(mvalue2),
            ) => {
                let (bank_redirect, supp_data) =
                    domain::BankRedirectData::from_values(mvalue1, mvalue2)?;
                Ok((Self::BankRedirect(bank_redirect), supp_data))
            }
            (VaultPaymentMethod::BankDebit(mvalue1), VaultPaymentMethod::BankDebit(mvalue2)) => {
                let (bank_debit, supp_data) = domain::BankDebitData::from_values(mvalue1, mvalue2)?;
                Ok((Self::BankDebit(bank_debit), supp_data))
            }

            _ => Err(errors::VaultError::PaymentMethodNotSupported)
                .attach_printable("Payment method not supported"),
        }
    }
}

#[cfg(feature = "payouts")]
impl Vaultable for api::CardPayout {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = api::TokenizedCardValue1 {
            card_number: self.card_number.peek().clone(),
            exp_year: self.expiry_year.peek().clone(),
            exp_month: self.expiry_month.peek().clone(),
            name_on_card: self.card_holder_name.clone().map(|n| n.peek().to_string()),
            nickname: None,
            card_last_four: None,
            card_token: None,
            card_network: self.card_network.clone(),
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode card value1")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = api::TokenizedCardValue2 {
            card_security_code: None,
            card_fingerprint: None,
            external_id: None,
            customer_id,
            payment_method_id: None,
        };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode card value2")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: api::TokenizedCardValue1 = value1
            .parse_struct("TokenizedCardValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into card value1")?;

        let value2: api::TokenizedCardValue2 = value2
            .parse_struct("TokenizedCardValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into card value2")?;

        let card = Self {
            card_number: value1
                .card_number
                .parse()
                .map_err(|_| errors::VaultError::FetchCardFailed)?,
            expiry_month: value1.exp_month.into(),
            expiry_year: value1.exp_year.into(),
            card_holder_name: value1.name_on_card.map(hyperswitch_masking::Secret::new),
            card_network: value1.card_network,
        };

        let supp_data = SupplementaryVaultData {
            customer_id: value2.customer_id,
            payment_method_id: value2.payment_method_id,
        };

        Ok((card, supp_data))
    }
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedWalletSensitiveValues {
    pub email: Option<Email>,
    pub telephone_number: Option<hyperswitch_masking::Secret<String>>,
    pub wallet_id: Option<hyperswitch_masking::Secret<String>>,
    pub wallet_type: PaymentMethodType,
    pub dpan: Option<cards::CardNumber>,
    pub expiry_month: Option<hyperswitch_masking::Secret<String>>,
    pub expiry_year: Option<hyperswitch_masking::Secret<String>>,
    pub card_holder_name: Option<hyperswitch_masking::Secret<String>>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedWalletInsensitiveValues {
    pub customer_id: Option<id_type::CustomerId>,
    pub card_network: Option<common_enums::CardNetwork>,
}

#[cfg(feature = "payouts")]
impl Vaultable for api::WalletPayout {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = match self {
            Self::Paypal(paypal_data) => TokenizedWalletSensitiveValues {
                email: paypal_data.email.clone(),
                telephone_number: paypal_data.telephone_number.clone(),
                wallet_id: paypal_data.paypal_id.clone(),
                wallet_type: PaymentMethodType::Paypal,
                dpan: None,
                expiry_month: None,
                expiry_year: None,
                card_holder_name: None,
            },
            Self::Venmo(venmo_data) => TokenizedWalletSensitiveValues {
                email: None,
                telephone_number: venmo_data.telephone_number.clone(),
                wallet_id: None,
                wallet_type: PaymentMethodType::Venmo,
                dpan: None,
                expiry_month: None,
                expiry_year: None,
                card_holder_name: None,
            },
            Self::ApplePayDecrypt(apple_pay_decrypt_data) => TokenizedWalletSensitiveValues {
                email: None,
                telephone_number: None,
                wallet_id: None,
                wallet_type: PaymentMethodType::ApplePay,
                dpan: Some(apple_pay_decrypt_data.dpan.clone()),
                expiry_month: Some(apple_pay_decrypt_data.expiry_month.clone()),
                expiry_year: Some(apple_pay_decrypt_data.expiry_year.clone()),
                card_holder_name: apple_pay_decrypt_data.card_holder_name.clone(),
            },
            Self::GooglePayDecrypt(google_pay_decrypt_data) => TokenizedWalletSensitiveValues {
                email: None,
                telephone_number: None,
                wallet_id: None,
                wallet_type: PaymentMethodType::GooglePay,
                dpan: Some(
                    google_pay_decrypt_data
                        .application_primary_account_number
                        .clone(),
                ),
                expiry_month: Some(google_pay_decrypt_data.expiry_month.clone()),
                expiry_year: Some(google_pay_decrypt_data.expiry_year.clone()),
                card_holder_name: google_pay_decrypt_data.card_holder_name.clone(),
            },
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode wallet data - TokenizedWalletSensitiveValues")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = match self {
            Self::Paypal(_paypal_data) => TokenizedWalletInsensitiveValues {
                customer_id,
                card_network: None,
            },
            Self::Venmo(_venmo_data) => TokenizedWalletInsensitiveValues {
                customer_id,
                card_network: None,
            },
            Self::ApplePayDecrypt(apple_pay_decrypt_data) => TokenizedWalletInsensitiveValues {
                customer_id,
                card_network: apple_pay_decrypt_data.card_network.clone(),
            },
            Self::GooglePayDecrypt(google_pay_decrypt_data) => TokenizedWalletInsensitiveValues {
                customer_id,
                card_network: google_pay_decrypt_data.card_network.clone(),
            },
        };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode data - TokenizedWalletInsensitiveValues")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: TokenizedWalletSensitiveValues = value1
            .parse_struct("TokenizedWalletSensitiveValues")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into wallet data wallet_sensitive_data")?;

        let value2: TokenizedWalletInsensitiveValues = value2
            .parse_struct("TokenizedWalletInsensitiveValues")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into wallet data wallet_insensitive_data")?;

        let wallet = match value1.wallet_type {
            PaymentMethodType::Paypal => Self::Paypal(api_models::payouts::Paypal {
                email: value1.email,
                telephone_number: value1.telephone_number,
                paypal_id: value1.wallet_id,
            }),
            PaymentMethodType::Venmo => Self::Venmo(api_models::payouts::Venmo {
                telephone_number: value1.telephone_number,
            }),
            PaymentMethodType::ApplePay => {
                match (value1.dpan, value1.expiry_month, value1.expiry_year) {
                    (Some(dpan), Some(expiry_month), Some(expiry_year)) => {
                        Self::ApplePayDecrypt(api_models::payouts::ApplePayDecrypt {
                            dpan,
                            expiry_month,
                            expiry_year,
                            card_holder_name: value1.card_holder_name,
                            card_network: value2.card_network,
                        })
                    }
                    _ => Err(errors::VaultError::ResponseDeserializationFailed)?,
                }
            }
            _ => Err(errors::VaultError::PayoutMethodNotSupported)?,
        };
        let supp_data = SupplementaryVaultData {
            customer_id: value2.customer_id,
            payment_method_id: None,
        };

        Ok((wallet, supp_data))
    }
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedBankSensitiveValues {
    pub bank_account_number: Option<hyperswitch_masking::Secret<String>>,
    pub bank_routing_number: Option<hyperswitch_masking::Secret<String>>,
    pub bic: Option<hyperswitch_masking::Secret<String>>,
    pub bank_sort_code: Option<hyperswitch_masking::Secret<String>>,
    pub iban: Option<hyperswitch_masking::Secret<String>>,
    pub pix_key: Option<hyperswitch_masking::Secret<String>>,
    pub emv: Option<hyperswitch_masking::Secret<String>>,
    pub tax_id: Option<hyperswitch_masking::Secret<String>>,
    pub bank_number: Option<hyperswitch_masking::Secret<String>>,
    pub account_holder_name: Option<hyperswitch_masking::Secret<String>>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedBankInsensitiveValues {
    pub customer_id: Option<id_type::CustomerId>,
    pub bank_name: Option<String>,
    pub bank_country_code: Option<api::enums::CountryAlpha2>,
    pub bank_city: Option<String>,
    pub bank_branch: Option<String>,
    pub ispb: Option<String>,
    pub bank_code: Option<String>,
    pub bank_account_type: Option<common_enums::BankType>,
    pub payout_method_type: Option<PaymentMethodType>,
}

#[cfg(feature = "payouts")]
impl Vaultable for api::BankPayout {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let bank_sensitive_data = match self {
            Self::Ach(b) => TokenizedBankSensitiveValues {
                bank_account_number: Some(b.bank_account_number.clone()),
                bank_routing_number: Some(b.bank_routing_number.to_owned()),
                bic: None,
                bank_sort_code: None,
                iban: None,
                pix_key: None,
                tax_id: None,
                bank_number: None,
                emv: None,
                account_holder_name: b.account_holder_name.clone(),
            },
            Self::Bacs(b) => TokenizedBankSensitiveValues {
                bank_account_number: Some(b.bank_account_number.to_owned()),
                bank_routing_number: None,
                bic: None,
                bank_sort_code: Some(b.bank_sort_code.to_owned()),
                iban: None,
                pix_key: None,
                tax_id: None,
                bank_number: None,
                emv: None,
                account_holder_name: b.account_holder_name.clone(),
            },
            Self::Sepa(b) => TokenizedBankSensitiveValues {
                bank_account_number: None,
                bank_routing_number: None,
                bic: b.bic.to_owned(),
                bank_sort_code: None,
                iban: Some(b.iban.to_owned()),
                pix_key: None,
                tax_id: None,
                bank_number: None,
                emv: None,
                account_holder_name: b.account_holder_name.clone(),
            },
            Self::Pix(bank_details) => TokenizedBankSensitiveValues {
                bank_account_number: bank_details.bank_account_number.to_owned(),
                bank_routing_number: None,
                bic: None,
                bank_sort_code: None,
                iban: None,
                pix_key: bank_details.pix_key.to_owned(),
                tax_id: bank_details.tax_id.to_owned(),
                bank_number: None,
                emv: None,
                account_holder_name: bank_details.account_holder_name.clone(),
            },
            Self::Trustly(bank_details) => TokenizedBankSensitiveValues {
                bank_account_number: bank_details.account_number.to_owned(),
                bank_routing_number: None,
                bic: None,
                bank_sort_code: None,
                iban: bank_details.iban.to_owned(),
                pix_key: None,
                tax_id: None,
                bank_number: bank_details.bank_number.to_owned(),
                emv: None,
                account_holder_name: None,
            },
            Self::OpenBanking(open_banking_details) => TokenizedBankSensitiveValues {
                bank_account_number: None,
                bank_routing_number: None,
                bic: None,
                bank_sort_code: None,
                iban: Some(open_banking_details.iban.clone()),
                pix_key: None,
                tax_id: None,
                bank_number: None,
                emv: None,
                account_holder_name: Some(open_banking_details.account_holder_name.clone()),
            },
        };

        bank_sensitive_data
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode data - bank_sensitive_data")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let bank_insensitive_data = match self {
            Self::Ach(b) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: b.bank_name.to_owned(),
                bank_country_code: b.bank_country_code.to_owned(),
                bank_city: b.bank_city.to_owned(),
                bank_branch: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::Ach),
            },
            Self::Bacs(b) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: b.bank_name.to_owned(),
                bank_country_code: b.bank_country_code.to_owned(),
                bank_city: b.bank_city.to_owned(),
                bank_branch: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::Bacs),
            },
            Self::Sepa(bank_details) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: bank_details.bank_name.to_owned(),
                bank_country_code: bank_details.bank_country_code.to_owned(),
                bank_city: bank_details.bank_city.to_owned(),
                bank_branch: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::Sepa),
            },
            Self::Pix(bank_details) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: bank_details.bank_name.to_owned(),
                bank_country_code: None,
                bank_city: None,
                bank_branch: bank_details.bank_branch.to_owned(),
                ispb: bank_details.ispb.to_owned(),
                bank_code: bank_details.bank_code.to_owned(),
                bank_account_type: bank_details.bank_account_type,
                payout_method_type: Some(PaymentMethodType::Pix),
            },
            Self::Trustly(bank_details) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: None,
                bank_city: None,
                bank_branch: None,
                bank_country_code: Some(bank_details.country_code.to_owned()),
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::Trustly),
            },
            Self::OpenBanking(_) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: None,
                bank_city: None,
                bank_branch: None,
                bank_country_code: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::OpenBanking),
            },
        };

        bank_insensitive_data
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode wallet data bank_insensitive_data")
    }

    fn from_values(
        bank_sensitive_data: String,
        bank_insensitive_data: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let bank_sensitive_data: TokenizedBankSensitiveValues = bank_sensitive_data
            .parse_struct("TokenizedBankValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into bank data bank_sensitive_data")?;

        let bank_insensitive_data: TokenizedBankInsensitiveValues = bank_insensitive_data
            .parse_struct("TokenizedBankValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into wallet data bank_insensitive_data")?;

        let bank = match (
            // ACH + BACS + PIX
            bank_sensitive_data.bank_account_number.to_owned(),
            bank_sensitive_data.bank_routing_number.to_owned(), // ACH
            bank_sensitive_data.bank_sort_code.to_owned(),      // BACS
            // SEPA
            bank_sensitive_data.iban.to_owned(),
            bank_sensitive_data.bic,
            // PIX
            bank_sensitive_data.pix_key,
            bank_sensitive_data.tax_id,
            // TRUSTLY
            bank_insensitive_data.bank_country_code,
            bank_insensitive_data.payout_method_type,
        ) {
            (Some(ban), Some(brn), None, None, None, None, None, _, _) => {
                Self::Ach(payouts::AchBankTransfer {
                    bank_account_number: ban,
                    bank_routing_number: brn,
                    bank_name: bank_insensitive_data.bank_name,
                    bank_country_code: bank_insensitive_data.bank_country_code,
                    bank_city: bank_insensitive_data.bank_city,
                    account_holder_name: bank_sensitive_data.account_holder_name,
                })
            }
            (Some(ban), None, Some(bsc), None, None, None, None, _, _) => {
                Self::Bacs(payouts::BacsBankTransfer {
                    bank_account_number: ban,
                    bank_sort_code: bsc,
                    bank_name: bank_insensitive_data.bank_name,
                    bank_country_code: bank_insensitive_data.bank_country_code,
                    bank_city: bank_insensitive_data.bank_city,
                    account_holder_name: bank_sensitive_data.account_holder_name,
                })
            }
            (
                ban,
                None,
                None,
                iban,
                None,
                None,
                None,
                Some(country_code),
                Some(PaymentMethodType::Trustly),
            ) => Self::Trustly(payouts::TrustlyBankTransfer {
                iban,
                account_number: ban,
                bank_number: bank_sensitive_data.bank_number,
                country_code,
            }),
            (None, None, None, Some(iban), bic, None, None, _, _) => {
                Self::Sepa(payouts::SepaBankTransfer {
                    iban,
                    bic,
                    bank_name: bank_insensitive_data.bank_name,
                    bank_country_code: bank_insensitive_data.bank_country_code,
                    bank_city: bank_insensitive_data.bank_city,
                    account_holder_name: bank_sensitive_data.account_holder_name,
                })
            }
            (Some(ban), None, None, None, None, Some(pix_key), tax_id, _, _) => {
                Self::Pix(payouts::PixBankTransfer {
                    bank_account_number: Some(ban),
                    bank_branch: bank_insensitive_data.bank_branch,
                    bank_name: bank_insensitive_data.bank_name,
                    pix_key: Some(pix_key),
                    tax_id,
                    emv: bank_sensitive_data.emv,
                    ispb: bank_insensitive_data.ispb,
                    bank_code: bank_insensitive_data.bank_code,
                    bank_account_type: bank_insensitive_data.bank_account_type,
                    account_holder_name: bank_sensitive_data.account_holder_name,
                })
            }
            _ => Err(errors::VaultError::ResponseDeserializationFailed)?,
        };

        let supp_data = SupplementaryVaultData {
            customer_id: bank_insensitive_data.customer_id,
            payment_method_id: None,
        };

        Ok((bank, supp_data))
    }
}

#[cfg(feature = "payouts")]
impl Vaultable for api::BankTransferPayout {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let bank_sensitive_data = match self {
            Self::Ach(b) => TokenizedBankSensitiveValues {
                bank_account_number: Some(b.bank_account_number.clone()),
                bank_routing_number: Some(b.bank_routing_number.to_owned()),
                bic: None,
                bank_sort_code: None,
                iban: None,
                pix_key: None,
                tax_id: None,
                bank_number: None,
                emv: None,
                account_holder_name: b.account_holder_name.clone(),
            },
            Self::Bacs(b) => TokenizedBankSensitiveValues {
                bank_account_number: Some(b.bank_account_number.to_owned()),
                bank_routing_number: None,
                bic: None,
                bank_sort_code: Some(b.bank_sort_code.to_owned()),
                iban: None,
                pix_key: None,
                tax_id: None,
                bank_number: None,
                emv: None,
                account_holder_name: b.account_holder_name.clone(),
            },
            Self::Sepa(b) => TokenizedBankSensitiveValues {
                bank_account_number: None,
                bank_routing_number: None,
                bic: b.bic.to_owned(),
                bank_sort_code: None,
                iban: Some(b.iban.to_owned()),
                pix_key: None,
                tax_id: None,
                bank_number: None,
                emv: None,
                account_holder_name: b.account_holder_name.clone(),
            },
            Self::Pix(bank_details) => TokenizedBankSensitiveValues {
                bank_account_number: Some(bank_details.bank_account_number.to_owned()),
                bank_routing_number: None,
                bic: None,
                bank_sort_code: None,
                iban: None,
                pix_key: None,
                tax_id: bank_details.tax_id.to_owned(),
                bank_number: None,
                emv: None,
                account_holder_name: bank_details.account_holder_name.clone(),
            },
            Self::PixKey(pix_key_details) => TokenizedBankSensitiveValues {
                bank_account_number: None,
                bank_routing_number: None,
                bic: None,
                bank_sort_code: None,
                iban: None,
                pix_key: Some(pix_key_details.pix_key.to_owned()),
                tax_id: None,
                bank_number: None,
                emv: None,
                account_holder_name: None,
            },
            Self::PixEmv(pix_emv_details) => TokenizedBankSensitiveValues {
                bank_account_number: None,
                bank_routing_number: None,
                bic: None,
                bank_sort_code: None,
                iban: None,
                pix_key: None,
                tax_id: None,
                bank_number: None,
                emv: Some(pix_emv_details.emv.to_owned()),
                account_holder_name: None,
            },
            Self::Trustly(bank_details) => TokenizedBankSensitiveValues {
                bank_account_number: bank_details.bank_account_number.to_owned(),
                bank_routing_number: None,
                bic: None,
                bank_sort_code: None,
                iban: bank_details.iban.to_owned(),
                pix_key: None,
                tax_id: None,
                bank_number: bank_details.bank_number.to_owned(),
                emv: None,
                account_holder_name: None,
            },
            Self::OpenBanking(bank_details) => TokenizedBankSensitiveValues {
                bank_account_number: None,
                bank_routing_number: None,
                bic: None,
                bank_sort_code: None,
                iban: Some(bank_details.iban.clone()),
                pix_key: None,
                tax_id: None,
                bank_number: None,
                emv: None,
                account_holder_name: Some(bank_details.account_holder_name.clone()),
            },
        };

        bank_sensitive_data
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode data - bank_sensitive_data")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let bank_insensitive_data = match self {
            Self::Ach(b) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: b.bank_name.to_owned(),
                bank_country_code: b.bank_country_code.to_owned(),
                bank_city: b.bank_city.to_owned(),
                bank_branch: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::Ach),
            },
            Self::Bacs(b) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: b.bank_name.to_owned(),
                bank_country_code: b.bank_country_code.to_owned(),
                bank_city: b.bank_city.to_owned(),
                bank_branch: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::Bacs),
            },
            Self::Sepa(bank_details) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: bank_details.bank_name.to_owned(),
                bank_country_code: bank_details.bank_country_code.to_owned(),
                bank_city: bank_details.bank_city.to_owned(),
                bank_branch: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::Sepa),
            },
            Self::Pix(bank_details) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: bank_details.bank_name.to_owned(),
                bank_country_code: None,
                bank_city: None,
                bank_branch: bank_details.bank_branch.to_owned(),
                ispb: bank_details.ispb.to_owned(),
                bank_code: bank_details.bank_code.to_owned(),
                bank_account_type: bank_details.bank_account_type,
                payout_method_type: Some(PaymentMethodType::Pix),
            },
            Self::PixKey(_) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: None,
                bank_country_code: None,
                bank_city: None,
                bank_branch: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::PixKey),
            },
            Self::PixEmv(_) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: None,
                bank_country_code: None,
                bank_city: None,
                bank_branch: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::PixEmv),
            },
            Self::Trustly(bank_details) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: None,
                bank_city: None,
                bank_branch: None,
                bank_country_code: Some(bank_details.bank_country_code.to_owned()),
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::Trustly),
            },
            Self::OpenBanking(_) => TokenizedBankInsensitiveValues {
                customer_id,
                bank_name: None,
                bank_city: None,
                bank_branch: None,
                bank_country_code: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                payout_method_type: Some(PaymentMethodType::OpenBanking),
            },
        };

        bank_insensitive_data
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode wallet data bank_insensitive_data")
    }

    fn from_values(
        bank_sensitive_data: String,
        bank_insensitive_data: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let bank_sensitive_data: TokenizedBankSensitiveValues = bank_sensitive_data
            .parse_struct("TokenizedBankValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into bank data bank_sensitive_data")?;

        let bank_insensitive_data: TokenizedBankInsensitiveValues = bank_insensitive_data
            .parse_struct("TokenizedBankValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into wallet data bank_insensitive_data")?;

        let bank = match bank_insensitive_data.payout_method_type {
            Some(PaymentMethodType::Ach) => Self::Ach(payouts::AchBankTransfer {
                bank_account_number: bank_sensitive_data.bank_account_number.ok_or(
                    errors::VaultError::MissingRequiredField {
                        field_name: "bank_account_number",
                    },
                )?,
                bank_routing_number: bank_sensitive_data.bank_routing_number.ok_or(
                    errors::VaultError::MissingRequiredField {
                        field_name: "bank_routing_number",
                    },
                )?,
                bank_name: bank_insensitive_data.bank_name,
                bank_country_code: bank_insensitive_data.bank_country_code,
                bank_city: bank_insensitive_data.bank_city,
                account_holder_name: bank_sensitive_data.account_holder_name,
            }),
            Some(PaymentMethodType::Bacs) => Self::Bacs(payouts::BacsBankTransfer {
                bank_account_number: bank_sensitive_data.bank_account_number.ok_or(
                    errors::VaultError::MissingRequiredField {
                        field_name: "bank_account_number",
                    },
                )?,
                bank_sort_code: bank_sensitive_data.bank_sort_code.ok_or(
                    errors::VaultError::MissingRequiredField {
                        field_name: "bank_sort_code",
                    },
                )?,
                bank_name: bank_insensitive_data.bank_name,
                bank_country_code: bank_insensitive_data.bank_country_code,
                bank_city: bank_insensitive_data.bank_city,
                account_holder_name: bank_sensitive_data.account_holder_name,
            }),
            Some(PaymentMethodType::Trustly) => Self::Trustly(payouts::TrustlyBankTransferData {
                iban: bank_sensitive_data.iban,
                bank_account_number: bank_sensitive_data.bank_account_number,
                bank_number: bank_sensitive_data.bank_number,
                bank_country_code: bank_insensitive_data.bank_country_code.ok_or(
                    errors::VaultError::MissingRequiredField {
                        field_name: "bank_country_code",
                    },
                )?,
            }),
            Some(PaymentMethodType::Sepa) => Self::Sepa(payouts::SepaBankTransfer {
                iban: bank_sensitive_data
                    .iban
                    .ok_or(errors::VaultError::MissingRequiredField { field_name: "iban" })?,
                bic: bank_sensitive_data.bic,
                bank_name: bank_insensitive_data.bank_name,
                bank_country_code: bank_insensitive_data.bank_country_code,
                bank_city: bank_insensitive_data.bank_city,
                account_holder_name: bank_sensitive_data.account_holder_name,
            }),
            Some(PaymentMethodType::Pix) => Self::Pix(payouts::PixAccountBankTransfer {
                bank_account_number: bank_sensitive_data.bank_account_number.ok_or(
                    errors::VaultError::MissingRequiredField {
                        field_name: "bank_account_number",
                    },
                )?,
                bank_branch: bank_insensitive_data.bank_branch,
                bank_name: bank_insensitive_data.bank_name,
                tax_id: bank_sensitive_data.tax_id,
                ispb: bank_insensitive_data.ispb,
                bank_code: bank_insensitive_data.bank_code,
                bank_account_type: bank_insensitive_data.bank_account_type,
                account_holder_name: bank_sensitive_data.account_holder_name,
            }),
            Some(PaymentMethodType::PixKey) => Self::PixKey(payouts::PixKeyBankTransfer {
                pix_key: bank_sensitive_data.pix_key.ok_or(
                    errors::VaultError::MissingRequiredField {
                        field_name: "pix_key",
                    },
                )?,
            }),
            Some(PaymentMethodType::PixEmv) => Self::PixEmv(payouts::PixEmvBankTransfer {
                emv: bank_sensitive_data
                    .emv
                    .ok_or(errors::VaultError::MissingRequiredField { field_name: "emv" })?,
            }),
            Some(PaymentMethodType::OpenBanking) => Self::OpenBanking(payouts::OpenBanking {
                iban: bank_sensitive_data
                    .iban
                    .ok_or(errors::VaultError::MissingRequiredField { field_name: "iban" })?,
                account_holder_name: bank_sensitive_data.account_holder_name.ok_or(
                    errors::VaultError::MissingRequiredField {
                        field_name: "account_holder_name",
                    },
                )?,
            }),
            _ => Err(errors::VaultError::ResponseDeserializationFailed)?,
        };

        let supp_data = SupplementaryVaultData {
            customer_id: bank_insensitive_data.customer_id,
            payment_method_id: None,
        };

        Ok((bank, supp_data))
    }
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
pub enum VaultPayoutMethod {
    Card(String),
    Bank(String),
    BankTransfer(String),
    Wallet(String),
    BankRedirect(String),
    Passthrough(String),
}

#[cfg(feature = "payouts")]
impl Vaultable for api::PayoutMethodData {
    fn get_value1(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = match self {
            Self::Card(card) => VaultPayoutMethod::Card(card.get_value1(customer_id)?),
            Self::Bank(bank) => VaultPayoutMethod::Bank(bank.get_value1(customer_id)?),
            Self::BankTransfer(bank) => {
                VaultPayoutMethod::BankTransfer(bank.get_value1(customer_id)?)
            }
            Self::Wallet(wallet) => VaultPayoutMethod::Wallet(wallet.get_value1(customer_id)?),
            Self::BankRedirect(bank_redirect) => {
                VaultPayoutMethod::BankRedirect(bank_redirect.get_value1(customer_id)?)
            }
            Self::Passthrough(passthrough) => {
                VaultPayoutMethod::Passthrough(passthrough.get_value1(customer_id)?)
            }
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode payout method value1")
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = match self {
            Self::Card(card) => VaultPayoutMethod::Card(card.get_value2(customer_id)?),
            Self::Bank(bank) => VaultPayoutMethod::Bank(bank.get_value2(customer_id)?),
            Self::BankTransfer(bank) => {
                VaultPayoutMethod::BankTransfer(bank.get_value2(customer_id)?)
            }
            Self::Wallet(wallet) => VaultPayoutMethod::Wallet(wallet.get_value2(customer_id)?),
            Self::BankRedirect(bank_redirect) => {
                VaultPayoutMethod::BankRedirect(bank_redirect.get_value2(customer_id)?)
            }
            Self::Passthrough(passthrough) => {
                VaultPayoutMethod::Passthrough(passthrough.get_value2(customer_id)?)
            }
        };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode payout method value2")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: VaultPayoutMethod = value1
            .parse_struct("VaultMethodValue1")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into vault method value 1")?;

        let value2: VaultPayoutMethod = value2
            .parse_struct("VaultMethodValue2")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into vault method value 2")?;

        match (value1, value2) {
            (VaultPayoutMethod::Card(mvalue1), VaultPayoutMethod::Card(mvalue2)) => {
                let (card, supp_data) = api::CardPayout::from_values(mvalue1, mvalue2)?;
                Ok((Self::Card(card), supp_data))
            }
            (VaultPayoutMethod::Bank(mvalue1), VaultPayoutMethod::Bank(mvalue2)) => {
                let (bank, supp_data) = api::BankPayout::from_values(mvalue1, mvalue2)?;
                Ok((Self::Bank(bank), supp_data))
            }
            (
                VaultPayoutMethod::BankTransfer(mvalue1),
                VaultPayoutMethod::BankTransfer(mvalue2),
            ) => {
                let (bank, supp_data) = api::BankTransferPayout::from_values(mvalue1, mvalue2)?;
                Ok((Self::BankTransfer(bank), supp_data))
            }
            (VaultPayoutMethod::Wallet(mvalue1), VaultPayoutMethod::Wallet(mvalue2)) => {
                let (wallet, supp_data) = api::WalletPayout::from_values(mvalue1, mvalue2)?;
                Ok((Self::Wallet(wallet), supp_data))
            }
            (
                VaultPayoutMethod::BankRedirect(mvalue1),
                VaultPayoutMethod::BankRedirect(mvalue2),
            ) => {
                let (bank_redirect, supp_data) =
                    api::BankRedirectPayout::from_values(mvalue1, mvalue2)?;
                Ok((Self::BankRedirect(bank_redirect), supp_data))
            }
            (VaultPayoutMethod::Passthrough(mvalue1), VaultPayoutMethod::Passthrough(mvalue2)) => {
                let (passthrough, supp_data) =
                    api::PassthroughPayout::from_values(mvalue1, mvalue2)?;
                Ok((Self::Passthrough(passthrough), supp_data))
            }
            _ => Err(errors::VaultError::PayoutMethodNotSupported)
                .attach_printable("Payout method not supported"),
        }
    }
}

#[cfg(feature = "payouts")]
impl Vaultable for api::BankRedirectPayout {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = match self {
            Self::Interac(interac_data) => TokenizedBankRedirectSensitiveValues {
                email: Some(interac_data.email.clone()),
                bank_redirect_type: PaymentMethodType::Interac,
                account_holder_name: None,
                iban: None,
            },
            Self::OpenBankingUk(open_banking_uk_data) => TokenizedBankRedirectSensitiveValues {
                email: None,
                iban: Some(open_banking_uk_data.iban.clone()),
                account_holder_name: Some(open_banking_uk_data.account_holder_name.clone()),
                bank_redirect_type: PaymentMethodType::OpenBankingUk,
            },
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable(
                "Failed to encode bank redirect data - TokenizedBankRedirectSensitiveValues",
            )
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = TokenizedBankRedirectInsensitiveValues { customer_id };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode wallet data value2")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: TokenizedBankRedirectSensitiveValues = value1
            .parse_struct("TokenizedBankRedirectSensitiveValues")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into wallet data value1")?;

        let value2: TokenizedBankRedirectInsensitiveValues = value2
            .parse_struct("TokenizedBankRedirectInsensitiveValues")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into wallet data value2")?;

        let bank_redirect = match value1.bank_redirect_type {
            PaymentMethodType::Interac => match value1.email {
                Some(email) => Self::Interac(api_models::payouts::Interac { email }),
                _ => Err(errors::VaultError::ResponseDeserializationFailed)
                    .attach_printable("Failed to deserialize into Interac")?,
            },
            PaymentMethodType::OpenBankingUk => match (value1.account_holder_name, value1.iban) {
                (Some(account_holder_name), Some(iban)) => {
                    Self::OpenBankingUk(api_models::payouts::OpenBankingUk {
                        account_holder_name,
                        iban,
                    })
                }
                _ => Err(errors::VaultError::ResponseDeserializationFailed)
                    .attach_printable("Failed to deserialize into OpenBankingUk")?,
            },
            _ => Err(errors::VaultError::PayoutMethodNotSupported)
                .attach_printable("Payout method not supported")?,
        };

        let supp_data = SupplementaryVaultData {
            customer_id: value2.customer_id,
            payment_method_id: None,
        };

        Ok((bank_redirect, supp_data))
    }
}

#[cfg(feature = "payouts")]
impl Vaultable for api::PassthroughPayout {
    fn get_value1(
        &self,
        _customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value1 = TokenizedPassthroughSensitiveValues {
            psp_token: self.psp_token.clone(),
            psp_customer_id: self.psp_customer_id.clone(),
        };

        value1
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable(
                "Failed to encode passthrough data - TokenizedPassthroughSensitiveValues",
            )
    }

    fn get_value2(
        &self,
        customer_id: Option<id_type::CustomerId>,
    ) -> CustomResult<String, errors::VaultError> {
        let value2 = TokenizedPassthroughInsensitiveValues {
            customer_id,
            token_type: self.token_type,
        };

        value2
            .encode_to_string_of_json()
            .change_context(errors::VaultError::RequestEncodingFailed)
            .attach_printable("Failed to encode passthrough data value2")
    }

    fn from_values(
        value1: String,
        value2: String,
    ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> {
        let value1: TokenizedPassthroughSensitiveValues = value1
            .parse_struct("TokenizedPassthroughSensitiveValues")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into connector token data value1")?;

        let value2: TokenizedPassthroughInsensitiveValues = value2
            .parse_struct("TokenizedPassthroughInsensitiveValues")
            .change_context(errors::VaultError::ResponseDeserializationFailed)
            .attach_printable("Could not deserialize into connector token data value2")?;

        let passthrough = Self {
            psp_token: value1.psp_token,
            psp_customer_id: value1.psp_customer_id,
            token_type: value2.token_type,
        };

        let supp_data = SupplementaryVaultData {
            customer_id: value2.customer_id,
            payment_method_id: None,
        };

        Ok((passthrough, supp_data))
    }
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedBankRedirectSensitiveValues {
    pub email: Option<Email>,
    pub iban: Option<hyperswitch_masking::Secret<String>>,
    pub account_holder_name: Option<hyperswitch_masking::Secret<String>>,
    pub bank_redirect_type: PaymentMethodType,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedBankRedirectInsensitiveValues {
    pub customer_id: Option<id_type::CustomerId>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedPassthroughSensitiveValues {
    pub psp_token: hyperswitch_masking::Secret<String>,
    pub psp_customer_id: Option<hyperswitch_masking::Secret<String>>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenizedPassthroughInsensitiveValues {
    pub customer_id: Option<id_type::CustomerId>,
    pub token_type: PaymentMethodType,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MockTokenizeDBValue {
    pub value1: String,
    pub value2: String,
}

pub struct Vault;

impl Vault {
    #[instrument(skip_all)]
    pub async fn get_payment_method_data_from_locker(
        state: &routes::SessionState,
        lookup_key: &str,
        merchant_key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<(Option<domain::PaymentMethodData>, SupplementaryVaultData)> {
        let de_tokenize =
            get_tokenized_data(state, lookup_key, true, merchant_key_store.key.get_inner()).await?;
        let (payment_method, customer_id) =
            domain::PaymentMethodData::from_values(de_tokenize.value1, de_tokenize.value2)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error parsing Payment Method from Values")?;

        Ok((Some(payment_method), customer_id))
    }

    #[instrument(skip_all)]
    pub async fn store_payment_method_data_in_locker(
        state: &routes::SessionState,
        token_id: Option<String>,
        payment_method: &domain::PaymentMethodData,
        customer_id: Option<id_type::CustomerId>,
        pm: enums::PaymentMethod,
        merchant_key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<String> {
        let value1 = payment_method
            .get_value1(customer_id.clone())
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error getting Value1 for locker")?;

        let value2 = payment_method
            .get_value2(customer_id)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error getting Value12 for locker")?;

        let lookup_key = token_id.unwrap_or_else(|| generate_id_with_default_len("token"));

        let lookup_key = create_tokenize_without_configurable_expiry(
            state,
            value1,
            Some(value2),
            lookup_key,
            merchant_key_store.key.get_inner(),
        )
        .await?;
        add_delete_tokenized_data_task(
            &*state.store,
            &lookup_key,
            pm,
            state.conf.application_source,
        )
        .await?;
        metrics::TOKENIZED_DATA_COUNT.add(1, &[]);
        Ok(lookup_key)
    }

    #[cfg(feature = "payouts")]
    #[instrument(skip_all)]
    pub async fn get_payout_method_data_from_temporary_locker(
        state: &routes::SessionState,
        lookup_key: &str,
        merchant_key_store: &domain::MerchantKeyStore,
    ) -> RouterResult<(Option<api::PayoutMethodData>, SupplementaryVaultData)> {
        let de_tokenize =
            get_tokenized_data(state, lookup_key, true, merchant_key_store.key.get_inner()).await?;
        let (payout_method, supp_data) =
            api::PayoutMethodData::from_values(de_tokenize.value1, de_tokenize.value2)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error parsing Payout Method from Values")?;

        Ok((Some(payout_method), supp_data))
    }

    #[cfg(feature = "payouts")]
    #[instrument(skip_all)]
    pub async fn store_payout_method_data_in_locker(
        state: &routes::SessionState,
        token_id: Option<String>,
        payout_method: &api::PayoutMethodData,
        customer_id: Option<id_type::CustomerId>,
        merchant_key_store: &domain::MerchantKeyStore,
        intent_fulfillment_time: Option<i64>,
    ) -> RouterResult<String> {
        let value1 = payout_method
            .get_value1(customer_id.clone())
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error getting Value1 for locker")?;

        let value2 = payout_method
            .get_value2(customer_id)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error getting Value2 for locker")?;

        let lookup_key =
            token_id.unwrap_or_else(|| generate_id_with_default_len("temporary_token"));

        let lookup_key = create_tokenize_with_configurable_expiry(
            state,
            value1,
            Some(value2),
            lookup_key,
            merchant_key_store.key.get_inner(),
            intent_fulfillment_time,
        )
        .await?;
        // add_delete_tokenized_data_task(&*state.store, &lookup_key, pm).await?;
        // scheduler_metrics::TOKENIZED_DATA_COUNT.add(1, &[]);
        Ok(lookup_key)
    }

    #[instrument(skip_all)]
    pub async fn delete_locker_payment_method_by_lookup_key(
        state: &routes::SessionState,
        lookup_key: &Option<String>,
    ) {
        if let Some(lookup_key) = lookup_key {
            delete_tokenized_data(state, lookup_key)
                .await
                .map(|_| logger::info!("Card From locker deleted Successfully"))
                .map_err(|err| logger::error!("Error: Deleting Card From Redis Locker : {:?}", err))
                .ok();
        }
    }
}

//------------------------------------------------TokenizeService------------------------------------------------

#[inline(always)]
fn get_redis_locker_key(lookup_key: &str) -> String {
    format!("{}_{}", consts::LOCKER_REDIS_PREFIX, lookup_key)
}

#[instrument(skip(state, value1, value2))]
pub async fn create_tokenize_without_configurable_expiry(
    state: &routes::SessionState,
    value1: String,
    value2: Option<String>,
    lookup_key: String,
    encryption_key: &hyperswitch_masking::Secret<Vec<u8>>,
) -> RouterResult<String> {
    create_tokenize(state, value1, value2, lookup_key, encryption_key, None).await
}

#[instrument(skip(state, value1, value2))]
pub async fn create_tokenize_with_configurable_expiry(
    state: &routes::SessionState,
    value1: String,
    value2: Option<String>,
    lookup_key: String,
    encryption_key: &hyperswitch_masking::Secret<Vec<u8>>,
    expiry_time: Option<i64>,
) -> RouterResult<String> {
    create_tokenize(
        state,
        value1,
        value2,
        lookup_key,
        encryption_key,
        expiry_time,
    )
    .await
}

#[instrument(skip(state, value1, value2))]
async fn create_tokenize(
    state: &routes::SessionState,
    value1: String,
    value2: Option<String>,
    lookup_key: String,
    encryption_key: &hyperswitch_masking::Secret<Vec<u8>>,
    expiry_time: Option<i64>,
) -> RouterResult<String> {
    let redis_key = get_redis_locker_key(lookup_key.as_str());
    let func = || async {
        metrics::CREATED_TOKENIZED_CARD.add(1, &[]);

        let payload_to_be_encrypted = api::TokenizePayloadRequest {
            value1: value1.clone(),
            value2: value2.clone().unwrap_or_default(),
            lookup_key: lookup_key.clone(),
            service_name: VAULT_SERVICE_NAME.to_string(),
        };

        let payload = payload_to_be_encrypted
            .encode_to_string_of_json()
            .change_context(errors::ApiErrorResponse::InternalServerError)?;

        let encrypted_payload = GcmAes256
            .encode_message(encryption_key.peek().as_ref(), payload.as_bytes())
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to encode redis temp locker data")?;

        let redis_conn = state
            .store
            .get_redis_conn()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to get redis connection")?;

        redis_conn
            .set_key_if_not_exists_with_expiry(
                &redis_key.as_str().into(),
                encrypted_payload.as_slice(),
                expiry_time.or(Some(i64::from(consts::LOCKER_REDIS_EXPIRY_SECONDS))),
            )
            .await
            .map(|_| lookup_key.clone())
            .inspect_err(|error| {
                metrics::TEMP_LOCKER_FAILURES.add(1, &[]);
                logger::error!(?error, "Failed to store tokenized data in Redis");
            })
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error from redis locker")
    };

    match func().await {
        Ok(s) => {
            logger::info!(
                "Insert payload in redis locker successful with lookup key: {:?}",
                redis_key
            );
            Ok(s)
        }
        Err(err) => {
            logger::error!("Redis Temp locker Failed: {:?}", err);
            Err(err)
        }
    }
}

#[instrument(skip(state))]
pub async fn get_tokenized_data(
    state: &routes::SessionState,
    lookup_key: &str,
    _should_get_value2: bool,
    encryption_key: &hyperswitch_masking::Secret<Vec<u8>>,
) -> RouterResult<api::TokenizePayloadRequest> {
    let redis_key = get_redis_locker_key(lookup_key);
    let func = || async {
        metrics::GET_TOKENIZED_CARD.add(1, &[]);

        let redis_conn = state
            .store
            .get_redis_conn()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to get redis connection")?;

        let response = redis_conn
            .get_key::<bytes::Bytes>(&redis_key.as_str().into())
            .await;

        match response {
            Ok(resp) => {
                let decrypted_payload = GcmAes256
                    .decode_message(
                        encryption_key.peek().as_ref(),
                        hyperswitch_masking::Secret::new(resp.into()),
                    )
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to decode redis temp locker data")?;

                let get_response: api::TokenizePayloadRequest =
                    bytes::Bytes::from(decrypted_payload)
                        .parse_struct("TokenizePayloadRequest")
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable(
                            "Error getting TokenizePayloadRequest from tokenize response",
                        )?;

                Ok(get_response)
            }
            Err(err) => {
                metrics::TEMP_LOCKER_FAILURES.add(1, &[]);
                Err(err).change_context(errors::ApiErrorResponse::UnprocessableEntity {
                    message: "Token is invalid or expired".into(),
                })
            }
        }
    };

    match func().await {
        Ok(s) => {
            logger::info!(
                "Fetch payload in redis locker successful with lookup key: {:?}",
                redis_key
            );
            Ok(s)
        }
        Err(err) => {
            logger::error!("Redis Temp locker Failed: {:?}", err);
            Err(err)
        }
    }
}

#[instrument(skip(state))]
pub async fn delete_tokenized_data(
    state: &routes::SessionState,
    lookup_key: &str,
) -> RouterResult<()> {
    let redis_key = get_redis_locker_key(lookup_key);
    let func = || async {
        metrics::DELETED_TOKENIZED_CARD.add(1, &[]);

        let redis_conn = state
            .store
            .get_redis_conn()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to get redis connection")?;

        let response = redis_conn.delete_key(&redis_key.as_str().into()).await;

        match response {
            Ok(redis_interface::DelReply::KeyDeleted) => Ok(()),
            Ok(redis_interface::DelReply::KeyNotDeleted) => {
                Err(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Token invalid or expired")
            }
            Err(err) => {
                metrics::TEMP_LOCKER_FAILURES.add(1, &[]);
                Err(errors::ApiErrorResponse::InternalServerError).attach_printable_lazy(|| {
                    format!("Failed to delete from redis locker: {err:?}")
                })
            }
        }
    };
    match func().await {
        Ok(s) => {
            logger::info!(
                "Delete payload in redis locker successful with lookup key: {:?}",
                redis_key
            );
            Ok(s)
        }
        Err(err) => {
            logger::error!("Redis Temp locker Failed: {:?}", err);
            Err(err)
        }
    }
}

async fn create_vault_request<R: pm_types::VaultingInterface>(
    jwekey: &settings::Jwekey,
    locker: &settings::Locker,
    payload: Vec<u8>,
    tenant_id: id_type::TenantId,
    write_mode: Option<pm_types::VaultQueryParam>,
    additional_headers: Option<HashMap<String, String>>,
) -> CustomResult<request::Request, errors::VaultError> {
    let private_key = jwekey.vault_private_key.peek().as_bytes();

    let jws = services::encryption::jws_sign_payload(
        &payload,
        &locker.locker_signing_key_id,
        private_key,
    )
    .await
    .change_context(errors::VaultError::RequestEncryptionFailed)?;

    let jwe_payload = pm_transforms::create_jwe_body_for_vault(jwekey, &jws).await?;

    let mut url = locker.host.to_owned();
    url.push_str(R::get_vaulting_request_url());

    let mut request = request::Request::new(services::Method::Post, &url);

    // Add query param for write mode if specified
    write_mode
        .and_then(|query_params| query_params.to_query_value())
        .map(|query_params_value| request.query_params = Some(query_params_value));

    request.add_header(
        headers::CONTENT_TYPE,
        consts::V2_VAULT_HEADER_CONTENT_TYPE.into(),
    );
    request.add_header(
        headers::X_TENANT_ID,
        tenant_id.get_string_repr().to_owned().into(),
    );

    if let Some(additional_headers) = additional_headers {
        for (header_name, header_value) in additional_headers {
            request.add_header(&header_name, header_value.into());
        }
    }
    request.set_body(request::RequestContent::Json(Box::new(jwe_payload)));
    Ok(request)
}

#[instrument(skip_all)]
pub async fn call_to_vault<V: pm_types::VaultingInterface>(
    state: &routes::SessionState,
    payload: Vec<u8>,
    query_params: Option<pm_types::VaultQueryParam>,
    additional_headers: Option<HashMap<String, String>>,
) -> CustomResult<String, errors::VaultError> {
    let locker = &state.conf.locker;
    let jwekey = state.conf.jwekey.get_inner();
    let additional_headers = if cfg!(feature = "ext_services_latency") {
        let mut additional_headers = additional_headers.unwrap_or_default();
        if let Some(request_id) = state.request_id.as_ref() {
            additional_headers.insert(
                common_utils::consts::X_REQUEST_ID.to_string(),
                request_id.to_string(),
            );
        }
        additional_headers
    } else {
        additional_headers.unwrap_or_default()
    };

    let request = create_vault_request::<V>(
        jwekey,
        locker,
        payload,
        state.tenant.tenant_id.to_owned(),
        query_params,
        Some(additional_headers),
    )
    .await?;
    let response = services::call_connector_api(state, request, V::get_vaulting_flow_name(), None)
        .await
        .change_context(errors::VaultError::VaultAPIError);

    let jwe_body: services::JweBody = response
        .get_response_inner("JweBody")
        .change_context(errors::VaultError::ResponseDeserializationFailed)
        .attach_printable("Failed to get JweBody from vault response")?;

    let decrypted_payload = pm_transforms::get_decrypted_vault_response_payload(
        jwekey,
        jwe_body,
        locker.decryption_scheme.clone(),
    )
    .await
    .change_context(errors::VaultError::ResponseDecryptionFailed)
    .attach_printable("Error getting decrypted vault response payload")?;

    Ok(decrypted_payload)
}

pub async fn create_entity_in_locker(
    state: &routes::SessionState,
    entity_id: &id_type::MerchantId,
) -> CustomResult<pm_types::EntityCreateResponse, errors::VaultError> {
    let payload = pm_types::EntityCreateRequest {
        entity_id: entity_id.clone(),
    }
    .encode_to_vec()
    .change_context(errors::VaultError::RequestEncodingFailed)?;

    let response = call_to_vault::<pm_types::EntityCreate>(state, payload, None, None)
        .await
        .change_context(errors::VaultError::VaultAPIError)
        .attach_printable("Call to vault failed while creating locker entity")?;

    response
        .parse_struct("EntityCreateResponse")
        .change_context(errors::VaultError::ResponseDeserializationFailed)
        .attach_printable("Failed to parse EntityCreateResponse")
}

#[cfg(feature = "v2")]
pub async fn get_fingerprint_id_for_payment_method(
    state: &routes::SessionState,
    payment_method_data: &domain::PaymentMethodVaultingData,
    customer_id: String,
) -> CustomResult<String, errors::VaultError> {
    let fingerprint_data = payment_method_data.to_fingerprint_data();

    get_fingerprint_id_from_vault(state, &fingerprint_data, customer_id).await
}

#[cfg(feature = "v2")]
pub async fn get_auxiliary_fingerprint_id_for_payment_method(
    state: &routes::SessionState,
    payment_method_data: &domain::PaymentMethodVaultingData,
    customer_id: String,
) -> CustomResult<String, errors::VaultError> {
    let fingerprint_data = payment_method_data.to_auxiliary_fingerprint_data();

    get_fingerprint_id_from_vault(state, &fingerprint_data, customer_id).await
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
async fn get_fingerprint_id_from_vault<D: serde::Serialize>(
    state: &routes::SessionState,
    data: &D,
    key: String,
) -> CustomResult<String, errors::VaultError> {
    let data = serde_json::to_string(data)
        .change_context(errors::VaultError::RequestEncodingFailed)
        .attach_printable("Failed to encode Vaulting data to string")?;

    let payload = pm_types::VaultFingerprintRequestNew { key, data }
        .encode_to_vec()
        .change_context(errors::VaultError::RequestEncodingFailed)
        .attach_printable("Failed to encode VaultFingerprintRequestNew")?;

    let resp = call_to_vault::<pm_types::GetVaultFingerprint>(state, payload, None, None)
        .await
        .change_context(errors::VaultError::VaultAPIError)
        .attach_printable("Call to vault failed")?;

    let fingerprint_resp: pm_types::VaultFingerprintResponse = resp
        .parse_struct("VaultFingerprintResponse")
        .change_context(errors::VaultError::ResponseDeserializationFailed)
        .attach_printable("Failed to parse data into VaultFingerprintResponse")?;

    Ok(fingerprint_resp.fingerprint_id)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn add_payment_method_to_vault(
    state: &routes::SessionState,
    platform: &domain::Platform,
    pmd: &domain::PaymentMethodVaultingData,
    existing_vault_id: Option<domain::VaultId>,
    customer_id: &id_type::GlobalCustomerId,
    write_mode: Option<pm_types::WriteMode>,
) -> CustomResult<pm_types::AddVaultResponse, errors::VaultError> {
    let should_trigger_fingerprint_migration =
        payment_method_utils::get_should_trigger_fingerprint_migration(
            state,
            None,
            platform.get_provider().get_provider_merchant_id(),
        )
        .await;

    let payload = pm_cards::encode_add_vault_request(
        should_trigger_fingerprint_migration,
        platform.get_provider().get_account().get_id().clone(),
        customer_id,
        pmd.clone(),
        state.conf.locker.ttl_for_storage_in_secs,
        existing_vault_id,
    )?;

    let query_params = write_mode.map(pm_types::VaultQueryParam::from);

    let resp = call_to_vault::<pm_types::AddVault>(state, payload, query_params, None)
        .await
        .change_context(errors::VaultError::VaultAPIError)
        .attach_printable("Call to vault failed")?;

    let stored_pm_resp = resp
        .parse_struct("AddVaultResponse")
        .change_context(errors::VaultError::ResponseDeserializationFailed)
        .attach_printable("Failed to parse data into AddVaultResponse")?;

    Ok(stored_pm_resp)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn retrieve_payment_method_from_vault_internal(
    state: &routes::SessionState,
    platform: &domain::Platform,
    vault_id: &domain::VaultId,
    customer_id: &id_type::GlobalCustomerId,
    payment_method_type: Option<enums::PaymentMethod>,
) -> CustomResult<pm_types::VaultRetrieveResponse, errors::VaultError> {
    let locker = LockerType::from_micro_services_config(&state.conf.micro_services);
    locker
        .retrieve_payment_method_from_locker(
            state,
            platform,
            vault_id,
            customer_id,
            payment_method_type,
        )
        .await
}

#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
#[instrument(skip_all)]
pub async fn retrieve_value_from_vault(
    state: &routes::SessionState,
    request: pm_types::VaultRetrieveRequest,
) -> CustomResult<serde_json::value::Value, errors::VaultError> {
    let payload = request
        .encode_to_vec()
        .change_context(errors::VaultError::RequestEncodingFailed)
        .attach_printable("Failed to encode VaultRetrieveRequest")?;

    let resp = call_to_vault::<pm_types::VaultRetrieve>(state, payload, None, None)
        .await
        .change_context(errors::VaultError::VaultAPIError)
        .attach_printable("Call to vault failed")?;

    let stored_resp: serde_json::Value = resp
        .parse_struct("VaultRetrieveResponse")
        .change_context(errors::VaultError::ResponseDeserializationFailed)
        .attach_printable("Failed to parse data into VaultRetrieveResponse")?;

    Ok(stored_resp)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn retrieve_payment_method_from_vault_external(
    state: &routes::SessionState,
    merchant_account: &domain::MerchantAccount,
    pm: &domain::PaymentMethod,
    merchant_connector_account: domain::MerchantConnectorAccountTypeDetails,
) -> RouterResult<pm_types::VaultRetrieveResponse> {
    let connector_vault_id = pm
        .locker_id
        .clone()
        .map(|id| id.get_string_repr().to_owned());

    let merchant_connector_account = match &merchant_connector_account {
        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 router_data = core_utils::construct_vault_router_data(
        state,
        merchant_account.get_id(),
        merchant_connector_account,
        None,
        connector_vault_id,
        None,
        None,
        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 retrieve api call",
        )?;

    let connector_name = merchant_connector_account.get_connector_name_as_string(); // always get the connector name from this call

    let connector_data = api::ConnectorData::get_external_vault_connector_by_name(
        &state.conf.connectors,
        connector_name,
        api::GetToken::Connector,
        Some(merchant_connector_account.get_id()),
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to get the connector data")?;

    access_token::create_access_token(
        state,
        &connector_data,
        merchant_account.get_id(),
        &mut old_router_data,
    )
    .await?;

    let connector_integration: services::BoxedVaultConnectorIntegrationInterface<
        ExternalVaultRetrieveFlow,
        types::VaultRequestData,
        types::VaultResponseData,
    > = connector_data.connector.get_connector_integration();

    let router_data_resp = services::execute_connector_processing_step(
        state,
        connector_integration,
        &old_router_data,
        payments_core::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_vault_failed_response()?;

    get_vault_response_for_retrieve_payment_method_data::<ExternalVaultRetrieveFlow>(
        router_data_resp,
    )
}

#[cfg(feature = "v2")]
pub fn get_vault_response_for_retrieve_payment_method_data<F>(
    router_data: VaultRouterData<F>,
) -> RouterResult<pm_types::VaultRetrieveResponse> {
    match router_data.response {
        Ok(response) => match response {
            types::VaultResponseData::ExternalVaultRetrieveResponse { vault_data } => {
                Ok(pm_types::VaultRetrieveResponse { data: vault_data })
            }
            types::VaultResponseData::ExternalVaultInsertResponse { .. }
            | types::VaultResponseData::ExternalVaultDeleteResponse { .. }
            | types::VaultResponseData::ExternalVaultCreateResponse { .. } => {
                Err(report!(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Invalid Vault Response"))
            }
        },
        Err(err) => Err(report!(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to retrieve payment method")),
    }
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn retrieve_payment_method_from_vault_using_payment_token(
    state: &routes::SessionState,
    platform: &domain::Platform,
    profile: &domain::Profile,
    payment_token: &String,
    payment_method_type: &common_enums::PaymentMethod,
) -> RouterResult<(domain::PaymentMethod, domain::PaymentMethodVaultingData)> {
    let pm_token_data =
        utils::retrieve_payment_token_data(state, payment_token.to_string()).await?;

    let payment_method_id = match pm_token_data {
        storage::PaymentTokenData::PermanentCard(card_token_data) => {
            card_token_data.payment_method_id
        }
        storage::PaymentTokenData::BankDebit(bank_debit_token_data) => {
            bank_debit_token_data.payment_method_id
        }
        storage::PaymentTokenData::TemporaryGeneric(_) => {
            Err(errors::ApiErrorResponse::NotImplemented {
                message: errors::NotImplementedMessage::Reason(
                    "TemporaryGeneric Token not implemented".to_string(),
                ),
            })?
        }
        storage::PaymentTokenData::AuthBankDebit(_) => {
            Err(errors::ApiErrorResponse::NotImplemented {
                message: errors::NotImplementedMessage::Reason(
                    "AuthBankDebit Token not implemented".to_string(),
                ),
            })?
        }
        storage::PaymentTokenData::TemporaryCardToken(_) => {
            Err(errors::ApiErrorResponse::NotImplemented {
                message: errors::NotImplementedMessage::Reason(
                    "TemporaryCardToken does not have a vaulted payment method".to_string(),
                ),
            })?
        }
    };
    let db = &*state.store;

    let storage_scheme = platform.get_processor().get_account().storage_scheme;

    let payment_method = db
        .find_payment_method(
            platform.get_processor().get_key_store(),
            &payment_method_id,
            storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

    let vault_data = Box::pin(retrieve_payment_method_from_vault(
        state,
        platform,
        profile,
        &payment_method,
    ))
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to retrieve payment method from vault")?
    .data;

    Ok((payment_method, vault_data))
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TemporaryVaultCvc {
    card_cvc: hyperswitch_masking::Secret<String>,
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn insert_cvc_using_payment_token(
    state: &routes::SessionState,
    token: &str,
    card_cvc: hyperswitch_masking::Secret<String>,
    fulfillment_time: i64,
    key_store: &domain::MerchantKeyStore,
) -> RouterResult<api_models::payment_methods::CardCVCTokenStorageDetails> {
    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_{token}_hyperswitch_cvc");

    let payload_to_be_encrypted = TemporaryVaultCvc { card_cvc };

    // Encrypt the CVC and store it in Redis
    let encrypted_payload: Encryption = create_encrypted_data(
        &(state.into()),
        key_store,
        payload_to_be_encrypted.clone(),
        common_utils::type_name!(diesel_models::payment_method::PaymentMethod),
    )
    .await
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to encrypt TemporaryVaultCvc for vault")?
    .into();

    redis_conn
        .serialize_and_set_key_with_expiry(
            &key.as_str().into(),
            encrypted_payload,
            fulfillment_time,
        )
        .await
        .map_err(Into::<errors::StorageError>::into)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to add encrypted cvc to redis")?;

    let card_token_cvc_storage =
        api_models::payment_methods::CardCVCTokenStorageDetails::generate_expiry_timestamp(
            fulfillment_time,
        );

    Ok(card_token_cvc_storage)
}

#[cfg(any(feature = "v1", feature = "v2"))]
#[instrument(skip_all)]
pub async fn retrieve_and_delete_cvc_from_payment_token(
    state: &routes::SessionState,
    payment_method_id: &String,
    key_store: &domain::MerchantKeyStore,
) -> RouterResult<hyperswitch_masking::Secret<String>> {
    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_{payment_method_id}_hyperswitch_cvc");

    let resp: Encryption = redis_conn
        .get_and_deserialize_key::<Encryption>(&key.clone().into(), "Vec<u8>")
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let cvc_data: TemporaryVaultCvc = pm_cards::decrypt_generic_data(state, Some(resp), key_store)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to decrypt volatile payment method vault data")?
        .get_required_value("PaymentMethodVaultingData")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get required decrypted volatile payment method vault data")?;

    logger::info!(
        "CVC retrieved successfully from redis for payment method id: {}",
        payment_method_id
    );

    // delete key after retrieving the cvc
    let _ = redis_conn.delete_key(&key.into()).await.map_err(|err| {
        logger::error!("Failed to delete token from redis: {:?}", err);
    });

    Ok(cvc_data.card_cvc)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn retrieve_key_and_ttl_for_cvc_from_payment_method_id(
    state: &routes::SessionState,
    payment_method_id: id_type::GlobalPaymentMethodId,
) -> RouterResult<i64> {
    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_cvc",
        payment_method_id.get_string_repr()
    );

    // check if key exists and get ttl
    redis_conn
        .get_key::<bytes::Bytes>(&key.clone().into())
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to fetch the payment_method_token from redis")?;

    let ttl = redis_conn
        .get_ttl(&key.clone().into())
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to fetch the payment_method_token ttl from redis")?;

    Ok(ttl)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn delete_payment_token(
    state: &routes::SessionState,
    key_for_token: &str,
    intent_status: enums::IntentStatus,
) -> RouterResult<()> {
    if ![
        enums::IntentStatus::RequiresCustomerAction,
        enums::IntentStatus::RequiresMerchantAction,
    ]
    .contains(&intent_status)
    {
        utils::delete_payment_token_data(state, key_for_token)
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to delete payment_token")?;
    }
    Ok(())
}

#[cfg(feature = "v2")]
pub async fn retrieve_payment_method_data_from_storage(
    state: &routes::SessionState,
    platform: &domain::Platform,
    profile: &domain::Profile,
    pm: &domain::PaymentMethod,
    storage_type: enums::StorageType,
) -> RouterResult<pm_types::VaultRetrieveResponse> {
    let mut payment_method_data = match storage_type {
        enums::StorageType::Persistent => {
            Box::pin(retrieve_payment_method_from_vault(
                state, platform, profile, pm,
            ))
            .await?
        }
        enums::StorageType::Volatile => {
            retrieve_volatile_payment_method_from_redis(
                state,
                platform.get_provider().get_key_store(),
                pm,
            )
            .await?
        }
    };

    let card_cvc = retrieve_and_delete_cvc_from_payment_token(
        state,
        &pm.id.get_string_repr().to_string(),
        platform.get_provider().get_key_store(),
    )
    .await
    .inspect_err(|err| {
        logger::warn!(
            "Failed to retrieve CVC for payment method {}",
            pm.id.get_string_repr()
        );
    });

    if let Ok(card_cvc) = card_cvc {
        payment_method_data.data.set_card_cvc(card_cvc);
    }

    Ok(payment_method_data)
}

#[cfg(feature = "v2")]
pub async fn retrieve_volatile_payment_method_from_redis(
    state: &routes::SessionState,
    key_store: &domain::MerchantKeyStore,
    pm: &domain::PaymentMethod,
) -> RouterResult<pm_types::VaultRetrieveResponse> {
    let vault_id = pm
        .locker_id
        .clone()
        .ok_or(errors::VaultError::MissingRequiredField {
            field_name: "locker_id",
        })
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Missing locker_id for VaultRetrieveRequest")?;

    let redis_conn = state
        .store
        .get_redis_conn()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get redis connection")?;

    let response = redis_conn
        .get_and_deserialize_key::<Encryption>(&vault_id.get_string_repr().into(), "Vec<u8>")
        .await;

    match response {
        Ok(resp) => {
            let decrypted_payload: domain::PaymentMethodVaultingData =
                pm_cards::decrypt_generic_data(state, Some(resp), key_store)
                    .await
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Failed to decrypt volatile payment method vault data")?
                    .get_required_value("PaymentMethodVaultingData")
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable(
                        "Failed to get required decrypted volatile payment method vault data",
                    )?;

            Ok(pm_types::VaultRetrieveResponse {
                data: decrypted_payload,
            })
        }
        Err(err) => Err(err).change_context(errors::ApiErrorResponse::UnprocessableEntity {
            message: "Token is invalid or expired".into(),
        }),
    }
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn retrieve_payment_method_from_vault(
    state: &routes::SessionState,
    platform: &domain::Platform,
    profile: &domain::Profile,
    pm: &domain::PaymentMethod,
) -> RouterResult<pm_types::VaultRetrieveResponse> {
    let external_vault_profile =
        payments_core::helpers::resolve_provider_profile(state, platform, profile).await?;
    let is_external_vault_enabled = external_vault_profile.is_external_vault_enabled();

    match is_external_vault_enabled {
        true => {
            let external_vault_source = pm.external_vault_source.as_ref();

            let merchant_connector_account =
                domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new(
                    payments_core::helpers::get_provider_mca_v2(
                        state,
                        platform.get_provider(),
                        external_vault_source,
                    )
                    .await
                    .attach_printable(
                        "failed to fetch merchant connector account for external vault retrieve",
                    )?,
                ));

            retrieve_payment_method_from_vault_external(
                state,
                platform.get_provider().get_account(),
                pm,
                merchant_connector_account,
            )
            .await
        }
        false => {
            let vault_id = pm
                .locker_id
                .clone()
                .ok_or(errors::VaultError::MissingRequiredField {
                    field_name: "locker_id",
                })
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Missing locker_id for VaultRetrieveRequest")?;
            let customer_id = pm
                .customer_id
                .clone()
                .get_required_value("GlobalCustomerId")?;

            retrieve_payment_method_from_vault_internal(
                state,
                platform,
                &vault_id,
                &customer_id,
                pm.payment_method_type,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to retrieve payment method from vault")
        }
    }
}

#[cfg(feature = "v2")]
pub async fn delete_payment_method_data_from_vault_internal(
    state: &routes::SessionState,
    platform: &domain::Platform,
    vault_id: domain::VaultId,
    customer_id: &id_type::GlobalCustomerId,
) -> CustomResult<pm_types::VaultDeleteResponse, errors::VaultError> {
    let locker = LockerType::from_micro_services_config(&state.conf.micro_services);
    locker
        .delete_payment_method_from_locker(state, platform, vault_id, customer_id)
        .await
}

#[cfg(feature = "v2")]
pub async fn delete_payment_method_data_from_vault_external(
    state: &routes::SessionState,
    merchant_account: &domain::MerchantAccount,
    merchant_connector_account: domain::MerchantConnectorAccountTypeDetails,
    vault_id: domain::VaultId,
    customer_id: &id_type::GlobalCustomerId,
) -> RouterResult<pm_types::VaultDeleteResponse> {
    let connector_vault_id = vault_id.get_string_repr().to_owned();

    // Extract MerchantConnectorAccount from the enum
    let merchant_connector_account = match &merchant_connector_account {
        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 router_data = core_utils::construct_vault_router_data(
        state,
        merchant_account.get_id(),
        merchant_connector_account,
        None,
        Some(connector_vault_id),
        None,
        None,
        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 delete api call",
        )?;

    let connector_name = merchant_connector_account.get_connector_name_as_string(); // always get the connector name from this call

    let connector_data = api::ConnectorData::get_external_vault_connector_by_name(
        &state.conf.connectors,
        connector_name,
        api::GetToken::Connector,
        Some(merchant_connector_account.get_id()),
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to get the connector data")?;

    let connector_integration: services::BoxedVaultConnectorIntegrationInterface<
        ExternalVaultDeleteFlow,
        types::VaultRequestData,
        types::VaultResponseData,
    > = connector_data.connector.get_connector_integration();

    let router_data_resp = services::execute_connector_processing_step(
        state,
        connector_integration,
        &old_router_data,
        payments_core::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_vault_failed_response()?;

    get_vault_response_for_delete_payment_method_data::<ExternalVaultDeleteFlow>(
        router_data_resp,
        customer_id.to_owned(),
    )
}

#[cfg(feature = "v2")]
pub fn get_vault_response_for_delete_payment_method_data<F>(
    router_data: VaultRouterData<F>,
    customer_id: id_type::GlobalCustomerId,
) -> RouterResult<pm_types::VaultDeleteResponse> {
    match router_data.response {
        Ok(response) => match response {
            types::VaultResponseData::ExternalVaultDeleteResponse { connector_vault_id } => {
                Ok(pm_types::VaultDeleteResponse {
                    vault_id: domain::VaultId::generate(connector_vault_id), // converted to VaultId type
                    entity_id: customer_id,
                })
            }
            types::VaultResponseData::ExternalVaultInsertResponse { .. }
            | types::VaultResponseData::ExternalVaultRetrieveResponse { .. }
            | types::VaultResponseData::ExternalVaultCreateResponse { .. } => {
                Err(report!(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Invalid Vault Response"))
            }
        },
        Err(err) => Err(report!(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to retrieve payment method")),
    }
}

#[cfg(feature = "v2")]
pub async fn delete_payment_method_data_from_vault(
    state: &routes::SessionState,
    platform: &domain::Platform,
    profile: &domain::Profile,
    pm: &domain::PaymentMethod,
) -> RouterResult<pm_types::VaultDeleteResponse> {
    let external_vault_profile =
        payments_core::helpers::resolve_provider_profile(state, platform, profile).await?;
    let is_external_vault_enabled = external_vault_profile.is_external_vault_enabled();

    let vault_id = pm
        .locker_id
        .clone()
        .get_required_value("locker_id")
        .attach_printable("Missing locker_id in PaymentMethod")?;
    let customer_id = &pm
        .customer_id
        .clone()
        .get_required_value("GlobalCustomerId")?;

    match is_external_vault_enabled {
        true => {
            let external_vault_source = pm.external_vault_source.as_ref();

            let merchant_connector_account =
                domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(Box::new(
                    payments_core::helpers::get_provider_mca_v2(
                        state,
                        platform.get_provider(),
                        external_vault_source,
                    )
                    .await
                    .attach_printable(
                        "failed to fetch merchant connector account for external vault delete",
                    )?,
                ));

            delete_payment_method_data_from_vault_external(
                state,
                platform.get_provider().get_account(),
                merchant_connector_account,
                vault_id.clone(),
                customer_id,
            )
            .await
        }
        false => {
            delete_payment_method_data_from_vault_internal(state, platform, vault_id, customer_id)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to delete payment method from vault")
        }
    }
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn retrieve_payment_method_from_vault_external_v1(
    state: &routes::SessionState,
    merchant_id: &id_type::MerchantId,
    pm: &domain::PaymentMethod,
    merchant_connector_account: hyperswitch_domain_models::merchant_connector_account::MerchantConnectorAccount,
) -> RouterResult<hyperswitch_domain_models::vault::PaymentMethodVaultingData> {
    let connector_vault_id = pm.locker_id.clone().map(|id| id.to_string());

    let router_data = core_utils::construct_vault_router_data(
        state,
        merchant_id,
        &merchant_connector_account,
        None,
        connector_vault_id,
        None,
        None,
        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 retrieve api call",
        )?;

    let connector_name = merchant_connector_account.get_connector_name_as_string();

    let connector_data = api::ConnectorData::get_external_vault_connector_by_name(
        &state.conf.connectors,
        connector_name,
        api::GetToken::Connector,
        Some(merchant_connector_account.get_id()),
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to get the connector data")?;

    access_token::create_access_token(state, &connector_data, merchant_id, &mut old_router_data)
        .await?;

    if old_router_data.response.is_ok() {
        let connector_integration: services::BoxedVaultConnectorIntegrationInterface<
            hyperswitch_domain_models::router_flow_types::ExternalVaultRetrieveFlow,
            types::VaultRequestData,
            types::VaultResponseData,
        > = connector_data.connector.get_connector_integration();

        let router_data_resp = services::execute_connector_processing_step(
            state,
            connector_integration,
            &old_router_data,
            payments::CallConnectorAction::Trigger,
            None,
            None,
        )
        .await
        .to_vault_failed_response()?;
        get_vault_response_for_retrieve_payment_method_data_v1(router_data_resp)
    } else {
        logger::error!(
            "Error vaulting payment method: {:?}",
            old_router_data.response
        );
        Err(report!(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to create access token for external vault"))
    }
}

pub fn get_vault_response_for_retrieve_payment_method_data_v1<F>(
    router_data: VaultRouterData<F>,
) -> RouterResult<hyperswitch_domain_models::vault::PaymentMethodVaultingData> {
    match router_data.response {
        Ok(response) => match response {
            types::VaultResponseData::ExternalVaultRetrieveResponse { vault_data } => {
                Ok(vault_data)
            }
            types::VaultResponseData::ExternalVaultInsertResponse { .. }
            | types::VaultResponseData::ExternalVaultDeleteResponse { .. }
            | types::VaultResponseData::ExternalVaultCreateResponse { .. } => {
                Err(report!(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Invalid Vault Response"))
            }
        },
        Err(err) => {
            logger::error!(
                "Failed to retrieve payment method from external vault: {:?}",
                err
            );
            Err(report!(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to retrieve payment method from external vault"))
        }
    }
}

// ********************************************** PROCESS TRACKER **********************************************

pub async fn add_delete_tokenized_data_task(
    db: &dyn db::StorageInterface,
    lookup_key: &str,
    pm: enums::PaymentMethod,
    application_source: common_enums::ApplicationSource,
) -> RouterResult<()> {
    let runner = storage::ProcessTrackerRunner::DeleteTokenizeDataWorkflow;
    let process_tracker_id = format!("{runner}_{lookup_key}");
    let task = runner.to_string();
    let tag = ["BASILISK-V3"];
    let tracking_data = storage::TokenizeCoreWorkflow {
        lookup_key: lookup_key.to_owned(),
        pm,
    };
    let schedule_time = get_delete_tokenize_schedule_time(db, pm, 0)
        .await
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to obtain initial process tracker schedule time")?;

    let process_tracker_entry = storage::ProcessTrackerNew::new(
        process_tracker_id,
        &task,
        runner,
        tag,
        tracking_data,
        None,
        schedule_time,
        common_types::consts::API_VERSION,
        application_source,
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to construct delete tokenized data process tracker task")?;

    let response = db.insert_process(process_tracker_entry).await;
    response.map(|_| ()).or_else(|err| {
        if err.current_context().is_db_unique_violation() {
            Ok(())
        } else {
            Err(report!(errors::ApiErrorResponse::InternalServerError))
        }
    })
}

pub async fn start_tokenize_data_workflow(
    state: &routes::SessionState,
    tokenize_tracker: &storage::ProcessTracker,
) -> Result<(), errors::ProcessTrackerError> {
    let db = &*state.store;
    let delete_tokenize_data = serde_json::from_value::<storage::TokenizeCoreWorkflow>(
        tokenize_tracker.tracking_data.clone(),
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable_lazy(|| {
        format!(
            "unable to convert into DeleteTokenizeByTokenRequest {:?}",
            tokenize_tracker.tracking_data
        )
    })?;

    match delete_tokenized_data(state, &delete_tokenize_data.lookup_key).await {
        Ok(()) => {
            logger::info!("Card From locker deleted Successfully");
            //mark task as finished
            db.as_scheduler()
                .finish_process_with_business_status(
                    tokenize_tracker.clone(),
                    diesel_models::process_tracker::business_status::COMPLETED_BY_PT,
                )
                .await?;
        }
        Err(err) => {
            logger::error!("Err: Deleting Card From Locker : {:?}", err);
            retry_delete_tokenize(db, delete_tokenize_data.pm, tokenize_tracker.to_owned()).await?;
            metrics::RETRIED_DELETE_DATA_COUNT.add(1, &[]);
        }
    }
    Ok(())
}

pub async fn get_delete_tokenize_schedule_time(
    db: &dyn db::StorageInterface,
    pm: enums::PaymentMethod,
    retry_count: i32,
) -> Option<time::PrimitiveDateTime> {
    let redis_mapping = db::get_and_deserialize_key(
        db,
        &format!("pt_mapping_delete_{pm}_tokenize_data"),
        "PaymentMethodsPTMapping",
    )
    .await;
    let mapping = match redis_mapping {
        Ok(x) => x,
        Err(error) => {
            logger::info!(?error, "Redis Mapping Error");
            process_data::PaymentMethodsPTMapping::default()
        }
    };
    let time_delta = process_tracker_utils::get_pm_schedule_time(mapping, pm, retry_count);

    process_tracker_utils::get_time_from_delta(time_delta)
}

pub async fn retry_delete_tokenize(
    db: &dyn db::StorageInterface,
    pm: enums::PaymentMethod,
    pt: storage::ProcessTracker,
) -> Result<(), errors::ProcessTrackerError> {
    let schedule_time = get_delete_tokenize_schedule_time(db, pm, pt.retry_count + 1).await;

    match schedule_time {
        Some(s_time) => {
            let retry_schedule = db
                .as_scheduler()
                .retry_process(pt, s_time)
                .await
                .map_err(Into::into);
            metrics::TASKS_RESET_COUNT.add(
                1,
                router_env::metric_attributes!(("flow", "DeleteTokenizeData")),
            );
            retry_schedule
        }
        None => db
            .as_scheduler()
            .finish_process_with_business_status(
                pt,
                diesel_models::process_tracker::business_status::RETRIES_EXCEEDED,
            )
            .await
            .map_err(Into::into),
    }
}

// Fallback logic of old temp locker needs to be removed later
