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

#[cfg(feature = "v2")]
use std::fmt::Debug;
#[cfg(feature = "v2")]
use std::str::FromStr;

use ::payment_methods::controller::PaymentMethodsController;
use api_models::payment_methods as api_payment_methods;
#[cfg(feature = "v2")]
use cards::{CardNumber, NetworkToken};
use common_utils::{
    errors::CustomResult,
    ext_traits::{BytesExt, Encode},
    id_type,
    metrics::utils::record_operation_time,
    request::RequestContent,
};
#[cfg(feature = "v1")]
use error_stack::ResultExt;
#[cfg(feature = "v2")]
use error_stack::{report, ResultExt};
#[cfg(feature = "v2")]
use hyperswitch_domain_models::payment_method_data::{
    NetworkTokenDetails, NetworkTokenDetailsPaymentMethod,
};
use josekit::jwe;
use masking::{ErasedMaskSerialize, ExposeInterface, Mask, PeekInterface, Secret};

use super::transformers::DeleteCardResp;
use crate::{
    core::{errors, payment_methods, payments::helpers},
    headers, logger,
    routes::{self, metrics},
    services::{self, encryption},
    settings,
    types::{api, domain, payment_methods as pm_types},
};

pub const NETWORK_TOKEN_SERVICE: &str = "NETWORK_TOKEN";

#[cfg(feature = "v1")]
pub async fn mk_tokenization_req(
    state: &routes::SessionState,
    payload_bytes: &[u8],
    customer_id: id_type::CustomerId,
    tokenization_service: &settings::NetworkTokenizationService,
) -> CustomResult<
    (pm_types::CardNetworkTokenResponsePayload, Option<String>),
    errors::NetworkTokenizationError,
> {
    let enc_key = tokenization_service.public_key.peek().clone();

    let key_id = tokenization_service.key_id.clone();

    let jwt = encryption::encrypt_jwe(
        payload_bytes,
        enc_key,
        services::EncryptionAlgorithm::A128GCM,
        Some(key_id.as_str()),
    )
    .await
    .change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed)
    .attach_printable("Error on jwe encrypt")?;

    let order_data = pm_types::OrderData {
        consent_id: uuid::Uuid::new_v4().to_string(),
        customer_id,
    };

    let api_payload = pm_types::ApiPayload {
        service: NETWORK_TOKEN_SERVICE.to_string(),
        card_data: Secret::new(jwt),
        order_data,
        should_send_token: true,
    };
    let masked_request_body = api_payload
        .masked_serialize()
        .inspect_err(|e| logger::error!(error=?e, "failed to mask serialize"))
        .unwrap_or(serde_json::json!({ "error": "failed to mask serialize"}));
    logger::info!(raw_network_token_service_request=?masked_request_body);

    let mut request = services::Request::new(
        services::Method::Post,
        tokenization_service.generate_token_url.as_str(),
    );
    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::AUTHORIZATION,
        tokenization_service
            .token_service_api_key
            .peek()
            .clone()
            .into_masked(),
    );
    request.add_default_headers();

    request.set_body(RequestContent::Json(Box::new(api_payload)));

    logger::info!("Request to generate token: {:?}", request);

    let response = services::call_connector_api(state, request, "generate_token")
        .await
        .change_context(errors::NetworkTokenizationError::ApiError);

    let res = response
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
        .attach_printable("Error while receiving response")
        .and_then(|inner| match inner {
            Err(err_res) => {
                let parsed_error: pm_types::NetworkTokenErrorResponse = err_res
                    .response
                    .parse_struct("Card Network Tokenization Response")
                    .change_context(
                        errors::NetworkTokenizationError::ResponseDeserializationFailed,
                    )?;
                logger::error!(
                    error_code = %parsed_error.error_info.code,
                    developer_message = %parsed_error.error_info.developer_message,
                    "Network tokenization error: {:?}",
                    parsed_error.error_message
                );
                Err(errors::NetworkTokenizationError::ResponseDeserializationFailed)
                    .attach_printable(format!("Response Deserialization Failed: {err_res:?}"))
            }
            Ok(res) => Ok(res),
        })
        .inspect_err(|err| {
            logger::error!("Error while deserializing response: {:?}", err);
        })?;

    let network_response: pm_types::CardNetworkTokenResponse = res
        .response
        .parse_struct("Card Network Tokenization Response")
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;

    let dec_key = tokenization_service.private_key.peek().clone();

    let card_network_token_response = services::decrypt_jwe(
        network_response.payload.peek(),
        services::KeyIdCheck::SkipKeyIdCheck,
        dec_key,
        jwe::RSA_OAEP_256,
    )
    .await
    .change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed)
    .attach_printable(
        "Failed to decrypt the tokenization response from the tokenization service",
    )?;

    let cn_response: pm_types::CardNetworkTokenResponsePayload =
        serde_json::from_str(&card_network_token_response)
            .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
    Ok((cn_response.clone(), Some(cn_response.card_reference)))
}

#[cfg(feature = "v2")]
pub async fn generate_network_token(
    state: &routes::SessionState,
    payload_bytes: &[u8],
    customer_id: id_type::GlobalCustomerId,
    tokenization_service: &settings::NetworkTokenizationService,
) -> CustomResult<
    (pm_types::GenerateNetworkTokenResponsePayload, String),
    errors::NetworkTokenizationError,
> {
    let enc_key = tokenization_service.public_key.peek().clone();

    let key_id = tokenization_service.key_id.clone();

    let jwt = encryption::encrypt_jwe(
        payload_bytes,
        enc_key,
        services::EncryptionAlgorithm::A128GCM,
        Some(key_id.as_str()),
    )
    .await
    .change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed)
    .attach_printable("Error on jwe encrypt")?;

    let order_data = pm_types::OrderData {
        consent_id: uuid::Uuid::new_v4().to_string(),
        customer_id,
    };

    let api_payload = pm_types::ApiPayload {
        service: NETWORK_TOKEN_SERVICE.to_string(),
        card_data: Secret::new(jwt),
        order_data,
        should_send_token: true,
    };
    let masked_request_body = api_payload
        .masked_serialize()
        .inspect_err(|e| logger::error!(error=?e, "failed to mask serialize"))
        .unwrap_or(serde_json::json!({ "error": "failed to mask serialize"}));
    logger::info!(raw_network_token_service_request=?masked_request_body);

    let mut request = services::Request::new(
        services::Method::Post,
        tokenization_service.generate_token_url.as_str(),
    );
    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::AUTHORIZATION,
        tokenization_service
            .token_service_api_key
            .peek()
            .clone()
            .into_masked(),
    );
    request.add_default_headers();

    request.set_body(RequestContent::Json(Box::new(api_payload)));

    logger::info!("Request to generate token: {:?}", request);

    let response = services::call_connector_api(state, request, "generate_token")
        .await
        .change_context(errors::NetworkTokenizationError::ApiError);

    let res = response
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
        .attach_printable("Error while receiving response")
        .and_then(|inner| match inner {
            Err(err_res) => {
                let parsed_error: pm_types::NetworkTokenErrorResponse = err_res
                    .response
                    .parse_struct("Card Network Tokenization Response")
                    .change_context(
                        errors::NetworkTokenizationError::ResponseDeserializationFailed,
                    )?;
                logger::error!(
                    error_code = %parsed_error.error_info.code,
                    developer_message = %parsed_error.error_info.developer_message,
                    "Network tokenization error: {:?}",
                    parsed_error.error_message
                );
                Err(errors::NetworkTokenizationError::ResponseDeserializationFailed)
                    .attach_printable(format!("Response Deserialization Failed: {err_res:?}"))
            }
            Ok(res) => Ok(res),
        })
        .inspect_err(|err| {
            logger::error!("Error while deserializing response: {:?}", err);
        })?;

    let network_response: pm_types::CardNetworkTokenResponse = res
        .response
        .parse_struct("Card Network Tokenization Response")
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
    logger::debug!("Network Token Response: {:?}", network_response);

    let dec_key = tokenization_service.private_key.peek().clone();

    let card_network_token_response = services::decrypt_jwe(
        network_response.payload.peek(),
        services::KeyIdCheck::SkipKeyIdCheck,
        dec_key,
        jwe::RSA_OAEP_256,
    )
    .await
    .change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed)
    .attach_printable(
        "Failed to decrypt the tokenization response from the tokenization service",
    )?;

    let cn_response: pm_types::GenerateNetworkTokenResponsePayload =
        serde_json::from_str(&card_network_token_response)
            .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
    Ok((cn_response.clone(), cn_response.card_reference))
}

#[cfg(feature = "v1")]
pub async fn make_card_network_tokenization_request(
    state: &routes::SessionState,
    card: &domain::CardDetail,
    optional_cvc: Option<Secret<String>>,
    customer_id: &id_type::CustomerId,
) -> CustomResult<
    (pm_types::CardNetworkTokenResponsePayload, Option<String>),
    errors::NetworkTokenizationError,
> {
    let card_data = pm_types::CardData {
        card_number: card.card_number.clone(),
        exp_month: card.card_exp_month.clone(),
        exp_year: card.card_exp_year.clone(),
        card_security_code: optional_cvc,
    };

    let payload = card_data
        .encode_to_string_of_json()
        .and_then(|x| x.encode_to_string_of_json())
        .change_context(errors::NetworkTokenizationError::RequestEncodingFailed)?;

    let payload_bytes = payload.as_bytes();
    if let Some(network_tokenization_service) = &state.conf.network_tokenization_service {
        record_operation_time(
            async {
                mk_tokenization_req(
                    state,
                    payload_bytes,
                    customer_id.clone(),
                    network_tokenization_service.get_inner(),
                )
                .await
                .inspect_err(
                    |e| logger::error!(error=?e, "Error while making tokenization request"),
                )
            },
            &metrics::GENERATE_NETWORK_TOKEN_TIME,
            router_env::metric_attributes!(("locker", "rust")),
        )
        .await
    } else {
        Err(errors::NetworkTokenizationError::NetworkTokenizationServiceNotConfigured)
            .inspect_err(|_| {
                logger::error!("Network Tokenization Service not configured");
            })
            .attach_printable("Network Tokenization Service not configured")
    }
}

#[cfg(feature = "v2")]
pub async fn make_card_network_tokenization_request(
    state: &routes::SessionState,
    card: &api_payment_methods::CardDetail,
    customer_id: &id_type::GlobalCustomerId,
) -> CustomResult<(NetworkTokenDetails, String), errors::NetworkTokenizationError> {
    let card_data = pm_types::CardData {
        card_number: card.card_number.clone(),
        exp_month: card.card_exp_month.clone(),
        exp_year: card.card_exp_year.clone(),
        card_security_code: None,
    };

    let payload = card_data
        .encode_to_string_of_json()
        .and_then(|x| x.encode_to_string_of_json())
        .change_context(errors::NetworkTokenizationError::RequestEncodingFailed)?;

    let payload_bytes = payload.as_bytes();
    let network_tokenization_service = match &state.conf.network_tokenization_service {
        Some(nt_service) => Ok(nt_service.get_inner()),
        None => Err(report!(
            errors::NetworkTokenizationError::NetworkTokenizationServiceNotConfigured
        )),
    }?;

    let (resp, network_token_req_ref_id) = record_operation_time(
        async {
            generate_network_token(
                state,
                payload_bytes,
                customer_id.clone(),
                network_tokenization_service,
            )
            .await
            .inspect_err(|e| logger::error!(error=?e, "Error while making tokenization request"))
        },
        &metrics::GENERATE_NETWORK_TOKEN_TIME,
        router_env::metric_attributes!(("locker", "rust")),
    )
    .await?;

    let network_token_details = NetworkTokenDetails {
        network_token: resp.token,
        network_token_exp_month: resp.token_expiry_month,
        network_token_exp_year: resp.token_expiry_year,
        card_issuer: card.card_issuer.clone(),
        card_network: Some(resp.card_brand),
        card_type: card.card_type.clone(),
        card_issuing_country: card.card_issuing_country,
        card_holder_name: card.card_holder_name.clone(),
        nick_name: card.nick_name.clone(),
        cryptogram: None,
    };
    Ok((network_token_details, network_token_req_ref_id))
}

#[cfg(feature = "v1")]
pub async fn get_network_token(
    state: &routes::SessionState,
    customer_id: id_type::CustomerId,
    network_token_requestor_ref_id: String,
    tokenization_service: &settings::NetworkTokenizationService,
) -> CustomResult<pm_types::TokenResponse, errors::NetworkTokenizationError> {
    let mut request = services::Request::new(
        services::Method::Post,
        tokenization_service.fetch_token_url.as_str(),
    );
    let payload = pm_types::GetCardToken {
        card_reference: network_token_requestor_ref_id,
        customer_id,
    };

    let masked_request_body = payload
        .masked_serialize()
        .inspect_err(|e| logger::error!(error=?e, "failed to mask serialize"))
        .unwrap_or(serde_json::json!({ "error": "failed to mask serialize"}));
    logger::info!(raw_network_token_service_request=?masked_request_body);

    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::AUTHORIZATION,
        tokenization_service
            .token_service_api_key
            .clone()
            .peek()
            .clone()
            .into_masked(),
    );
    request.add_default_headers();
    request.set_body(RequestContent::Json(Box::new(payload)));

    logger::info!("Request to fetch network token: {:?}", request);

    // Send the request using `call_connector_api`
    let response = services::call_connector_api(state, request, "get network token")
        .await
        .change_context(errors::NetworkTokenizationError::ApiError);

    let res = response
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
        .attach_printable("Error while receiving response")
        .and_then(|inner| match inner {
            Err(err_res) => {
                let parsed_error: pm_types::NetworkTokenErrorResponse = err_res
                    .response
                    .parse_struct("Card Network Tokenization Response")
                    .change_context(
                        errors::NetworkTokenizationError::ResponseDeserializationFailed,
                    )?;
                logger::error!(
                    error_code = %parsed_error.error_info.code,
                    developer_message = %parsed_error.error_info.developer_message,
                    "Network tokenization error: {:?}",
                    parsed_error.error_message
                );
                Err(errors::NetworkTokenizationError::ResponseDeserializationFailed)
                    .attach_printable(format!("Response Deserialization Failed: {err_res:?}"))
            }
            Ok(res) => Ok(res),
        })?;

    let token_response: pm_types::TokenResponse = res
        .response
        .parse_struct("Get Network Token Response")
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
    logger::info!("Fetch Network Token Response: {:?}", token_response);

    Ok(token_response)
}

#[cfg(feature = "v2")]
pub async fn get_network_token(
    state: &routes::SessionState,
    customer_id: &id_type::GlobalCustomerId,
    network_token_requestor_ref_id: String,
    tokenization_service: &settings::NetworkTokenizationService,
) -> CustomResult<pm_types::TokenResponse, errors::NetworkTokenizationError> {
    let mut request = services::Request::new(
        services::Method::Post,
        tokenization_service.fetch_token_url.as_str(),
    );
    let payload = pm_types::GetCardToken {
        card_reference: network_token_requestor_ref_id,
        customer_id: customer_id.clone(),
    };

    let masked_request_body = payload
        .masked_serialize()
        .inspect_err(|e| logger::error!(error=?e, "failed to mask serialize"))
        .unwrap_or(serde_json::json!({ "error": "failed to mask serialize"}));
    logger::info!(raw_network_token_service_request=?masked_request_body);

    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::AUTHORIZATION,
        tokenization_service
            .token_service_api_key
            .clone()
            .peek()
            .clone()
            .into_masked(),
    );
    request.add_default_headers();
    request.set_body(RequestContent::Json(Box::new(payload)));

    logger::info!("Request to fetch network token: {:?}", request);

    // Send the request using `call_connector_api`
    let response = services::call_connector_api(state, request, "get network token")
        .await
        .change_context(errors::NetworkTokenizationError::ApiError);

    let res = response
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
        .attach_printable("Error while receiving response")
        .and_then(|inner| match inner {
            Err(err_res) => {
                let parsed_error: pm_types::NetworkTokenErrorResponse = err_res
                    .response
                    .parse_struct("Card Network Tokenization Response")
                    .change_context(
                        errors::NetworkTokenizationError::ResponseDeserializationFailed,
                    )?;
                logger::error!(
                    error_code = %parsed_error.error_info.code,
                    developer_message = %parsed_error.error_info.developer_message,
                    "Network tokenization error: {:?}",
                    parsed_error.error_message
                );
                Err(errors::NetworkTokenizationError::ResponseDeserializationFailed)
                    .attach_printable(format!("Response Deserialization Failed: {err_res:?}"))
            }
            Ok(res) => Ok(res),
        })?;

    let token_response: pm_types::TokenResponse = res
        .response
        .parse_struct("Get Network Token Response")
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
    logger::info!("Fetch Network Token Response: {:?}", token_response);

    Ok(token_response)
}

#[cfg(feature = "v1")]
pub async fn get_token_from_tokenization_service(
    state: &routes::SessionState,
    network_token_requestor_ref_id: String,
    pm_data: &domain::PaymentMethod,
) -> errors::RouterResult<domain::NetworkTokenData> {
    let token_response =
        if let Some(network_tokenization_service) = &state.conf.network_tokenization_service {
            record_operation_time(
                async {
                    get_network_token(
                state,
                pm_data.customer_id.clone(),
                network_token_requestor_ref_id,
                network_tokenization_service.get_inner(),
            )
            .await
            .inspect_err(
                |e| logger::error!(error=?e, "Error while fetching token from tokenization service")
            )
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Fetch network token failed")
                },
                &metrics::FETCH_NETWORK_TOKEN_TIME,
                &[],
            )
            .await
        } else {
            Err(errors::NetworkTokenizationError::NetworkTokenizationServiceNotConfigured)
                .inspect_err(|err| {
                    logger::error!(error=? err);
                })
                .change_context(errors::ApiErrorResponse::InternalServerError)
        }?;

    let token_decrypted = pm_data
        .network_token_payment_method_data
        .clone()
        .map(|x| x.into_inner().expose())
        .and_then(|v| serde_json::from_value::<api_payment_methods::PaymentMethodsData>(v).ok())
        .and_then(|pmd| match pmd {
            api_payment_methods::PaymentMethodsData::Card(token) => {
                Some(api::CardDetailFromLocker::from(token))
            }
            _ => None,
        })
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to obtain decrypted token object from db")?;

    let network_token_data = domain::NetworkTokenData {
        token_number: token_response.authentication_details.token,
        token_cryptogram: Some(token_response.authentication_details.cryptogram),
        token_exp_month: token_decrypted
            .expiry_month
            .unwrap_or(token_response.token_details.exp_month),
        token_exp_year: token_decrypted
            .expiry_year
            .unwrap_or(token_response.token_details.exp_year),
        nick_name: token_decrypted.card_holder_name,
        card_issuer: None,
        card_network: Some(token_response.network),
        card_type: None,
        card_issuing_country: None,
        bank_code: None,
        eci: token_response.eci,
    };
    Ok(network_token_data)
}

#[cfg(feature = "v2")]
pub async fn get_token_from_tokenization_service(
    state: &routes::SessionState,
    network_token_requestor_ref_id: String,
    pm_data: &domain::PaymentMethod,
) -> errors::RouterResult<domain::NetworkTokenData> {
    let token_response =
        if let Some(network_tokenization_service) = &state.conf.network_tokenization_service {
            record_operation_time(
                async {
                    get_network_token(
                state,
                &pm_data.customer_id,
                network_token_requestor_ref_id,
                network_tokenization_service.get_inner(),
            )
            .await
            .inspect_err(
                |e| logger::error!(error=?e, "Error while fetching token from tokenization service")
            )
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Fetch network token failed")
                },
                &metrics::FETCH_NETWORK_TOKEN_TIME,
                &[],
            )
            .await
        } else {
            Err(errors::NetworkTokenizationError::NetworkTokenizationServiceNotConfigured)
                .inspect_err(|err| {
                    logger::error!(error=? err);
                })
                .change_context(errors::ApiErrorResponse::InternalServerError)
        }?;

    let token_decrypted = pm_data
        .network_token_payment_method_data
        .clone()
        .map(|value| value.into_inner())
        .and_then(|payment_method_data| match payment_method_data {
            hyperswitch_domain_models::payment_method_data::PaymentMethodsData::NetworkToken(
                token,
            ) => Some(token),
            _ => None,
        })
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to obtain decrypted token object from db")?;

    let network_token_data = domain::NetworkTokenData {
        network_token: token_response.authentication_details.token,
        cryptogram: Some(token_response.authentication_details.cryptogram),
        network_token_exp_month: token_decrypted
            .network_token_expiry_month
            .unwrap_or(token_response.token_details.exp_month),
        network_token_exp_year: token_decrypted
            .network_token_expiry_year
            .unwrap_or(token_response.token_details.exp_year),
        card_holder_name: token_decrypted.card_holder_name,
        nick_name: token_decrypted.nick_name.or(token_response.nickname),
        card_issuer: token_decrypted.card_issuer.or(token_response.issuer),
        card_network: Some(token_response.network),
        card_type: token_decrypted
            .card_type
            .or(token_response.card_type)
            .as_ref()
            .map(|c| api_payment_methods::CardType::from_str(c))
            .transpose()
            .ok()
            .flatten(),
        card_issuing_country: token_decrypted.issuer_country,
        bank_code: None,
        eci: token_response.eci,
    };
    Ok(network_token_data)
}

#[cfg(feature = "v1")]
pub async fn do_status_check_for_network_token(
    state: &routes::SessionState,
    payment_method_info: &domain::PaymentMethod,
) -> CustomResult<(Option<Secret<String>>, Option<Secret<String>>), errors::ApiErrorResponse> {
    let network_token_data_decrypted = payment_method_info
        .network_token_payment_method_data
        .clone()
        .map(|x| x.into_inner().expose())
        .and_then(|v| serde_json::from_value::<api_payment_methods::PaymentMethodsData>(v).ok())
        .and_then(|pmd| match pmd {
            api_payment_methods::PaymentMethodsData::Card(token) => {
                Some(api::CardDetailFromLocker::from(token))
            }
            _ => None,
        });
    let network_token_requestor_reference_id = payment_method_info
        .network_token_requestor_reference_id
        .clone();
    if network_token_data_decrypted
        .and_then(|token_data| token_data.expiry_month.zip(token_data.expiry_year))
        .and_then(|(exp_month, exp_year)| helpers::validate_card_expiry(&exp_month, &exp_year).ok())
        .is_none()
    {
        if let Some(ref_id) = network_token_requestor_reference_id {
            if let Some(network_tokenization_service) = &state.conf.network_tokenization_service {
                let (token_exp_month, token_exp_year) = record_operation_time(
                    async {
                        check_token_status_with_tokenization_service(
                            state,
                            &payment_method_info.customer_id.clone(),
                            ref_id,
                            network_tokenization_service.get_inner(),
                        )
                        .await
                        .inspect_err(
                            |e| logger::error!(error=?e, "Error while fetching token from tokenization service")
                        )
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable(
                            "Check network token status with tokenization service failed",
                        )
                    },
                    &metrics::CHECK_NETWORK_TOKEN_STATUS_TIME,

                    &[],
                )
                .await?;
                Ok((token_exp_month, token_exp_year))
            } else {
                Err(errors::NetworkTokenizationError::NetworkTokenizationServiceNotConfigured)
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .inspect_err(|_| {
                        logger::error!("Network Tokenization Service not configured");
                    })
            }
        } else {
            Err(errors::NetworkTokenizationError::FetchNetworkTokenFailed)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Check network token status failed")?
        }
    } else {
        Ok((None, None))
    }
}

#[cfg(feature = "v1")]
pub async fn check_token_status_with_tokenization_service(
    state: &routes::SessionState,
    customer_id: &id_type::CustomerId,
    network_token_requestor_reference_id: String,
    tokenization_service: &settings::NetworkTokenizationService,
) -> CustomResult<(Option<Secret<String>>, Option<Secret<String>>), errors::NetworkTokenizationError>
{
    let mut request = services::Request::new(
        services::Method::Post,
        tokenization_service.check_token_status_url.as_str(),
    );
    let payload = pm_types::CheckTokenStatus {
        card_reference: network_token_requestor_reference_id,
        customer_id: customer_id.clone(),
    };

    let masked_request_body = payload
        .masked_serialize()
        .inspect_err(|e| logger::error!(error=?e, "failed to mask serialize"))
        .unwrap_or(serde_json::json!({ "error": "failed to mask serialize"}));
    logger::info!(raw_network_token_service_request=?masked_request_body);

    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::AUTHORIZATION,
        tokenization_service
            .token_service_api_key
            .clone()
            .peek()
            .clone()
            .into_masked(),
    );
    request.add_default_headers();
    request.set_body(RequestContent::Json(Box::new(payload)));

    // Send the request using `call_connector_api`
    let response = services::call_connector_api(state, request, "Check Network token Status")
        .await
        .change_context(errors::NetworkTokenizationError::ApiError);
    let res = response
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
        .attach_printable("Error while receiving response")
        .and_then(|inner| match inner {
            Err(err_res) => {
                let parsed_error: pm_types::NetworkTokenErrorResponse = err_res
                    .response
                    .parse_struct("Delete Network Tokenization Response")
                    .change_context(
                        errors::NetworkTokenizationError::ResponseDeserializationFailed,
                    )?;
                logger::error!(
                    error_code = %parsed_error.error_info.code,
                    developer_message = %parsed_error.error_info.developer_message,
                    "Network tokenization error: {:?}",
                    parsed_error.error_message
                );
                Err(errors::NetworkTokenizationError::ResponseDeserializationFailed)
                    .attach_printable(format!("Response Deserialization Failed: {err_res:?}"))
            }
            Ok(res) => Ok(res),
        })
        .inspect_err(|err| {
            logger::error!("Error while deserializing response: {:?}", err);
        })?;

    let check_token_status_response: pm_types::CheckTokenStatusResponse = res
        .response
        .parse_struct("Delete Network Tokenization Response")
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;

    match check_token_status_response.payload.token_status {
        pm_types::TokenStatus::Active => Ok((
            check_token_status_response.payload.token_expiry_month,
            check_token_status_response.payload.token_expiry_year,
        )),
        _ => Ok((None, None)),
    }
}

#[cfg(feature = "v2")]
pub async fn check_token_status_with_tokenization_service(
    state: &routes::SessionState,
    customer_id: &id_type::GlobalCustomerId,
    network_token_requestor_reference_id: String,
    tokenization_service: &settings::NetworkTokenizationService,
) -> CustomResult<pm_types::CheckTokenStatusResponse, errors::NetworkTokenizationError> {
    let mut request = services::Request::new(
        services::Method::Post,
        tokenization_service.check_token_status_url.as_str(),
    );
    let payload = pm_types::CheckTokenStatus {
        card_reference: network_token_requestor_reference_id,
        customer_id: customer_id.clone(),
    };

    let masked_request_body = payload
        .masked_serialize()
        .inspect_err(|e| logger::error!(error=?e, "failed to mask serialize"))
        .unwrap_or(serde_json::json!({ "error": "failed to mask serialize"}));
    logger::info!(raw_network_token_service_request=?masked_request_body);

    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::AUTHORIZATION,
        tokenization_service
            .token_service_api_key
            .clone()
            .peek()
            .clone()
            .into_masked(),
    );
    request.add_default_headers();
    request.set_body(RequestContent::Json(Box::new(payload)));

    // Send the request using `call_connector_api`
    let response = services::call_connector_api(state, request, "Check Network token Status")
        .await
        .change_context(errors::NetworkTokenizationError::ApiError);
    let res = response
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
        .attach_printable("Error while receiving response")
        .and_then(|inner| match inner {
            Err(err_res) => {
                let parsed_error: pm_types::NetworkTokenErrorResponse = err_res
                    .response
                    .parse_struct("Network Tokenization Error Response")
                    .change_context(
                        errors::NetworkTokenizationError::ResponseDeserializationFailed,
                    )?;
                logger::error!(
                    error_code = %parsed_error.error_info.code,
                    developer_message = %parsed_error.error_info.developer_message,
                    "Network tokenization error: {:?}",
                    parsed_error.error_message
                );
                Err(errors::NetworkTokenizationError::ResponseDeserializationFailed)
                    .attach_printable(format!("Response Deserialization Failed: {err_res:?}"))
            }
            Ok(res) => Ok(res),
        })
        .inspect_err(|err| {
            logger::error!("Error while deserializing response: {:?}", err);
        })?;

    let check_token_status_response: pm_types::CheckTokenStatusResponse = res
        .response
        .parse_struct("CheckTokenStatusResponse")
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;

    Ok(check_token_status_response)
}

#[cfg(feature = "v2")]
pub async fn do_status_check_for_network_token(
    state: &routes::SessionState,
    payment_method_info: &domain::PaymentMethod,
) -> CustomResult<pm_types::CheckTokenStatusResponse, errors::NetworkTokenizationError> {
    let network_token_requestor_reference_id = payment_method_info
        .network_token_requestor_reference_id
        .clone();

    if let Some(ref_id) = network_token_requestor_reference_id {
        if let Some(network_tokenization_service) = &state.conf.network_tokenization_service {
            let network_token_details = record_operation_time(
                async {
                    check_token_status_with_tokenization_service(
                        state,
                        &payment_method_info.customer_id,
                        ref_id,
                        network_tokenization_service.get_inner(),
                    )
                    .await
                    .inspect_err(
                        |e| logger::error!(error=?e, "Error while fetching token from tokenization service")
                    )
                    .attach_printable(
                        "Check network token status with tokenization service failed",
                    )
                },
                &metrics::CHECK_NETWORK_TOKEN_STATUS_TIME,
                &[],
            )
            .await?;
            Ok(network_token_details)
        } else {
            Err(errors::NetworkTokenizationError::NetworkTokenizationServiceNotConfigured)
                .attach_printable("Network Tokenization Service not configured")
                .inspect_err(|_| {
                    logger::error!("Network Tokenization Service not configured");
                })
        }
    } else {
        Err(errors::NetworkTokenizationError::FetchNetworkTokenFailed)
            .attach_printable("Check network token status failed")?
    }
}

#[cfg(feature = "v1")]
pub async fn delete_network_token_from_locker_and_token_service(
    state: &routes::SessionState,
    customer_id: &id_type::CustomerId,
    merchant_id: &id_type::MerchantId,
    payment_method_id: String,
    network_token_locker_id: Option<String>,
    network_token_requestor_reference_id: String,
    platform: &domain::Platform,
) -> errors::RouterResult<DeleteCardResp> {
    //deleting network token from locker
    let resp = payment_methods::cards::PmCards { state, platform }
        .delete_card_from_locker(
            customer_id,
            merchant_id,
            network_token_locker_id
                .as_ref()
                .unwrap_or(&payment_method_id),
        )
        .await?;
    if let Some(tokenization_service) = &state.conf.network_tokenization_service {
        let delete_token_resp = record_operation_time(
            async {
                delete_network_token_from_tokenization_service(
                    state,
                    network_token_requestor_reference_id,
                    customer_id,
                    tokenization_service.get_inner(),
                )
                .await
            },
            &metrics::DELETE_NETWORK_TOKEN_TIME,
            &[],
        )
        .await;
        match delete_token_resp {
            Ok(_) => logger::info!("Token From Tokenization Service deleted Successfully!"),
            Err(e) => {
                logger::error!(error=?e, "Error while deleting Token From Tokenization Service!")
            }
        };
    };

    Ok(resp)
}

#[cfg(feature = "v1")]
pub async fn delete_network_token_from_tokenization_service(
    state: &routes::SessionState,
    network_token_requestor_reference_id: String,
    customer_id: &id_type::CustomerId,
    tokenization_service: &settings::NetworkTokenizationService,
) -> CustomResult<bool, errors::NetworkTokenizationError> {
    let mut request = services::Request::new(
        services::Method::Post,
        tokenization_service.delete_token_url.as_str(),
    );
    let payload = pm_types::DeleteCardToken {
        card_reference: network_token_requestor_reference_id,
        customer_id: customer_id.clone(),
    };

    let masked_request_body = payload
        .masked_serialize()
        .inspect_err(|e| logger::error!(error=?e, "failed to mask serialize"))
        .unwrap_or(serde_json::json!({ "error": "failed to mask serialize"}));
    logger::info!(raw_network_token_service_request=?masked_request_body);

    request.add_header(headers::CONTENT_TYPE, "application/json".into());
    request.add_header(
        headers::AUTHORIZATION,
        tokenization_service
            .token_service_api_key
            .clone()
            .peek()
            .clone()
            .into_masked(),
    );
    request.add_default_headers();
    request.set_body(RequestContent::Json(Box::new(payload)));

    logger::info!("Request to delete network token: {:?}", request);

    // Send the request using `call_connector_api`
    let response = services::call_connector_api(state, request, "delete network token")
        .await
        .change_context(errors::NetworkTokenizationError::ApiError);
    let res = response
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)
        .attach_printable("Error while receiving response")
        .and_then(|inner| match inner {
            Err(err_res) => {
                let parsed_error: pm_types::NetworkTokenErrorResponse = err_res
                    .response
                    .parse_struct("Delete Network Tokenization Response")
                    .change_context(
                        errors::NetworkTokenizationError::ResponseDeserializationFailed,
                    )?;
                logger::error!(
                    error_code = %parsed_error.error_info.code,
                    developer_message = %parsed_error.error_info.developer_message,
                    "Network tokenization error: {:?}",
                    parsed_error.error_message
                );
                Err(errors::NetworkTokenizationError::ResponseDeserializationFailed)
                    .attach_printable(format!("Response Deserialization Failed: {err_res:?}"))
            }
            Ok(res) => Ok(res),
        })
        .inspect_err(|err| {
            logger::error!("Error while deserializing response: {:?}", err);
        })?;

    let delete_token_response: pm_types::DeleteNetworkTokenResponse = res
        .response
        .parse_struct("Delete Network Tokenization Response")
        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;

    logger::info!("Delete Network Token Response: {:?}", delete_token_response);

    if delete_token_response.status == pm_types::DeleteNetworkTokenStatus::Success {
        Ok(true)
    } else {
        Err(errors::NetworkTokenizationError::DeleteNetworkTokenFailed)
            .attach_printable("Delete Token at Token service failed")
    }
}

#[cfg(feature = "v2")]
pub async fn delete_network_token_from_locker_and_token_service(
    _state: &routes::SessionState,
    _customer_id: &id_type::GlobalCustomerId,
    _merchant_id: &id_type::MerchantId,
    _payment_method_id: String,
    _network_token_locker_id: Option<String>,
    _network_token_requestor_reference_id: String,
) -> errors::RouterResult<DeleteCardResp> {
    todo!()
}


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

use api_models::{admin, payment_methods::PaymentMethodCollectLinkRequest};
use common_utils::link_utils;
use diesel_models::generic_link::PaymentMethodCollectLinkData;
use error_stack::ResultExt;
use masking::Secret;

use crate::{
    consts,
    core::{
        errors::{self, RouterResult},
        utils as core_utils,
    },
    routes::{app::StorageInterface, SessionState},
    types::domain,
    utils,
};

#[cfg(feature = "v2")]
pub async fn validate_request_and_initiate_payment_method_collect_link(
    _state: &SessionState,
    _platform: &domain::Platform,
    _req: &PaymentMethodCollectLinkRequest,
) -> RouterResult<PaymentMethodCollectLinkData> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn validate_request_and_initiate_payment_method_collect_link(
    state: &SessionState,
    platform: &domain::Platform,
    req: &PaymentMethodCollectLinkRequest,
) -> RouterResult<PaymentMethodCollectLinkData> {
    // Validate customer_id
    let db: &dyn StorageInterface = &*state.store;
    let customer_id = req.customer_id.clone();
    let merchant_id = platform.get_processor().get_account().get_id().clone();
    #[cfg(feature = "v1")]
    match db
        .find_customer_by_customer_id_merchant_id(
            &customer_id,
            &merchant_id,
            platform.get_processor().get_key_store(),
            platform.get_processor().get_account().storage_scheme,
        )
        .await
    {
        Ok(_) => Ok(()),
        Err(err) => {
            if err.current_context().is_db_not_found() {
                Err(err).change_context(errors::ApiErrorResponse::InvalidRequestData {
                    message: format!(
                        "customer [{}] not found for merchant [{:?}]",
                        customer_id.get_string_repr(),
                        merchant_id
                    ),
                })
            } else {
                Err(err)
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("database error while finding customer")
            }
        }
    }?;

    // Create payment method collect link ID
    let pm_collect_link_id = core_utils::get_or_generate_id(
        "pm_collect_link_id",
        &req.pm_collect_link_id,
        "pm_collect_link",
    )?;

    // Fetch all configs
    let default_config = &state.conf.generic_link.payment_method_collect;

    #[cfg(feature = "v1")]
    let merchant_config = platform
        .get_processor()
        .get_account()
        .pm_collect_link_config
        .as_ref()
        .map(|config| {
            common_utils::ext_traits::ValueExt::parse_value::<admin::BusinessCollectLinkConfig>(
                config.clone(),
                "BusinessCollectLinkConfig",
            )
        })
        .transpose()
        .change_context(errors::ApiErrorResponse::InvalidDataValue {
            field_name: "pm_collect_link_config in merchant_account",
        })?;

    #[cfg(feature = "v2")]
    let merchant_config = Option::<admin::BusinessCollectLinkConfig>::None;

    let merchant_ui_config = merchant_config.as_ref().map(|c| c.config.ui_config.clone());
    let ui_config = req
        .ui_config
        .as_ref()
        .or(merchant_ui_config.as_ref())
        .cloned();

    // Form data to be injected in the link
    let (logo, merchant_name, theme) = match ui_config {
        Some(config) => (config.logo, config.merchant_name, config.theme),
        _ => (None, None, None),
    };
    let pm_collect_link_config = link_utils::GenericLinkUiConfig {
        logo,
        merchant_name,
        theme,
    };
    let client_secret = utils::generate_id(consts::ID_LENGTH, "pm_collect_link_secret");
    let domain = merchant_config
        .clone()
        .and_then(|c| c.config.domain_name)
        .map(|domain| format!("https://{domain}"))
        .unwrap_or(state.base_url.clone());
    let session_expiry = match req.session_expiry {
        Some(expiry) => expiry,
        None => default_config.expiry,
    };
    let link = Secret::new(format!(
        "{domain}/payment_methods/collect/{}/{pm_collect_link_id}",
        merchant_id.get_string_repr()
    ));
    let enabled_payment_methods = match (&req.enabled_payment_methods, &merchant_config) {
        (Some(enabled_payment_methods), _) => enabled_payment_methods.clone(),
        (None, Some(config)) => config.enabled_payment_methods.clone(),
        _ => {
            let mut default_enabled_payout_methods: Vec<link_utils::EnabledPaymentMethod> = vec![];
            for (payment_method, payment_method_types) in
                default_config.enabled_payment_methods.clone().into_iter()
            {
                let enabled_payment_method = link_utils::EnabledPaymentMethod {
                    payment_method,
                    payment_method_types: payment_method_types.into_iter().collect(),
                };
                default_enabled_payout_methods.push(enabled_payment_method);
            }

            default_enabled_payout_methods
        }
    };

    Ok(PaymentMethodCollectLinkData {
        pm_collect_link_id: pm_collect_link_id.clone(),
        customer_id,
        link,
        client_secret: Secret::new(client_secret),
        session_expiry,
        ui_config: pm_collect_link_config,
        enabled_payment_methods: Some(enabled_payment_methods),
    })
}


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

use common_utils::ext_traits::AsyncExt;
use error_stack::ResultExt;
use hyperswitch_domain_models::types::VaultRouterData;

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

pub async fn create_access_token<F: Clone + 'static>(
    state: &SessionState,
    connector_data: &api_types::ConnectorData,
    merchant_account: &domain::MerchantAccount,
    router_data: &mut VaultRouterData<F>,
) -> RouterResult<()> {
    let connector_access_token =
        add_access_token_for_external_vault(state, connector_data, merchant_account, router_data)
            .await?;

    if connector_access_token.connector_supports_access_token {
        match connector_access_token.access_token_result {
            Ok(access_token) => {
                router_data.access_token = access_token;
            }
            Err(connector_error) => {
                router_data.response = Err(connector_error);
            }
        }
    }

    Ok(())
}

pub async fn add_access_token_for_external_vault<F: Clone + 'static>(
    state: &SessionState,
    connector: &api_types::ConnectorData,
    merchant_account: &domain::MerchantAccount,
    router_data: &VaultRouterData<F>,
) -> RouterResult<types::AddAccessTokenResult> {
    if connector
        .connector_name
        .supports_access_token_for_external_vault()
    {
        let merchant_id = merchant_account.get_id();
        let store = &*state.store;
        let key = common_utils::access_token::get_default_access_token_key(
            merchant_id,
            connector.connector.id(),
        );
        let old_access_token = store
            .get_access_token(key)
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("DB error when accessing the access token")?;

        let res = match old_access_token {
            Some(access_token) => Ok(Some(access_token)),
            None => {
                let cloned_router_data = router_data.clone();
                let refresh_token_request_data = types::AccessTokenRequestData::try_from(
                    router_data.connector_auth_type.clone(),
                )
                .attach_printable(
                    "Could not create access token request, invalid connector account credentials",
                )?;

                let refresh_token_response_data: Result<types::AccessToken, types::ErrorResponse> =
                    Err(types::ErrorResponse::default());
                let refresh_token_router_data = payments::helpers::router_data_type_conversion::<
                    _,
                    api_types::AccessTokenAuth,
                    _,
                    _,
                    _,
                    _,
                >(
                    cloned_router_data,
                    refresh_token_request_data,
                    refresh_token_response_data,
                );
                refresh_connector_auth(state, connector, &refresh_token_router_data)
                    .await?
                    .async_map(|access_token| async {
                        //Store the access token in db
                        let store = &*state.store;
                        // This error should not be propagated, we don't want payments to fail once we have
                        // the access token, the next request will create new access token
                        let key = common_utils::access_token::get_default_access_token_key(
                            merchant_id,
                            connector.connector.id(),
                        );
                        let _ = store
                            .set_access_token(key, access_token.clone())
                            .await
                            .change_context(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable("DB error when setting the access token");
                        Some(access_token)
                    })
                    .await
            }
        };

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

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

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

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

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

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


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

use actix_multipart::form::{bytes::Bytes, text::Text, MultipartForm};
use api_models::{enums as api_enums, payment_methods as payment_methods_api};
use cards::CardNumber;
use common_utils::{
    crypto::Encryptable,
    id_type,
    transformers::{ForeignFrom, ForeignTryFrom},
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::router_request_types as domain_request_types;
use masking::{ExposeInterface, Secret};
use router_env::logger;

use crate::{
    core::payment_methods::{
        cards::{add_card_to_hs_locker, create_encrypted_data, tokenize_card_flow},
        network_tokenization, transformers as pm_transformers,
    },
    errors::{self, RouterResult},
    services,
    types::{api, domain, payment_methods as pm_types},
    SessionState,
};

pub mod card_executor;
pub mod payment_method_executor;

pub use card_executor::*;
pub use payment_method_executor::*;
use rdkafka::message::ToBytes;

#[derive(Debug, MultipartForm)]
pub struct CardNetworkTokenizeForm {
    #[multipart(limit = "1MB")]
    pub file: Bytes,
    pub merchant_id: Text<id_type::MerchantId>,
}

pub fn parse_csv(
    merchant_id: &id_type::MerchantId,
    data: &[u8],
) -> csv::Result<Vec<payment_methods_api::CardNetworkTokenizeRequest>> {
    let mut csv_reader = csv::ReaderBuilder::new()
        .has_headers(true)
        .from_reader(data);
    let mut records = Vec::new();
    let mut id_counter = 0;
    for (i, result) in csv_reader
        .deserialize::<domain::CardNetworkTokenizeRecord>()
        .enumerate()
    {
        match result {
            Ok(mut record) => {
                logger::info!("Parsed Record (line {}): {:?}", i + 1, record);
                id_counter += 1;
                record.line_number = Some(id_counter);
                record.merchant_id = Some(merchant_id.clone());
                match payment_methods_api::CardNetworkTokenizeRequest::foreign_try_from(record) {
                    Ok(record) => {
                        records.push(record);
                    }
                    Err(err) => {
                        logger::error!("Error parsing line {}: {}", i + 1, err.to_string());
                    }
                }
            }
            Err(e) => logger::error!("Error parsing line {}: {}", i + 1, e),
        }
    }
    Ok(records)
}

pub fn get_tokenize_card_form_records(
    form: CardNetworkTokenizeForm,
) -> Result<
    (
        id_type::MerchantId,
        Vec<payment_methods_api::CardNetworkTokenizeRequest>,
    ),
    errors::ApiErrorResponse,
> {
    match parse_csv(&form.merchant_id, form.file.data.to_bytes()) {
        Ok(records) => {
            logger::info!("Parsed a total of {} records", records.len());
            Ok((form.merchant_id.0, records))
        }
        Err(e) => {
            logger::error!("Failed to parse CSV: {:?}", e);
            Err(errors::ApiErrorResponse::PreconditionFailed {
                message: e.to_string(),
            })
        }
    }
}

pub async fn tokenize_cards(
    state: &SessionState,
    records: Vec<payment_methods_api::CardNetworkTokenizeRequest>,
    platform: &domain::Platform,
) -> errors::RouterResponse<Vec<payment_methods_api::CardNetworkTokenizeResponse>> {
    use futures::stream::StreamExt;

    // Process all records in parallel
    let responses = futures::stream::iter(records.into_iter())
        .map(|record| async move {
            let tokenize_request = record.data.clone();
            let customer = record.customer.clone();
            Box::pin(tokenize_card_flow(
                state,
                domain::CardNetworkTokenizeRequest::foreign_from(record),
                platform,
            ))
            .await
            .unwrap_or_else(|e| {
                let err = e.current_context();
                payment_methods_api::CardNetworkTokenizeResponse {
                    tokenization_data: Some(tokenize_request),
                    error_code: Some(err.error_code()),
                    error_message: Some(err.error_message()),
                    card_tokenized: false,
                    payment_method_response: None,
                    customer: Some(customer),
                }
            })
        })
        .buffer_unordered(10)
        .collect()
        .await;

    // Return the final response
    Ok(services::ApplicationResponse::Json(responses))
}

// Data types
type NetworkTokenizationResponse = (pm_types::CardNetworkTokenResponsePayload, Option<String>);

pub struct StoreLockerResponse {
    pub store_card_resp: pm_transformers::StoreCardRespPayload,
    pub store_token_resp: pm_transformers::StoreCardRespPayload,
}

// Builder
pub struct NetworkTokenizationBuilder<'a, S: State> {
    /// Current state
    state: std::marker::PhantomData<S>,

    /// Customer details
    pub customer: Option<&'a api::CustomerDetails>,

    /// Card details
    pub card: Option<domain::CardDetail>,

    /// CVC
    pub card_cvc: Option<Secret<String>>,

    /// Network token details
    pub network_token: Option<&'a pm_types::CardNetworkTokenResponsePayload>,

    /// Stored card details
    pub stored_card: Option<&'a pm_transformers::StoreCardRespPayload>,

    /// Stored token details
    pub stored_token: Option<&'a pm_transformers::StoreCardRespPayload>,

    /// Payment method response
    pub payment_method_response: Option<api::PaymentMethodResponse>,

    /// Card network tokenization status
    pub card_tokenized: bool,

    /// Error code
    pub error_code: Option<&'a String>,

    /// Error message
    pub error_message: Option<&'a String>,
}

// Async executor
pub struct CardNetworkTokenizeExecutor<'a, D> {
    pub state: &'a SessionState,
    pub merchant_account: &'a domain::MerchantAccount,
    key_store: &'a domain::MerchantKeyStore,
    data: &'a D,
    customer: &'a domain_request_types::CustomerDetails,
}

// State machine
pub trait State {}
pub trait TransitionTo<S: State> {}

// Trait for network tokenization
#[async_trait::async_trait]
pub trait NetworkTokenizationProcess<'a, D> {
    fn new(
        state: &'a SessionState,
        platform: &'a domain::Platform,
        data: &'a D,
        customer: &'a domain_request_types::CustomerDetails,
    ) -> Self;
    async fn encrypt_card(
        &self,
        card_details: &domain::CardDetail,
        saved_to_locker: bool,
    ) -> RouterResult<Encryptable<Secret<serde_json::Value>>>;
    async fn encrypt_network_token(
        &self,
        network_token_details: &NetworkTokenizationResponse,
        card_details: &domain::CardDetail,
        saved_to_locker: bool,
    ) -> RouterResult<Encryptable<Secret<serde_json::Value>>>;
    async fn fetch_bin_details_and_validate_card_network(
        &self,
        card_number: CardNumber,
        card_issuer: Option<&String>,
        card_network: Option<&api_enums::CardNetwork>,
        card_type: Option<&api_models::payment_methods::CardType>,
        card_issuing_country: Option<&String>,
    ) -> RouterResult<Option<diesel_models::CardInfo>>;
    fn validate_card_network(
        &self,
        optional_card_network: Option<&api_enums::CardNetwork>,
    ) -> RouterResult<()>;
    async fn tokenize_card(
        &self,
        customer_id: &id_type::CustomerId,
        card: &domain::CardDetail,
        optional_cvc: Option<Secret<String>>,
    ) -> RouterResult<NetworkTokenizationResponse>;
    async fn store_network_token_in_locker(
        &self,
        network_token: &NetworkTokenizationResponse,
        customer_id: &id_type::CustomerId,
        card_holder_name: Option<Secret<String>>,
        nick_name: Option<Secret<String>>,
    ) -> RouterResult<pm_transformers::StoreCardRespPayload>;
}

// Generic implementation
#[async_trait::async_trait]
impl<'a, D> NetworkTokenizationProcess<'a, D> for CardNetworkTokenizeExecutor<'a, D>
where
    D: Send + Sync + 'static,
{
    fn new(
        state: &'a SessionState,
        platform: &'a domain::Platform,
        data: &'a D,
        customer: &'a domain_request_types::CustomerDetails,
    ) -> Self {
        Self {
            data,
            customer,
            state,
            merchant_account: platform.get_processor().get_account(),
            key_store: platform.get_processor().get_key_store(),
        }
    }
    async fn encrypt_card(
        &self,
        card_details: &domain::CardDetail,
        saved_to_locker: bool,
    ) -> RouterResult<Encryptable<Secret<serde_json::Value>>> {
        let pm_data = api::PaymentMethodsData::Card(api::CardDetailsPaymentMethod {
            last4_digits: Some(card_details.card_number.get_last4()),
            expiry_month: Some(card_details.card_exp_month.clone()),
            expiry_year: Some(card_details.card_exp_year.clone()),
            card_isin: Some(card_details.card_number.get_card_isin()),
            nick_name: card_details.nick_name.clone(),
            card_holder_name: card_details.card_holder_name.clone(),
            issuer_country: card_details.card_issuing_country.clone(),
            card_issuer: card_details.card_issuer.clone(),
            card_network: card_details.card_network.clone(),
            card_type: card_details.card_type.clone(),
            saved_to_locker,
            co_badged_card_data: card_details
                .co_badged_card_data
                .as_ref()
                .map(|data| data.into()),
        });

        create_encrypted_data(&self.state.into(), self.key_store, pm_data)
            .await
            .inspect_err(|err| logger::info!("Error encrypting payment method data: {:?}", err))
            .change_context(errors::ApiErrorResponse::InternalServerError)
    }
    async fn encrypt_network_token(
        &self,
        network_token_details: &NetworkTokenizationResponse,
        card_details: &domain::CardDetail,
        saved_to_locker: bool,
    ) -> RouterResult<Encryptable<Secret<serde_json::Value>>> {
        let network_token = &network_token_details.0;
        let token_data = api::PaymentMethodsData::Card(api::CardDetailsPaymentMethod {
            last4_digits: Some(network_token.token_last_four.clone()),
            expiry_month: Some(network_token.token_expiry_month.clone()),
            expiry_year: Some(network_token.token_expiry_year.clone()),
            card_isin: Some(network_token.token_isin.clone()),
            nick_name: card_details.nick_name.clone(),
            card_holder_name: card_details.card_holder_name.clone(),
            issuer_country: card_details.card_issuing_country.clone(),
            card_issuer: card_details.card_issuer.clone(),
            card_network: card_details.card_network.clone(),
            card_type: card_details.card_type.clone(),
            saved_to_locker,
            co_badged_card_data: None,
        });
        create_encrypted_data(&self.state.into(), self.key_store, token_data)
            .await
            .inspect_err(|err| logger::info!("Error encrypting network token data: {:?}", err))
            .change_context(errors::ApiErrorResponse::InternalServerError)
    }
    async fn fetch_bin_details_and_validate_card_network(
        &self,
        card_number: CardNumber,
        card_issuer: Option<&String>,
        card_network: Option<&api_enums::CardNetwork>,
        card_type: Option<&api_models::payment_methods::CardType>,
        card_issuing_country: Option<&String>,
    ) -> RouterResult<Option<diesel_models::CardInfo>> {
        let db = &*self.state.store;
        if card_issuer.is_some()
            && card_network.is_some()
            && card_type.is_some()
            && card_issuing_country.is_some()
        {
            self.validate_card_network(card_network)?;
            return Ok(None);
        }

        db.get_card_info(&card_number.get_card_isin())
            .await
            .attach_printable("Failed to perform BIN lookup")
            .change_context(errors::ApiErrorResponse::InternalServerError)?
            .map(|card_info| {
                self.validate_card_network(card_info.card_network.as_ref())?;
                Ok(card_info)
            })
            .transpose()
    }
    async fn tokenize_card(
        &self,
        customer_id: &id_type::CustomerId,
        card: &domain::CardDetail,
        optional_cvc: Option<Secret<String>>,
    ) -> RouterResult<NetworkTokenizationResponse> {
        network_tokenization::make_card_network_tokenization_request(
            self.state,
            card,
            optional_cvc,
            customer_id,
        )
        .await
        .map_err(|err| {
            logger::error!("Failed to tokenize card with the network: {:?}", err);
            report!(errors::ApiErrorResponse::InternalServerError)
        })
    }
    fn validate_card_network(
        &self,
        optional_card_network: Option<&api_enums::CardNetwork>,
    ) -> RouterResult<()> {
        optional_card_network.map_or(
            Err(report!(errors::ApiErrorResponse::NotSupported {
                message: "Unknown card network".to_string()
            })),
            |card_network| {
                if self
                    .state
                    .conf
                    .network_tokenization_supported_card_networks
                    .card_networks
                    .contains(card_network)
                {
                    Ok(())
                } else {
                    Err(report!(errors::ApiErrorResponse::NotSupported {
                        message: format!(
                            "Network tokenization for {card_network} is not supported",
                        )
                    }))
                }
            },
        )
    }
    async fn store_network_token_in_locker(
        &self,
        network_token: &NetworkTokenizationResponse,
        customer_id: &id_type::CustomerId,
        card_holder_name: Option<Secret<String>>,
        nick_name: Option<Secret<String>>,
    ) -> RouterResult<pm_transformers::StoreCardRespPayload> {
        let network_token = &network_token.0;
        let merchant_id = self.merchant_account.get_id();
        let locker_req =
            pm_transformers::StoreLockerReq::LockerCard(pm_transformers::StoreCardReq {
                merchant_id: merchant_id.clone(),
                merchant_customer_id: customer_id.clone(),
                card: payment_methods_api::Card {
                    card_number: network_token.token.clone(),
                    card_exp_month: network_token.token_expiry_month.clone(),
                    card_exp_year: network_token.token_expiry_year.clone(),
                    card_brand: Some(network_token.card_brand.to_string()),
                    card_isin: Some(network_token.token_isin.clone()),
                    name_on_card: card_holder_name,
                    nick_name: nick_name.map(|nick_name| nick_name.expose()),
                },
                requestor_card_reference: None,
                ttl: self.state.conf.locker.ttl_for_storage_in_secs,
            });

        let stored_resp = add_card_to_hs_locker(
            self.state,
            &locker_req,
            customer_id,
            api_enums::LockerChoice::HyperswitchCardVault,
        )
        .await
        .inspect_err(|err| logger::info!("Error adding card in locker: {:?}", err))
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

        Ok(stored_resp)
    }
}


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

use actix_multipart::form::{self, bytes, text};
use api_models::payment_methods as pm_api;
use common_utils::{errors::CustomResult, id_type};
use csv::Reader;
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    api::ApplicationResponse, errors::api_error_response as errors,
    payment_methods::PaymentMethodUpdate, platform,
};
use masking::{ExposeInterface, PeekInterface};
use payment_methods::core::migration::MerchantConnectorValidator;
use rdkafka::message::ToBytes;
use router_env::logger;

use crate::{
    core::{errors::StorageErrorExt, payment_methods::cards::create_encrypted_data},
    routes::SessionState,
};
type PmMigrationResult<T> = CustomResult<ApplicationResponse<T>, errors::ApiErrorResponse>;

#[cfg(feature = "v1")]
pub async fn update_payment_methods(
    state: &SessionState,
    payment_methods: Vec<pm_api::UpdatePaymentMethodRecord>,
    merchant_id: &id_type::MerchantId,
    platform: &platform::Platform,
) -> PmMigrationResult<Vec<pm_api::PaymentMethodUpdateResponse>> {
    let mut result = Vec::with_capacity(payment_methods.len());

    for record in payment_methods {
        let update_res =
            update_payment_method_record(state, record.clone(), merchant_id, platform).await;
        let res = match update_res {
            Ok(ApplicationResponse::Json(response)) => Ok(response),
            Err(e) => Err(e.to_string()),
            _ => Err("Failed to update payment method".to_string()),
        };

        result.push(pm_api::PaymentMethodUpdateResponse::from((res, record)));
    }
    Ok(ApplicationResponse::Json(result))
}

#[cfg(feature = "v1")]
pub async fn update_payment_method_record(
    state: &SessionState,
    req: pm_api::UpdatePaymentMethodRecord,
    merchant_id: &id_type::MerchantId,
    platform: &platform::Platform,
) -> CustomResult<
    ApplicationResponse<pm_api::PaymentMethodRecordUpdateResponse>,
    errors::ApiErrorResponse,
> {
    use std::collections::HashMap;

    use common_enums::enums;
    use common_utils::pii;
    use hyperswitch_domain_models::mandates::{
        CommonMandateReference, PaymentsMandateReference, PaymentsMandateReferenceRecord,
        PayoutsMandateReference, PayoutsMandateReferenceRecord,
    };

    let db = &*state.store;
    let payment_method_id = req.payment_method_id.clone();
    let network_transaction_id = req.network_transaction_id.clone();
    let status = req.status;
    let key_manager_state = state.into();
    let mut updated_card_expiry = false;

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

    if payment_method.merchant_id != *merchant_id {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
                    message: "Merchant ID in the request does not match the Merchant ID in the payment method record.".to_string(),
                }.into());
    }

    let updated_payment_method_data = match payment_method.payment_method_data.as_ref() {
        Some(data) => {
            match serde_json::from_value::<pm_api::PaymentMethodsData>(
                data.clone().into_inner().expose(),
            ) {
                Ok(pm_api::PaymentMethodsData::Card(mut card_data)) => {
                    if let Some(new_month) = &req.card_expiry_month {
                        card_data.expiry_month = Some(new_month.clone());
                        updated_card_expiry = true;
                    }

                    if let Some(new_year) = &req.card_expiry_year {
                        card_data.expiry_year = Some(new_year.clone());
                        updated_card_expiry = true;
                    }

                    if updated_card_expiry {
                        Some(
                            create_encrypted_data(
                                &key_manager_state,
                                platform.get_processor().get_key_store(),
                                pm_api::PaymentMethodsData::Card(card_data),
                            )
                            .await
                            .change_context(errors::ApiErrorResponse::InternalServerError)
                            .attach_printable("Unable to encrypt payment method data")
                            .map(Into::into),
                        )
                    } else {
                        None
                    }
                }
                _ => None,
            }
        }
        None => None,
    }
    .transpose()?;

    let mca_data_cache = if let Some(merchant_connector_ids) = &req.merchant_connector_ids {
        let parsed_mca_ids =
            MerchantConnectorValidator::parse_comma_separated_ids(merchant_connector_ids)?;
        let mut cache = HashMap::new();

        for merchant_connector_id in parsed_mca_ids {
            let mca = db
                .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
                    platform.get_processor().get_account().get_id(),
                    &merchant_connector_id,
                    platform.get_processor().get_key_store(),
                )
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: merchant_connector_id.get_string_repr().to_string(),
                    },
                )?;

            cache.insert(merchant_connector_id, mca);
        }
        Some(cache)
    } else {
        None
    };

    let pm_update = match (
        &req.payment_instrument_id,
        &req.connector_customer_id,
        &mca_data_cache,
    ) {
        // Case: payment_instrument_id (with or without connector_customer_id)
        (Some(payment_instrument_id), _, Some(cache)) => {
            let mandate_details = payment_method
                .get_common_mandate_reference()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to deserialize to Payment Mandate Reference ")?;

            let mut existing_payments_mandate = mandate_details
                .payments
                .clone()
                .unwrap_or(PaymentsMandateReference(HashMap::new()));
            let mut existing_payouts_mandate = mandate_details
                .payouts
                .clone()
                .unwrap_or(PayoutsMandateReference(HashMap::new()));

            for (merchant_connector_id, mca) in cache.iter() {
                match mca.connector_type {
                    enums::ConnectorType::PayoutProcessor => {
                        let new_payout_record = PayoutsMandateReferenceRecord {
                            transfer_method_id: Some(payment_instrument_id.peek().to_string()),
                            connector_customer_id: req.connector_customer_id.clone(),
                        };

                        if let Some(existing_record) =
                            existing_payouts_mandate.0.get_mut(merchant_connector_id)
                        {
                            if let Some(transfer_method_id) = &new_payout_record.transfer_method_id
                            {
                                existing_record.transfer_method_id =
                                    Some(transfer_method_id.clone());
                            }
                            // Update connector_customer_id if provided
                            if req.connector_customer_id.is_some() {
                                existing_record.connector_customer_id =
                                    req.connector_customer_id.clone();
                            }
                        } else {
                            existing_payouts_mandate
                                .0
                                .insert(merchant_connector_id.clone(), new_payout_record);
                        }
                    }
                    _ => {
                        if let Some(existing_record) =
                            existing_payments_mandate.0.get_mut(merchant_connector_id)
                        {
                            existing_record.connector_mandate_id =
                                payment_instrument_id.peek().to_string();
                            // Update connector_customer_id if provided
                            if req.connector_customer_id.is_some() {
                                existing_record.connector_customer_id =
                                    req.connector_customer_id.clone();
                            }
                        } else {
                            existing_payments_mandate.0.insert(
                                merchant_connector_id.clone(),
                                PaymentsMandateReferenceRecord {
                                    connector_mandate_id: payment_instrument_id.peek().to_string(),
                                    payment_method_type: None,
                                    original_payment_authorized_amount: None,
                                    original_payment_authorized_currency: None,
                                    mandate_metadata: None,
                                    connector_mandate_status: None,
                                    connector_mandate_request_reference_id: None,
                                    connector_customer_id: req.connector_customer_id.clone(),
                                },
                            );
                        }
                    }
                }
            }

            let updated_connector_mandate_details = CommonMandateReference {
                payments: if !existing_payments_mandate.0.is_empty() {
                    Some(existing_payments_mandate)
                } else {
                    mandate_details.payments
                },
                payouts: if !existing_payouts_mandate.0.is_empty() {
                    Some(existing_payouts_mandate)
                } else {
                    mandate_details.payouts
                },
            };

            let connector_mandate_details_value = updated_connector_mandate_details
                .get_mandate_details_value()
                .map_err(|err| {
                    logger::error!("Failed to get get_mandate_details_value : {:?}", err);
                    errors::ApiErrorResponse::MandateUpdateFailed
                })?;

            PaymentMethodUpdate::PaymentMethodBatchUpdate {
                connector_mandate_details: Some(pii::SecretSerdeValue::new(
                    connector_mandate_details_value,
                )),
                network_transaction_id,
                status,
                payment_method_data: updated_payment_method_data.clone(),
                last_modified_by: None,
            }
        }
        // Case: Only connector_customer_id provided
        (None, Some(connector_customer_id), Some(cache)) => {
            let mandate_details = payment_method
                .get_common_mandate_reference()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to deserialize to Payment Mandate Reference ")?;

            // Error if no mandate details exist
            if mandate_details.payments.is_none() && mandate_details.payouts.is_none() {
                return Err(errors::ApiErrorResponse::InvalidRequestData {
                    message: "connector_mandate_details does not exist".to_string(),
                }
                .into());
            }

            let mut existing_payments_mandate = mandate_details
                .payments
                .clone()
                .unwrap_or(PaymentsMandateReference(HashMap::new()));
            let mut existing_payouts_mandate = mandate_details
                .payouts
                .clone()
                .unwrap_or(PayoutsMandateReference(HashMap::new()));

            // For each merchant_connector_id, check appropriate reference based on connector_type
            for (merchant_connector_id, mca) in cache.iter() {
                match mca.connector_type {
                    enums::ConnectorType::PayoutProcessor => {
                        // Check if exists in payouts mandate
                        if let Some(existing_record) =
                            existing_payouts_mandate.0.get_mut(merchant_connector_id)
                        {
                            existing_record.connector_customer_id =
                                Some(connector_customer_id.clone());
                        } else {
                            return Err(errors::ApiErrorResponse::InvalidRequestData {
                                message: format!("merchant_connector_id {} does not exist in payouts connector_mandate_details", merchant_connector_id.get_string_repr()),
                            }.into());
                        }
                    }
                    _ => {
                        // Check if exists in payments mandate
                        if let Some(existing_record) =
                            existing_payments_mandate.0.get_mut(merchant_connector_id)
                        {
                            existing_record.connector_customer_id =
                                Some(connector_customer_id.clone());
                        } else {
                            return Err(errors::ApiErrorResponse::InvalidRequestData {
                                message: format!("merchant_connector_id {} does not exist in payments connector_mandate_details", merchant_connector_id.get_string_repr()),
                            }.into());
                        }
                    }
                }
            }

            let updated_connector_mandate_details = CommonMandateReference {
                payments: if !existing_payments_mandate.0.is_empty() {
                    Some(existing_payments_mandate)
                } else {
                    mandate_details.payments
                },
                payouts: if !existing_payouts_mandate.0.is_empty() {
                    Some(existing_payouts_mandate)
                } else {
                    mandate_details.payouts
                },
            };

            let connector_mandate_details_value = updated_connector_mandate_details
                .get_mandate_details_value()
                .map_err(|err| {
                    logger::error!("Failed to get get_mandate_details_value : {:?}", err);
                    errors::ApiErrorResponse::MandateUpdateFailed
                })?;

            PaymentMethodUpdate::PaymentMethodBatchUpdate {
                connector_mandate_details: Some(pii::SecretSerdeValue::new(
                    connector_mandate_details_value,
                )),
                network_transaction_id,
                status,
                payment_method_data: updated_payment_method_data.clone(),
                last_modified_by: None,
            }
        }
        _ => {
            if updated_payment_method_data.is_some() {
                PaymentMethodUpdate::PaymentMethodDataUpdate {
                    payment_method_data: updated_payment_method_data,
                    last_modified_by: None,
                }
            } else {
                PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate {
                    network_transaction_id,
                    status,
                    last_modified_by: None,
                }
            }
        }
    };

    let response = db
        .update_payment_method(
            platform.get_processor().get_key_store(),
            payment_method,
            pm_update,
            platform.get_processor().get_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable(format!(
            "Failed to update payment method for existing pm_id: {payment_method_id:?} in db",
        ))?;

    Ok(ApplicationResponse::Json(
        pm_api::PaymentMethodRecordUpdateResponse {
            payment_method_id: response.payment_method_id,
            status: response.status,
            network_transaction_id: response.network_transaction_id,
            connector_mandate_details: response
                .connector_mandate_details
                .map(pii::SecretSerdeValue::new),
            updated_payment_method_data: Some(updated_card_expiry),
        },
    ))
}

#[derive(Debug, form::MultipartForm)]
pub struct PaymentMethodsUpdateForm {
    #[multipart(limit = "1MB")]
    pub file: bytes::Bytes,

    pub merchant_id: text::Text<id_type::MerchantId>,
}

fn parse_update_csv(data: &[u8]) -> csv::Result<Vec<pm_api::UpdatePaymentMethodRecord>> {
    let mut csv_reader = Reader::from_reader(data);
    let mut records = Vec::new();
    let mut id_counter = 0;
    for result in csv_reader.deserialize() {
        let mut record: pm_api::UpdatePaymentMethodRecord = result?;
        id_counter += 1;
        record.line_number = Some(id_counter);
        records.push(record);
    }
    Ok(records)
}

type UpdateValidationResult =
    Result<(id_type::MerchantId, Vec<pm_api::UpdatePaymentMethodRecord>), errors::ApiErrorResponse>;

impl PaymentMethodsUpdateForm {
    pub fn validate_and_get_payment_method_records(self) -> UpdateValidationResult {
        let records = parse_update_csv(self.file.data.to_bytes()).map_err(|e| {
            errors::ApiErrorResponse::PreconditionFailed {
                message: e.to_string(),
            }
        })?;
        Ok((self.merchant_id.clone(), records))
    }
}