// File: crates/router/src/core/payment_methods/surcharge_decision_configs.rs

use api_models::{
    payment_methods::SurchargeDetailsResponse,
    payments, routing,
    surcharge_decision_configs::{self, SurchargeDecisionConfigs, SurchargeDecisionManagerRecord},
};
#[cfg(feature = "v1")]
use common_utils::{ext_traits::StringExt, types as common_utils_types};
#[cfg(feature = "v2")]
use common_utils::{
    ext_traits::{OptionExt, StringExt},
    types as common_utils_types,
};
use error_stack::{self, ResultExt};
use euclid::{
    backend,
    backend::{inputs as dsl_inputs, EuclidBackend},
};
use router_env::{instrument, logger, tracing};
use serde::{Deserialize, Serialize};
use storage_impl::redis::cache::{self, SURCHARGE_CACHE};

use crate::{
    core::{
        errors::{self, ConditionalConfigError as ConfigError},
        payments::{
            conditional_configs::ConditionalConfigResult, routing::make_dsl_input_for_surcharge,
            types,
        },
    },
    db::StorageInterface,
    types::{
        storage::{self, payment_attempt::PaymentAttemptExt},
        transformers::ForeignTryFrom,
    },
    SessionState,
};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct VirInterpreterBackendCacheWrapper {
    cached_algorithm: backend::VirInterpreterBackend<SurchargeDecisionConfigs>,
    merchant_surcharge_configs: surcharge_decision_configs::MerchantSurchargeConfigs,
}

impl TryFrom<SurchargeDecisionManagerRecord> for VirInterpreterBackendCacheWrapper {
    type Error = error_stack::Report<ConfigError>;

    fn try_from(value: SurchargeDecisionManagerRecord) -> Result<Self, Self::Error> {
        let cached_algorithm = backend::VirInterpreterBackend::with_program(value.algorithm)
            .change_context(ConfigError::DslBackendInitError)
            .attach_printable("Error initializing DSL interpreter backend")?;
        let merchant_surcharge_configs = value.merchant_surcharge_configs;
        Ok(Self {
            cached_algorithm,
            merchant_surcharge_configs,
        })
    }
}

enum SurchargeSource {
    /// Surcharge will be generated through the surcharge rules
    Generate(VirInterpreterBackendCacheWrapper),
    /// Surcharge is predefined by the merchant through payment create request
    Predetermined(payments::RequestSurchargeDetails),
}

impl SurchargeSource {
    pub fn generate_surcharge_details_and_populate_surcharge_metadata(
        &self,
        backend_input: &backend::BackendInput,
        payment_attempt: &storage::PaymentAttempt,
        surcharge_metadata_and_key: (&mut types::SurchargeMetadata, types::SurchargeKey),
    ) -> ConditionalConfigResult<Option<types::SurchargeDetails>> {
        match self {
            Self::Generate(interpreter) => {
                let surcharge_output = execute_dsl_and_get_conditional_config(
                    backend_input.clone(),
                    &interpreter.cached_algorithm,
                )?;
                Ok(surcharge_output
                    .surcharge_details
                    .map(|surcharge_details| {
                        get_surcharge_details_from_surcharge_output(
                            surcharge_details,
                            payment_attempt,
                        )
                    })
                    .transpose()?
                    .inspect(|surcharge_details| {
                        let (surcharge_metadata, surcharge_key) = surcharge_metadata_and_key;
                        surcharge_metadata
                            .insert_surcharge_details(surcharge_key, surcharge_details.clone());
                    }))
            }
            Self::Predetermined(request_surcharge_details) => Ok(Some(
                types::SurchargeDetails::from((request_surcharge_details, payment_attempt)),
            )),
        }
    }
}

#[cfg(feature = "v2")]
pub async fn perform_surcharge_decision_management_for_payment_method_list(
    _state: &SessionState,
    _algorithm_ref: routing::RoutingAlgorithmRef,
    _payment_attempt: &storage::PaymentAttempt,
    _payment_intent: &storage::PaymentIntent,
    _billing_address: Option<payments::Address>,
    _response_payment_method_types: &mut [api_models::payment_methods::ResponsePaymentMethodsEnabled],
) -> ConditionalConfigResult<(
    types::SurchargeMetadata,
    surcharge_decision_configs::MerchantSurchargeConfigs,
)> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn perform_surcharge_decision_management_for_payment_method_list(
    state: &SessionState,
    algorithm_ref: routing::RoutingAlgorithmRef,
    payment_attempt: &storage::PaymentAttempt,
    payment_intent: &storage::PaymentIntent,
    billing_address: Option<hyperswitch_domain_models::address::Address>,
    response_payment_method_types: &mut [api_models::payment_methods::ResponsePaymentMethodsEnabled],
) -> ConditionalConfigResult<(
    types::SurchargeMetadata,
    surcharge_decision_configs::MerchantSurchargeConfigs,
)> {
    let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.attempt_id.clone());

    let (surcharge_source, merchant_surcharge_configs) = match (
        payment_attempt.get_surcharge_details(),
        algorithm_ref.surcharge_config_algo_id,
    ) {
        (Some(request_surcharge_details), _) => (
            SurchargeSource::Predetermined(request_surcharge_details),
            surcharge_decision_configs::MerchantSurchargeConfigs::default(),
        ),
        (None, Some(algorithm_id)) => {
            let cached_algo = ensure_algorithm_cached(
                &*state.store,
                &payment_attempt.merchant_id,
                algorithm_id.as_str(),
            )
            .await?;

            let merchant_surcharge_config = cached_algo.merchant_surcharge_configs.clone();
            (
                SurchargeSource::Generate(cached_algo),
                merchant_surcharge_config,
            )
        }
        (None, None) => {
            return Ok((
                surcharge_metadata,
                surcharge_decision_configs::MerchantSurchargeConfigs::default(),
            ))
        }
    };
    let surcharge_source_log_message = match &surcharge_source {
        SurchargeSource::Generate(_) => "Surcharge was calculated through surcharge rules",
        SurchargeSource::Predetermined(_) => "Surcharge was sent in payment create request",
    };
    logger::debug!(payment_method_list_surcharge_source = surcharge_source_log_message);

    let mut backend_input =
        make_dsl_input_for_surcharge(payment_attempt, payment_intent, billing_address)
            .change_context(ConfigError::InputConstructionError)?;

    for payment_methods_enabled in response_payment_method_types.iter_mut() {
        for payment_method_type_response in
            &mut payment_methods_enabled.payment_method_types.iter_mut()
        {
            let payment_method_type = payment_method_type_response.payment_method_type;
            backend_input.payment_method.payment_method_type = Some(payment_method_type);
            backend_input.payment_method.payment_method =
                Some(payment_methods_enabled.payment_method);

            if let Some(card_network_list) = &mut payment_method_type_response.card_networks {
                for card_network_type in card_network_list.iter_mut() {
                    backend_input.payment_method.card_network =
                        Some(card_network_type.card_network.clone());
                    let surcharge_details = surcharge_source
                        .generate_surcharge_details_and_populate_surcharge_metadata(
                            &backend_input,
                            payment_attempt,
                            (
                                &mut surcharge_metadata,
                                types::SurchargeKey::PaymentMethodData(
                                    payment_methods_enabled.payment_method,
                                    payment_method_type_response.payment_method_type,
                                    Some(card_network_type.card_network.clone()),
                                ),
                            ),
                        )?;
                    card_network_type.surcharge_details = surcharge_details
                        .map(|surcharge_details| {
                            SurchargeDetailsResponse::foreign_try_from((
                                &surcharge_details,
                                payment_attempt,
                            ))
                            .change_context(ConfigError::DslExecutionError)
                            .attach_printable("Error while constructing Surcharge response type")
                        })
                        .transpose()?;
                }
            } else {
                let surcharge_details = surcharge_source
                    .generate_surcharge_details_and_populate_surcharge_metadata(
                        &backend_input,
                        payment_attempt,
                        (
                            &mut surcharge_metadata,
                            types::SurchargeKey::PaymentMethodData(
                                payment_methods_enabled.payment_method,
                                payment_method_type_response.payment_method_type,
                                None,
                            ),
                        ),
                    )?;
                payment_method_type_response.surcharge_details = surcharge_details
                    .map(|surcharge_details| {
                        SurchargeDetailsResponse::foreign_try_from((
                            &surcharge_details,
                            payment_attempt,
                        ))
                        .change_context(ConfigError::DslExecutionError)
                        .attach_printable("Error while constructing Surcharge response type")
                    })
                    .transpose()?;
            }
        }
    }
    Ok((surcharge_metadata, merchant_surcharge_configs))
}

#[cfg(feature = "v1")]
pub async fn perform_surcharge_decision_management_for_session_flow(
    state: &SessionState,
    algorithm_ref: routing::RoutingAlgorithmRef,
    payment_attempt: &storage::PaymentAttempt,
    payment_intent: &storage::PaymentIntent,
    billing_address: Option<hyperswitch_domain_models::address::Address>,
    payment_method_type_list: &Vec<common_enums::PaymentMethodType>,
) -> ConditionalConfigResult<types::SurchargeMetadata> {
    let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.attempt_id.clone());
    let surcharge_source = match (
        payment_attempt.get_surcharge_details(),
        algorithm_ref.surcharge_config_algo_id,
    ) {
        (Some(request_surcharge_details), _) => {
            SurchargeSource::Predetermined(request_surcharge_details)
        }
        (None, Some(algorithm_id)) => {
            let cached_algo = ensure_algorithm_cached(
                &*state.store,
                &payment_attempt.merchant_id,
                algorithm_id.as_str(),
            )
            .await?;

            SurchargeSource::Generate(cached_algo)
        }
        (None, None) => return Ok(surcharge_metadata),
    };
    let mut backend_input =
        make_dsl_input_for_surcharge(payment_attempt, payment_intent, billing_address)
            .change_context(ConfigError::InputConstructionError)?;
    for payment_method_type in payment_method_type_list {
        backend_input.payment_method.payment_method_type = Some(*payment_method_type);
        // in case of session flow, payment_method will always be wallet
        backend_input.payment_method.payment_method = Some(payment_method_type.to_owned().into());
        surcharge_source.generate_surcharge_details_and_populate_surcharge_metadata(
            &backend_input,
            payment_attempt,
            (
                &mut surcharge_metadata,
                types::SurchargeKey::PaymentMethodData(
                    payment_method_type.to_owned().into(),
                    *payment_method_type,
                    None,
                ),
            ),
        )?;
    }
    Ok(surcharge_metadata)
}

#[cfg(feature = "v1")]
pub async fn perform_surcharge_decision_management_for_saved_cards(
    state: &SessionState,
    algorithm_ref: routing::RoutingAlgorithmRef,
    payment_attempt: &storage::PaymentAttempt,
    payment_intent: &storage::PaymentIntent,
    customer_payment_method_list: &mut [api_models::payment_methods::CustomerPaymentMethod],
) -> ConditionalConfigResult<types::SurchargeMetadata> {
    let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.attempt_id.clone());
    let surcharge_source = match (
        payment_attempt.get_surcharge_details(),
        algorithm_ref.surcharge_config_algo_id,
    ) {
        (Some(request_surcharge_details), _) => {
            SurchargeSource::Predetermined(request_surcharge_details)
        }
        (None, Some(algorithm_id)) => {
            let cached_algo = ensure_algorithm_cached(
                &*state.store,
                &payment_attempt.merchant_id,
                algorithm_id.as_str(),
            )
            .await?;

            SurchargeSource::Generate(cached_algo)
        }
        (None, None) => return Ok(surcharge_metadata),
    };
    let surcharge_source_log_message = match &surcharge_source {
        SurchargeSource::Generate(_) => "Surcharge was calculated through surcharge rules",
        SurchargeSource::Predetermined(_) => "Surcharge was sent in payment create request",
    };
    logger::debug!(customer_saved_card_list_surcharge_source = surcharge_source_log_message);
    let mut backend_input = make_dsl_input_for_surcharge(payment_attempt, payment_intent, None)
        .change_context(ConfigError::InputConstructionError)?;

    for customer_payment_method in customer_payment_method_list.iter_mut() {
        let payment_token = customer_payment_method.payment_token.clone();

        backend_input.payment_method.payment_method = Some(customer_payment_method.payment_method);
        backend_input.payment_method.payment_method_type =
            customer_payment_method.payment_method_type;

        let card_network = customer_payment_method
            .card
            .as_ref()
            .and_then(|card| card.scheme.as_ref())
            .map(|scheme| {
                scheme
                    .clone()
                    .parse_enum("CardNetwork")
                    .change_context(ConfigError::DslExecutionError)
            })
            .transpose()?;

        backend_input.payment_method.card_network = card_network;

        let surcharge_details = surcharge_source
            .generate_surcharge_details_and_populate_surcharge_metadata(
                &backend_input,
                payment_attempt,
                (
                    &mut surcharge_metadata,
                    types::SurchargeKey::Token(payment_token),
                ),
            )?;
        customer_payment_method.surcharge_details = surcharge_details
            .map(|surcharge_details| {
                SurchargeDetailsResponse::foreign_try_from((&surcharge_details, payment_attempt))
                    .change_context(ConfigError::DslParsingError)
            })
            .transpose()?;
    }
    Ok(surcharge_metadata)
}

// TODO: uncomment and resolve compiler error when required
// #[cfg(feature = "v2")]
// pub async fn perform_surcharge_decision_management_for_saved_cards(
//     state: &SessionState,
//     algorithm_ref: routing::RoutingAlgorithmRef,
//     payment_attempt: &storage::PaymentAttempt,
//     payment_intent: &storage::PaymentIntent,
//     customer_payment_method_list: &mut [api_models::payment_methods::CustomerPaymentMethod],
// ) -> ConditionalConfigResult<types::SurchargeMetadata> {
//     // let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.id.clone());
//     let mut surcharge_metadata = todo!();

//     let surcharge_source = match (
//         payment_attempt.get_surcharge_details(),
//         algorithm_ref.surcharge_config_algo_id,
//     ) {
//         (Some(request_surcharge_details), _) => {
//             SurchargeSource::Predetermined(request_surcharge_details)
//         }
//         (None, Some(algorithm_id)) => {
//             let cached_algo = ensure_algorithm_cached(
//                 &*state.store,
//                 &payment_attempt.merchant_id,
//                 algorithm_id.as_str(),
//             )
//             .await?;

//             SurchargeSource::Generate(cached_algo)
//         }
//         (None, None) => return Ok(surcharge_metadata),
//     };
//     let surcharge_source_log_message = match &surcharge_source {
//         SurchargeSource::Generate(_) => "Surcharge was calculated through surcharge rules",
//         SurchargeSource::Predetermined(_) => "Surcharge was sent in payment create request",
//     };
//     logger::debug!(customer_saved_card_list_surcharge_source = surcharge_source_log_message);
//     let mut backend_input = make_dsl_input_for_surcharge(payment_attempt, payment_intent, None)
//         .change_context(ConfigError::InputConstructionError)?;

//     for customer_payment_method in customer_payment_method_list.iter_mut() {
//         let payment_token = customer_payment_method
//             .payment_token
//             .clone()
//             .get_required_value("payment_token")
//             .change_context(ConfigError::InputConstructionError)?;

//         backend_input.payment_method.payment_method =
//             Some(customer_payment_method.payment_method_type);
//         backend_input.payment_method.payment_method_type =
//             customer_payment_method.payment_method_subtype;

//         let card_network = match customer_payment_method.payment_method_data.as_ref() {
//             Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => {
//                 card.card_network.clone()
//             }
//             _ => None,
//         };
//         backend_input.payment_method.card_network = card_network;

//         let surcharge_details = surcharge_source
//             .generate_surcharge_details_and_populate_surcharge_metadata(
//                 &backend_input,
//                 payment_attempt,
//                 (
//                     &mut surcharge_metadata,
//                     types::SurchargeKey::Token(payment_token),
//                 ),
//             )?;
//         customer_payment_method.surcharge_details = surcharge_details
//             .map(|surcharge_details| {
//                 SurchargeDetailsResponse::foreign_try_from((&surcharge_details, payment_attempt))
//                     .change_context(ConfigError::DslParsingError)
//             })
//             .transpose()?;
//     }
//     Ok(surcharge_metadata)
// }

#[cfg(feature = "v2")]
fn get_surcharge_details_from_surcharge_output(
    _surcharge_details: surcharge_decision_configs::SurchargeDetailsOutput,
    _payment_attempt: &storage::PaymentAttempt,
) -> ConditionalConfigResult<types::SurchargeDetails> {
    todo!()
}

#[cfg(feature = "v1")]
fn get_surcharge_details_from_surcharge_output(
    surcharge_details: surcharge_decision_configs::SurchargeDetailsOutput,
    payment_attempt: &storage::PaymentAttempt,
) -> ConditionalConfigResult<types::SurchargeDetails> {
    let surcharge_amount = match surcharge_details.surcharge.clone() {
        surcharge_decision_configs::SurchargeOutput::Fixed { amount } => amount,
        surcharge_decision_configs::SurchargeOutput::Rate(percentage) => percentage
            .apply_and_ceil_result(payment_attempt.net_amount.get_total_amount())
            .change_context(ConfigError::DslExecutionError)
            .attach_printable("Failed to Calculate surcharge amount by applying percentage")?,
    };
    let tax_on_surcharge_amount = surcharge_details
        .tax_on_surcharge
        .clone()
        .map(|tax_on_surcharge| {
            tax_on_surcharge
                .apply_and_ceil_result(surcharge_amount)
                .change_context(ConfigError::DslExecutionError)
                .attach_printable("Failed to Calculate tax amount")
        })
        .transpose()?
        .unwrap_or_default();
    Ok(types::SurchargeDetails {
        original_amount: payment_attempt.net_amount.get_order_amount(),
        surcharge: match surcharge_details.surcharge {
            surcharge_decision_configs::SurchargeOutput::Fixed { amount } => {
                common_utils_types::Surcharge::Fixed(amount)
            }
            surcharge_decision_configs::SurchargeOutput::Rate(percentage) => {
                common_utils_types::Surcharge::Rate(percentage)
            }
        },
        tax_on_surcharge: surcharge_details.tax_on_surcharge,
        surcharge_amount,
        tax_on_surcharge_amount,
    })
}

#[instrument(skip_all)]
pub async fn ensure_algorithm_cached(
    store: &dyn StorageInterface,
    merchant_id: &common_utils::id_type::MerchantId,
    algorithm_id: &str,
) -> ConditionalConfigResult<VirInterpreterBackendCacheWrapper> {
    let key = merchant_id.get_surcharge_dsk_key();

    let value_to_cache = || async {
        let config: diesel_models::Config = store.find_config_by_key(algorithm_id).await?;
        let record: SurchargeDecisionManagerRecord = config
            .config
            .parse_struct("Program")
            .change_context(errors::StorageError::DeserializationFailed)
            .attach_printable("Error parsing routing algorithm from configs")?;
        VirInterpreterBackendCacheWrapper::try_from(record)
            .change_context(errors::StorageError::ValueNotFound("Program".to_string()))
            .attach_printable("Error initializing DSL interpreter backend")
    };
    let interpreter = cache::get_or_populate_in_memory(
        store.get_cache_store().as_ref(),
        &key,
        value_to_cache,
        &SURCHARGE_CACHE,
    )
    .await
    .change_context(ConfigError::CacheMiss)
    .attach_printable("Unable to retrieve cached routing algorithm even after refresh")?;
    Ok(interpreter)
}

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


// File: crates/router/src/core/payment_methods/transformers.rs

pub use ::payment_methods::controller::{DataDuplicationCheck, DeleteCardResp};
#[cfg(feature = "v2")]
use api_models::payment_methods::PaymentMethodResponseItem;
use api_models::{enums as api_enums, payment_methods::Card};
use common_enums::CardNetwork;
use common_utils::{
    ext_traits::{Encode, StringExt},
    id_type,
    pii::Email,
    request::RequestContent,
};
use error_stack::ResultExt;
#[cfg(feature = "v2")]
use hyperswitch_domain_models::payment_method_data;
use josekit::jwe;
use router_env::RequestId;
use serde::{Deserialize, Serialize};

#[cfg(feature = "v2")]
use crate::types::{payment_methods as pm_types, transformers};
use crate::{
    configs::settings,
    core::errors::{self, CustomResult},
    headers,
    pii::{prelude::*, Secret},
    services::{api as services, encryption, EncryptionAlgorithm},
    types::{api, domain},
    utils::OptionExt,
};

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum StoreLockerReq {
    LockerCard(StoreCardReq),
    LockerGeneric(StoreGenericReq),
}

impl StoreLockerReq {
    pub fn update_requestor_card_reference(&mut self, card_reference: Option<String>) {
        match self {
            Self::LockerCard(c) => c.requestor_card_reference = card_reference,
            Self::LockerGeneric(_) => (),
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct StoreCardReq {
    pub merchant_id: id_type::MerchantId,
    pub merchant_customer_id: id_type::CustomerId,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub requestor_card_reference: Option<String>,
    pub card: Card,
    pub ttl: i64,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct StoreGenericReq {
    pub merchant_id: id_type::MerchantId,
    pub merchant_customer_id: id_type::CustomerId,
    #[serde(rename = "enc_card_data")]
    pub enc_data: String,
    pub ttl: i64,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct StoreCardResp {
    pub status: String,
    pub error_message: Option<String>,
    pub error_code: Option<String>,
    pub payload: Option<StoreCardRespPayload>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct StoreCardRespPayload {
    pub card_reference: String,
    pub duplication_check: Option<DataDuplicationCheck>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct CardReqBody {
    pub merchant_id: id_type::MerchantId,
    pub merchant_customer_id: id_type::CustomerId,
    pub card_reference: String,
}

#[cfg(feature = "v2")]
#[derive(Debug, Deserialize, Serialize)]
pub struct CardReqBodyV2 {
    pub merchant_id: id_type::MerchantId,
    pub merchant_customer_id: String, // Not changing this as it might lead to api contract failure
    pub card_reference: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct RetrieveCardResp {
    pub status: String,
    pub error_message: Option<String>,
    pub error_code: Option<String>,
    pub payload: Option<RetrieveCardRespPayload>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct RetrieveCardRespPayload {
    pub card: Option<Card>,
    pub enc_card_data: Option<Secret<String>>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AddCardRequest {
    pub card_number: cards::CardNumber,
    pub customer_id: id_type::CustomerId,
    pub card_exp_month: Secret<String>,
    pub card_exp_year: Secret<String>,
    pub merchant_id: id_type::MerchantId,
    pub email_address: Option<Email>,
    pub name_on_card: Option<Secret<String>>,
    pub nickname: Option<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AddCardResponse {
    pub card_id: String,
    pub external_id: String,
    pub card_fingerprint: Secret<String>,
    pub card_global_fingerprint: Secret<String>,
    #[serde(rename = "merchant_id")]
    pub merchant_id: Option<id_type::MerchantId>,
    pub card_number: Option<cards::CardNumber>,
    pub card_exp_year: Option<Secret<String>>,
    pub card_exp_month: Option<Secret<String>>,
    pub name_on_card: Option<Secret<String>>,
    pub nickname: Option<String>,
    pub customer_id: Option<id_type::CustomerId>,
    pub duplicate: Option<bool>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AddPaymentMethodResponse {
    pub payment_method_id: String,
    pub external_id: String,
    #[serde(rename = "merchant_id")]
    pub merchant_id: Option<id_type::MerchantId>,
    pub nickname: Option<String>,
    pub customer_id: Option<id_type::CustomerId>,
    pub duplicate: Option<bool>,
    pub payment_method_data: Secret<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct GetPaymentMethodResponse {
    pub payment_method: AddPaymentMethodResponse,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct GetCardResponse {
    pub card: AddCardResponse,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GetCard<'a> {
    merchant_id: &'a str,
    card_id: &'a str,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DeleteCardResponse {
    pub card_id: Option<String>,
    pub external_id: Option<String>,
    pub card_isin: Option<Secret<String>>,
    pub status: String,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(transparent)]
pub struct PaymentMethodMetadata {
    pub payment_method_tokenization: std::collections::HashMap<String, String>,
}

pub fn get_dotted_jwe(jwe: encryption::JweBody) -> String {
    let header = jwe.header;
    let encryption_key = jwe.encrypted_key;
    let iv = jwe.iv;
    let encryption_payload = jwe.encrypted_payload;
    let tag = jwe.tag;
    format!("{header}.{encryption_key}.{iv}.{encryption_payload}.{tag}")
}

pub fn get_dotted_jws(jws: encryption::JwsBody) -> String {
    let header = jws.header;
    let payload = jws.payload;
    let signature = jws.signature;
    format!("{header}.{payload}.{signature}")
}

pub async fn get_decrypted_response_payload(
    jwekey: &settings::Jwekey,
    jwe_body: encryption::JweBody,
    locker_choice: Option<api_enums::LockerChoice>,
    decryption_scheme: settings::DecryptionScheme,
) -> CustomResult<String, errors::VaultError> {
    let target_locker = locker_choice.unwrap_or(api_enums::LockerChoice::HyperswitchCardVault);

    let public_key = match target_locker {
        api_enums::LockerChoice::HyperswitchCardVault => {
            jwekey.vault_encryption_key.peek().as_bytes()
        }
    };

    let private_key = jwekey.vault_private_key.peek().as_bytes();

    let jwt = get_dotted_jwe(jwe_body);
    let alg = match decryption_scheme {
        settings::DecryptionScheme::RsaOaep => jwe::RSA_OAEP,
        settings::DecryptionScheme::RsaOaep256 => jwe::RSA_OAEP_256,
    };

    let jwe_decrypted = encryption::decrypt_jwe(
        &jwt,
        encryption::KeyIdCheck::SkipKeyIdCheck,
        private_key,
        alg,
    )
    .await
    .change_context(errors::VaultError::SaveCardFailed)
    .attach_printable("Jwe Decryption failed for JweBody for vault")?;

    let jws = jwe_decrypted
        .parse_struct("JwsBody")
        .change_context(errors::VaultError::ResponseDeserializationFailed)?;
    let jws_body = get_dotted_jws(jws);

    encryption::verify_sign(jws_body, public_key)
        .change_context(errors::VaultError::SaveCardFailed)
        .attach_printable("Jws Decryption failed for JwsBody for vault")
}

pub async fn get_decrypted_vault_response_payload(
    jwekey: &settings::Jwekey,
    jwe_body: encryption::JweBody,
    decryption_scheme: settings::DecryptionScheme,
) -> CustomResult<String, errors::VaultError> {
    let public_key = jwekey.vault_encryption_key.peek().as_bytes();

    let private_key = jwekey.vault_private_key.peek().as_bytes();

    let jwt = get_dotted_jwe(jwe_body);
    let alg = match decryption_scheme {
        settings::DecryptionScheme::RsaOaep => jwe::RSA_OAEP,
        settings::DecryptionScheme::RsaOaep256 => jwe::RSA_OAEP_256,
    };

    let jwe_decrypted = encryption::decrypt_jwe(
        &jwt,
        encryption::KeyIdCheck::SkipKeyIdCheck,
        private_key,
        alg,
    )
    .await
    .change_context(errors::VaultError::SaveCardFailed)
    .attach_printable("Jwe Decryption failed for JweBody for vault")?;

    let jws = jwe_decrypted
        .parse_struct("JwsBody")
        .change_context(errors::VaultError::ResponseDeserializationFailed)?;
    let jws_body = get_dotted_jws(jws);

    encryption::verify_sign(jws_body, public_key)
        .change_context(errors::VaultError::SaveCardFailed)
        .attach_printable("Jws Decryption failed for JwsBody for vault")
}

#[cfg(feature = "v2")]
pub async fn create_jwe_body_for_vault(
    jwekey: &settings::Jwekey,
    jws: &str,
) -> CustomResult<encryption::JweBody, errors::VaultError> {
    let jws_payload: Vec<&str> = jws.split('.').collect();

    let generate_jws_body = |payload: Vec<&str>| -> Option<encryption::JwsBody> {
        Some(encryption::JwsBody {
            header: payload.first()?.to_string(),
            payload: payload.get(1)?.to_string(),
            signature: payload.get(2)?.to_string(),
        })
    };

    let jws_body =
        generate_jws_body(jws_payload).ok_or(errors::VaultError::RequestEncryptionFailed)?;

    let payload = jws_body
        .encode_to_vec()
        .change_context(errors::VaultError::RequestEncodingFailed)?;

    let public_key = jwekey.vault_encryption_key.peek().as_bytes();

    let jwe_encrypted =
        encryption::encrypt_jwe(&payload, public_key, EncryptionAlgorithm::A256GCM, None)
            .await
            .change_context(errors::VaultError::SaveCardFailed)
            .attach_printable("Error on jwe encrypt")?;
    let jwe_payload: Vec<&str> = jwe_encrypted.split('.').collect();

    let generate_jwe_body = |payload: Vec<&str>| -> Option<encryption::JweBody> {
        Some(encryption::JweBody {
            header: payload.first()?.to_string(),
            iv: payload.get(2)?.to_string(),
            encrypted_payload: payload.get(3)?.to_string(),
            tag: payload.get(4)?.to_string(),
            encrypted_key: payload.get(1)?.to_string(),
        })
    };

    let jwe_body =
        generate_jwe_body(jwe_payload).ok_or(errors::VaultError::RequestEncodingFailed)?;

    Ok(jwe_body)
}

pub async fn mk_basilisk_req(
    jwekey: &settings::Jwekey,
    jws: &str,
    locker_choice: api_enums::LockerChoice,
) -> CustomResult<encryption::JweBody, errors::VaultError> {
    let jws_payload: Vec<&str> = jws.split('.').collect();

    let generate_jws_body = |payload: Vec<&str>| -> Option<encryption::JwsBody> {
        Some(encryption::JwsBody {
            header: payload.first()?.to_string(),
            payload: payload.get(1)?.to_string(),
            signature: payload.get(2)?.to_string(),
        })
    };

    let jws_body = generate_jws_body(jws_payload).ok_or(errors::VaultError::SaveCardFailed)?;

    let payload = jws_body
        .encode_to_vec()
        .change_context(errors::VaultError::SaveCardFailed)?;

    let public_key = match locker_choice {
        api_enums::LockerChoice::HyperswitchCardVault => {
            jwekey.vault_encryption_key.peek().as_bytes()
        }
    };

    let jwe_encrypted =
        encryption::encrypt_jwe(&payload, public_key, EncryptionAlgorithm::A256GCM, None)
            .await
            .change_context(errors::VaultError::SaveCardFailed)
            .attach_printable("Error on jwe encrypt")?;
    let jwe_payload: Vec<&str> = jwe_encrypted.split('.').collect();

    let generate_jwe_body = |payload: Vec<&str>| -> Option<encryption::JweBody> {
        Some(encryption::JweBody {
            header: payload.first()?.to_string(),
            iv: payload.get(2)?.to_string(),
            encrypted_payload: payload.get(3)?.to_string(),
            tag: payload.get(4)?.to_string(),
            encrypted_key: payload.get(1)?.to_string(),
        })
    };

    let jwe_body = generate_jwe_body(jwe_payload).ok_or(errors::VaultError::SaveCardFailed)?;

    Ok(jwe_body)
}

pub async fn mk_add_locker_request_hs(
    jwekey: &settings::Jwekey,
    locker: &settings::Locker,
    payload: &StoreLockerReq,
    locker_choice: api_enums::LockerChoice,
    tenant_id: id_type::TenantId,
    request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
    let payload = payload
        .encode_to_vec()
        .change_context(errors::VaultError::RequestEncodingFailed)?;

    let private_key = jwekey.vault_private_key.peek().as_bytes();

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

    let jwe_payload = mk_basilisk_req(jwekey, &jws, locker_choice).await?;
    let mut url = match locker_choice {
        api_enums::LockerChoice::HyperswitchCardVault => locker.host.to_owned(),
    };
    url.push_str("/cards/add");
    let mut request = services::Request::new(services::Method::Post, &url);
    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::X_TENANT_ID,
        tenant_id.get_string_repr().to_owned().into(),
    );
    if let Some(req_id) = request_id {
        request.add_header(headers::X_REQUEST_ID, req_id.to_string().into());
    }
    request.set_body(RequestContent::Json(Box::new(jwe_payload)));
    Ok(request)
}

#[cfg(all(feature = "v1", feature = "payouts"))]
pub fn mk_add_bank_response_hs(
    bank: api::BankPayout,
    bank_reference: String,
    req: api::PaymentMethodCreate,
    merchant_id: &id_type::MerchantId,
) -> api::PaymentMethodResponse {
    api::PaymentMethodResponse {
        merchant_id: merchant_id.to_owned(),
        customer_id: req.customer_id,
        payment_method_id: bank_reference,
        payment_method: req.payment_method,
        payment_method_type: req.payment_method_type,
        bank_transfer: Some(bank),
        card: None,
        metadata: req.metadata,
        created: Some(common_utils::date_time::now()),
        recurring_enabled: Some(false),           // [#256]
        installment_payment_enabled: Some(false), // #[#256]
        payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
        last_used_at: Some(common_utils::date_time::now()),
        client_secret: None,
    }
}

#[cfg(all(feature = "v2", feature = "payouts"))]
pub fn mk_add_bank_response_hs(
    _bank: api::BankPayout,
    _bank_reference: String,
    _req: api::PaymentMethodCreate,
    _merchant_id: &id_type::MerchantId,
) -> api::PaymentMethodResponse {
    todo!()
}

#[cfg(feature = "v1")]
pub fn mk_add_card_response_hs(
    card: api::CardDetail,
    card_reference: String,
    req: api::PaymentMethodCreate,
    merchant_id: &id_type::MerchantId,
) -> api::PaymentMethodResponse {
    let card_number = card.card_number.clone();
    let last4_digits = card_number.get_last4();
    let card_isin = card_number.get_card_isin();

    let card = api::CardDetailFromLocker {
        scheme: card
            .card_network
            .clone()
            .map(|card_network| card_network.to_string()),
        last4_digits: Some(last4_digits),
        issuer_country: card.card_issuing_country,
        card_number: Some(card.card_number.clone()),
        expiry_month: Some(card.card_exp_month.clone()),
        expiry_year: Some(card.card_exp_year.clone()),
        card_token: None,
        card_fingerprint: None,
        card_holder_name: card.card_holder_name.clone(),
        nick_name: card.nick_name.clone(),
        card_isin: Some(card_isin),
        card_issuer: card.card_issuer,
        card_network: card.card_network,
        card_type: card.card_type,
        saved_to_locker: true,
    };
    api::PaymentMethodResponse {
        merchant_id: merchant_id.to_owned(),
        customer_id: req.customer_id,
        payment_method_id: card_reference,
        payment_method: req.payment_method,
        payment_method_type: req.payment_method_type,
        #[cfg(feature = "payouts")]
        bank_transfer: None,
        card: Some(card),
        metadata: req.metadata,
        created: Some(common_utils::date_time::now()),
        recurring_enabled: Some(false),           // [#256]
        installment_payment_enabled: Some(false), // #[#256]
        payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]),
        last_used_at: Some(common_utils::date_time::now()), // [#256]
        client_secret: req.client_secret,
    }
}

#[cfg(feature = "v2")]
pub fn mk_add_card_response_hs(
    card: api::CardDetail,
    card_reference: String,
    req: api::PaymentMethodCreate,
    merchant_id: &id_type::MerchantId,
) -> api::PaymentMethodResponse {
    todo!()
}

#[cfg(feature = "v2")]
pub fn generate_pm_vaulting_req_from_update_request(
    pm_create: domain::PaymentMethodVaultingData,
    pm_update: api::PaymentMethodUpdateData,
) -> domain::PaymentMethodVaultingData {
    match (pm_create, pm_update) {
        (
            domain::PaymentMethodVaultingData::Card(card_create),
            api::PaymentMethodUpdateData::Card(update_card),
        ) => domain::PaymentMethodVaultingData::Card(api::CardDetail {
            card_number: card_create.card_number,
            card_exp_month: card_create.card_exp_month,
            card_exp_year: card_create.card_exp_year,
            card_issuing_country: card_create.card_issuing_country,
            card_network: card_create.card_network,
            card_issuer: card_create.card_issuer,
            card_type: card_create.card_type,
            card_holder_name: update_card
                .card_holder_name
                .or(card_create.card_holder_name),
            nick_name: update_card.nick_name.or(card_create.nick_name),
            card_cvc: None,
        }),
        _ => todo!(), //todo! - since support for network tokenization is not added PaymentMethodUpdateData. should be handled later.
    }
}

#[cfg(feature = "v2")]
pub fn generate_payment_method_response(
    payment_method: &domain::PaymentMethod,
    single_use_token: &Option<payment_method_data::SingleUsePaymentMethodToken>,
) -> errors::RouterResult<api::PaymentMethodResponse> {
    let pmd = payment_method
        .payment_method_data
        .clone()
        .map(|data| data.into_inner())
        .and_then(|data| match data {
            api::PaymentMethodsData::Card(card) => {
                Some(api::PaymentMethodResponseData::Card(card.into()))
            }
            _ => None,
        });
    let mut connector_tokens = payment_method
        .connector_mandate_details
        .as_ref()
        .and_then(|connector_token_details| connector_token_details.payments.clone())
        .map(|payment_token_details| payment_token_details.0)
        .map(|payment_token_details| {
            payment_token_details
                .into_iter()
                .map(transformers::ForeignFrom::foreign_from)
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();

    if let Some(token) = single_use_token {
        let connector_token_single_use = transformers::ForeignFrom::foreign_from(token);
        connector_tokens.push(connector_token_single_use);
    }
    let connector_tokens = if connector_tokens.is_empty() {
        None
    } else {
        Some(connector_tokens)
    };

    let network_token_pmd = payment_method
        .network_token_payment_method_data
        .clone()
        .map(|data| data.into_inner())
        .and_then(|data| match data {
            domain::PaymentMethodsData::NetworkToken(token) => {
                Some(api::NetworkTokenDetailsPaymentMethod::from(token))
            }
            _ => None,
        });

    let network_token = network_token_pmd.map(|pmd| api::NetworkTokenResponse {
        payment_method_data: pmd,
    });

    let resp = api::PaymentMethodResponse {
        merchant_id: payment_method.merchant_id.to_owned(),
        customer_id: payment_method.customer_id.to_owned(),
        id: payment_method.id.to_owned(),
        payment_method_type: payment_method.get_payment_method_type(),
        payment_method_subtype: payment_method.get_payment_method_subtype(),
        created: Some(payment_method.created_at),
        recurring_enabled: Some(false),
        last_used_at: Some(payment_method.last_used_at),
        payment_method_data: pmd,
        connector_tokens,
        network_token,
    };

    Ok(resp)
}

#[allow(clippy::too_many_arguments)]
pub async fn mk_get_card_request_hs(
    jwekey: &settings::Jwekey,
    locker: &settings::Locker,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    card_reference: &str,
    locker_choice: Option<api_enums::LockerChoice>,
    tenant_id: id_type::TenantId,
    request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
    let merchant_customer_id = customer_id.to_owned();
    let card_req_body = CardReqBody {
        merchant_id: merchant_id.to_owned(),
        merchant_customer_id,
        card_reference: card_reference.to_owned(),
    };
    let payload = card_req_body
        .encode_to_vec()
        .change_context(errors::VaultError::RequestEncodingFailed)?;

    let private_key = jwekey.vault_private_key.peek().as_bytes();

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

    let target_locker = locker_choice.unwrap_or(api_enums::LockerChoice::HyperswitchCardVault);

    let jwe_payload = mk_basilisk_req(jwekey, &jws, target_locker).await?;
    let mut url = match target_locker {
        api_enums::LockerChoice::HyperswitchCardVault => locker.host.to_owned(),
    };
    url.push_str("/cards/retrieve");
    let mut request = services::Request::new(services::Method::Post, &url);
    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::X_TENANT_ID,
        tenant_id.get_string_repr().to_owned().into(),
    );
    if let Some(req_id) = request_id {
        request.add_header(headers::X_REQUEST_ID, req_id.to_string().into());
    }

    request.set_body(RequestContent::Json(Box::new(jwe_payload)));
    Ok(request)
}

pub fn mk_get_card_request(
    locker: &settings::Locker,
    locker_id: &'static str,
    card_id: &'static str,
) -> CustomResult<services::Request, errors::VaultError> {
    let get_card_req = GetCard {
        merchant_id: locker_id,
        card_id,
    };

    let mut url = locker.host.to_owned();
    url.push_str("/card/getCard");
    let mut request = services::Request::new(services::Method::Post, &url);
    request.set_body(RequestContent::FormUrlEncoded(Box::new(get_card_req)));
    Ok(request)
}

pub fn mk_get_card_response(card: GetCardResponse) -> errors::RouterResult<Card> {
    Ok(Card {
        card_number: card.card.card_number.get_required_value("card_number")?,
        name_on_card: card.card.name_on_card,
        card_exp_month: card
            .card
            .card_exp_month
            .get_required_value("card_exp_month")?,
        card_exp_year: card
            .card
            .card_exp_year
            .get_required_value("card_exp_year")?,
        card_brand: None,
        card_isin: None,
        nick_name: card.card.nickname,
    })
}

pub async fn mk_delete_card_request_hs(
    jwekey: &settings::Jwekey,
    locker: &settings::Locker,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    card_reference: &str,
    tenant_id: id_type::TenantId,
    request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
    let merchant_customer_id = customer_id.to_owned();
    let card_req_body = CardReqBody {
        merchant_id: merchant_id.to_owned(),
        merchant_customer_id,
        card_reference: card_reference.to_owned(),
    };
    let payload = card_req_body
        .encode_to_vec()
        .change_context(errors::VaultError::RequestEncodingFailed)?;

    let private_key = jwekey.vault_private_key.peek().as_bytes();

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

    let jwe_payload =
        mk_basilisk_req(jwekey, &jws, api_enums::LockerChoice::HyperswitchCardVault).await?;

    let mut url = locker.host.to_owned();
    url.push_str("/cards/delete");
    let mut request = services::Request::new(services::Method::Post, &url);
    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::X_TENANT_ID,
        tenant_id.get_string_repr().to_owned().into(),
    );
    if let Some(req_id) = request_id {
        request.add_header(headers::X_REQUEST_ID, req_id.to_string().into());
    }

    request.set_body(RequestContent::Json(Box::new(jwe_payload)));
    Ok(request)
}

// Need to fix this once we start moving to v2 completion
#[cfg(feature = "v2")]
pub async fn mk_delete_card_request_hs_by_id(
    jwekey: &settings::Jwekey,
    locker: &settings::Locker,
    id: &String,
    merchant_id: &id_type::MerchantId,
    card_reference: &str,
    tenant_id: id_type::TenantId,
    request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
    let merchant_customer_id = id.to_owned();
    let card_req_body = CardReqBodyV2 {
        merchant_id: merchant_id.to_owned(),
        merchant_customer_id,
        card_reference: card_reference.to_owned(),
    };
    let payload = card_req_body
        .encode_to_vec()
        .change_context(errors::VaultError::RequestEncodingFailed)?;

    let private_key = jwekey.vault_private_key.peek().as_bytes();

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

    let jwe_payload =
        mk_basilisk_req(jwekey, &jws, api_enums::LockerChoice::HyperswitchCardVault).await?;

    let mut url = locker.host.to_owned();
    url.push_str("/cards/delete");
    let mut request = services::Request::new(services::Method::Post, &url);
    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::X_TENANT_ID,
        tenant_id.get_string_repr().to_owned().into(),
    );
    if let Some(req_id) = request_id {
        request.add_header(headers::X_REQUEST_ID, req_id.to_string().into());
    }

    request.set_body(RequestContent::Json(Box::new(jwe_payload)));
    Ok(request)
}

pub fn mk_delete_card_response(
    response: DeleteCardResponse,
) -> errors::RouterResult<DeleteCardResp> {
    Ok(DeleteCardResp {
        status: response.status,
        error_message: None,
        error_code: None,
    })
}

#[cfg(feature = "v1")]
pub fn get_card_detail(
    pm: &domain::PaymentMethod,
    response: Card,
) -> CustomResult<api::CardDetailFromLocker, errors::VaultError> {
    let card_number = response.card_number;
    let last4_digits = card_number.clone().get_last4();
    //fetch form card bin

    let card_detail = api::CardDetailFromLocker {
        scheme: pm.scheme.to_owned(),
        issuer_country: pm.issuer_country.clone(),
        last4_digits: Some(last4_digits),
        card_number: Some(card_number),
        expiry_month: Some(response.card_exp_month),
        expiry_year: Some(response.card_exp_year),
        card_token: None,
        card_fingerprint: None,
        card_holder_name: response.name_on_card,
        nick_name: response.nick_name.map(Secret::new),
        card_isin: None,
        card_issuer: None,
        card_network: None,
        card_type: None,
        saved_to_locker: true,
    };
    Ok(card_detail)
}

#[cfg(feature = "v2")]
pub fn get_card_detail(
    _pm: &domain::PaymentMethod,
    response: Card,
) -> CustomResult<api::CardDetailFromLocker, errors::VaultError> {
    let card_number = response.card_number;
    let last4_digits = card_number.clone().get_last4();
    //fetch form card bin

    let card_detail = api::CardDetailFromLocker {
        issuer_country: None,
        last4_digits: Some(last4_digits),
        card_number: Some(card_number),
        expiry_month: Some(response.card_exp_month),
        expiry_year: Some(response.card_exp_year),
        card_fingerprint: None,
        card_holder_name: response.name_on_card,
        nick_name: response.nick_name.map(Secret::new),
        card_isin: None,
        card_issuer: None,
        card_network: None,
        card_type: None,
        saved_to_locker: true,
    };
    Ok(card_detail)
}

//------------------------------------------------TokenizeService------------------------------------------------
pub fn mk_crud_locker_request(
    locker: &settings::Locker,
    path: &str,
    req: api::TokenizePayloadEncrypted,
    tenant_id: id_type::TenantId,
    request_id: Option<RequestId>,
) -> CustomResult<services::Request, errors::VaultError> {
    let mut url = locker.basilisk_host.to_owned();
    url.push_str(path);
    let mut request = services::Request::new(services::Method::Post, &url);
    request.add_default_headers();
    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::X_TENANT_ID,
        tenant_id.get_string_repr().to_owned().into(),
    );
    if let Some(req_id) = request_id {
        request.add_header(headers::X_REQUEST_ID, req_id.to_string().into());
    }

    request.set_body(RequestContent::Json(Box::new(req)));
    Ok(request)
}

#[allow(clippy::too_many_arguments)]
pub fn mk_card_value1(
    card_number: cards::CardNumber,
    exp_year: String,
    exp_month: String,
    name_on_card: Option<String>,
    nickname: Option<String>,
    card_last_four: Option<String>,
    card_token: Option<String>,
    card_network: Option<CardNetwork>,
) -> CustomResult<String, errors::VaultError> {
    let value1 = api::TokenizedCardValue1 {
        card_number: card_number.peek().clone(),
        exp_year,
        exp_month,
        name_on_card,
        nickname,
        card_last_four,
        card_token,
        card_network,
    };
    let value1_req = value1
        .encode_to_string_of_json()
        .change_context(errors::VaultError::FetchCardFailed)?;
    Ok(value1_req)
}

pub fn mk_card_value2(
    card_security_code: Option<String>,
    card_fingerprint: Option<String>,
    external_id: Option<String>,
    customer_id: Option<id_type::CustomerId>,
    payment_method_id: Option<String>,
) -> CustomResult<String, errors::VaultError> {
    let value2 = api::TokenizedCardValue2 {
        card_security_code,
        card_fingerprint,
        external_id,
        customer_id,
        payment_method_id,
    };
    let value2_req = value2
        .encode_to_string_of_json()
        .change_context(errors::VaultError::FetchCardFailed)?;
    Ok(value2_req)
}

#[cfg(feature = "v2")]
impl transformers::ForeignTryFrom<(domain::PaymentMethod, String)>
    for api::CustomerPaymentMethodResponseItem
{
    type Error = error_stack::Report<errors::ValidationError>;

    fn foreign_try_from(
        (item, payment_token): (domain::PaymentMethod, String),
    ) -> Result<Self, Self::Error> {
        // For payment methods that are active we should always have the payment method subtype
        let payment_method_subtype =
            item.payment_method_subtype
                .ok_or(errors::ValidationError::MissingRequiredField {
                    field_name: "payment_method_subtype".to_string(),
                })?;

        // For payment methods that are active we should always have the payment method type
        let payment_method_type =
            item.payment_method_type
                .ok_or(errors::ValidationError::MissingRequiredField {
                    field_name: "payment_method_type".to_string(),
                })?;

        let payment_method_data = item
            .payment_method_data
            .map(|payment_method_data| payment_method_data.into_inner())
            .map(|payment_method_data| match payment_method_data {
                api_models::payment_methods::PaymentMethodsData::Card(
                    card_details_payment_method,
                ) => {
                    let card_details = api::CardDetailFromLocker::from(card_details_payment_method);
                    api_models::payment_methods::PaymentMethodListData::Card(card_details)
                }
                api_models::payment_methods::PaymentMethodsData::BankDetails(..) => todo!(),
                api_models::payment_methods::PaymentMethodsData::WalletDetails(..) => {
                    todo!()
                }
            });

        let payment_method_billing = item
            .payment_method_billing_address
            .clone()
            .map(|billing| billing.into_inner())
            .map(From::from);

        // TODO: check how we can get this field
        let recurring_enabled = true;

        Ok(Self {
            id: item.id,
            customer_id: item.customer_id,
            payment_method_type,
            payment_method_subtype,
            created: item.created_at,
            last_used_at: item.last_used_at,
            recurring_enabled,
            payment_method_data,
            bank: None,
            requires_cvv: true,
            is_default: false,
            billing: payment_method_billing,
            payment_token,
        })
    }
}

#[cfg(feature = "v2")]
impl transformers::ForeignTryFrom<domain::PaymentMethod> for PaymentMethodResponseItem {
    type Error = error_stack::Report<errors::ValidationError>;

    fn foreign_try_from(item: domain::PaymentMethod) -> Result<Self, Self::Error> {
        // For payment methods that are active we should always have the payment method subtype
        let payment_method_subtype =
            item.payment_method_subtype
                .ok_or(errors::ValidationError::MissingRequiredField {
                    field_name: "payment_method_subtype".to_string(),
                })?;

        // For payment methods that are active we should always have the payment method type
        let payment_method_type =
            item.payment_method_type
                .ok_or(errors::ValidationError::MissingRequiredField {
                    field_name: "payment_method_type".to_string(),
                })?;

        let payment_method_data = item
            .payment_method_data
            .map(|payment_method_data| payment_method_data.into_inner())
            .map(|payment_method_data| match payment_method_data {
                api_models::payment_methods::PaymentMethodsData::Card(
                    card_details_payment_method,
                ) => {
                    let card_details = api::CardDetailFromLocker::from(card_details_payment_method);
                    api_models::payment_methods::PaymentMethodListData::Card(card_details)
                }
                api_models::payment_methods::PaymentMethodsData::BankDetails(..) => todo!(),
                api_models::payment_methods::PaymentMethodsData::WalletDetails(..) => {
                    todo!()
                }
            });

        let payment_method_billing = item
            .payment_method_billing_address
            .clone()
            .map(|billing| billing.into_inner())
            .map(From::from);

        let network_token_pmd = item
            .network_token_payment_method_data
            .clone()
            .map(|data| data.into_inner())
            .and_then(|data| match data {
                domain::PaymentMethodsData::NetworkToken(token) => {
                    Some(api::NetworkTokenDetailsPaymentMethod::from(token))
                }
                _ => None,
            });

        let network_token_resp = network_token_pmd.map(|pmd| api::NetworkTokenResponse {
            payment_method_data: pmd,
        });

        // TODO: check how we can get this field
        let recurring_enabled = Some(true);

        let psp_tokenization_enabled = item.connector_mandate_details.and_then(|details| {
            details.payments.map(|payments| {
                payments.values().any(|connector_token_reference| {
                    connector_token_reference.connector_token_status
                        == api_enums::ConnectorTokenStatus::Active
                })
            })
        });

        Ok(Self {
            id: item.id,
            customer_id: item.customer_id,
            payment_method_type,
            payment_method_subtype,
            created: item.created_at,
            last_used_at: item.last_used_at,
            recurring_enabled,
            payment_method_data,
            bank: None,
            requires_cvv: true,
            is_default: false,
            billing: payment_method_billing,
            network_tokenization: network_token_resp,
            psp_tokenization_enabled: psp_tokenization_enabled.unwrap_or(false),
        })
    }
}

#[cfg(feature = "v2")]
pub fn generate_payment_method_session_response(
    payment_method_session: hyperswitch_domain_models::payment_methods::PaymentMethodSession,
    client_secret: Secret<String>,
    associated_payment: Option<api_models::payments::PaymentsResponse>,
    tokenization_service_response: Option<api_models::tokenization::GenericTokenizationResponse>,
) -> api_models::payment_methods::PaymentMethodSessionResponse {
    let next_action = associated_payment
        .as_ref()
        .and_then(|payment| payment.next_action.clone());

    let authentication_details =
        associated_payment.map(
            |payment| api_models::payment_methods::AuthenticationDetails {
                status: payment.status,
                error: payment.error,
            },
        );

    let token_id = tokenization_service_response
        .as_ref()
        .map(|tokenization_service_response| tokenization_service_response.id.clone());

    api_models::payment_methods::PaymentMethodSessionResponse {
        id: payment_method_session.id,
        customer_id: payment_method_session.customer_id,
        billing: payment_method_session
            .billing
            .map(|address| address.into_inner())
            .map(From::from),
        psp_tokenization: payment_method_session.psp_tokenization,
        network_tokenization: payment_method_session.network_tokenization,
        tokenization_data: payment_method_session.tokenization_data,
        expires_at: payment_method_session.expires_at,
        client_secret,
        next_action,
        return_url: payment_method_session.return_url,
        associated_payment_methods: payment_method_session.associated_payment_methods,
        authentication_details,
        associated_token_id: token_id,
    }
}

#[cfg(feature = "v2")]
impl transformers::ForeignFrom<api_models::payment_methods::ConnectorTokenDetails>
    for hyperswitch_domain_models::mandates::ConnectorTokenReferenceRecord
{
    fn foreign_from(item: api_models::payment_methods::ConnectorTokenDetails) -> Self {
        let api_models::payment_methods::ConnectorTokenDetails {
            status,
            connector_token_request_reference_id,
            original_payment_authorized_amount,
            original_payment_authorized_currency,
            metadata,
            token,
            ..
        } = item;

        Self {
            connector_token: token.expose().clone(),
            // TODO: check why do we need this field
            payment_method_subtype: None,
            original_payment_authorized_amount,
            original_payment_authorized_currency,
            metadata,
            connector_token_status: status,
            connector_token_request_reference_id,
        }
    }
}

#[cfg(feature = "v2")]
impl
    transformers::ForeignFrom<(
        id_type::MerchantConnectorAccountId,
        hyperswitch_domain_models::mandates::ConnectorTokenReferenceRecord,
    )> for api_models::payment_methods::ConnectorTokenDetails
{
    fn foreign_from(
        (connector_id, mandate_reference_record): (
            id_type::MerchantConnectorAccountId,
            hyperswitch_domain_models::mandates::ConnectorTokenReferenceRecord,
        ),
    ) -> Self {
        let hyperswitch_domain_models::mandates::ConnectorTokenReferenceRecord {
            connector_token_request_reference_id,
            original_payment_authorized_amount,
            original_payment_authorized_currency,
            metadata,
            connector_token,
            connector_token_status,
            ..
        } = mandate_reference_record;

        Self {
            connector_id,
            status: connector_token_status,
            connector_token_request_reference_id,
            original_payment_authorized_amount,
            original_payment_authorized_currency,
            metadata,
            token: Secret::new(connector_token),
            // Token that is derived from payments mandate reference will always be multi use token
            token_type: common_enums::TokenizationType::MultiUse,
        }
    }
}

#[cfg(feature = "v2")]
impl transformers::ForeignFrom<&payment_method_data::SingleUsePaymentMethodToken>
    for api_models::payment_methods::ConnectorTokenDetails
{
    fn foreign_from(token: &payment_method_data::SingleUsePaymentMethodToken) -> Self {
        Self {
            connector_id: token.clone().merchant_connector_id,
            token_type: common_enums::TokenizationType::SingleUse,
            status: api_enums::ConnectorTokenStatus::Active,
            connector_token_request_reference_id: None,
            original_payment_authorized_amount: None,
            original_payment_authorized_currency: None,
            metadata: None,
            token: token.clone().token,
        }
    }
}