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

use api_models::{blocklist, enums as api_enums};
use common_utils::{
    ext_traits::{Encode, StringExt},
    id_type,
    request::RequestContent,
};
use error_stack::ResultExt;
use josekit::jwe;
use masking::{PeekInterface, StrongSecret};
use router_env::{instrument, tracing};

use crate::{
    configs::settings,
    core::{
        errors::{self, CustomResult},
        payment_methods::transformers as payment_methods,
    },
    headers, routes,
    services::{api as services, encryption, EncryptionAlgorithm},
    types::{storage, transformers::ForeignFrom},
    utils::ConnectorResponseExt,
};

const LOCKER_FINGERPRINT_PATH: &str = "/cards/fingerprint";

impl ForeignFrom<storage::Blocklist> for blocklist::AddToBlocklistResponse {
    fn foreign_from(from: storage::Blocklist) -> Self {
        Self {
            fingerprint_id: from.fingerprint_id,
            data_kind: from.data_kind,
            created_at: from.created_at,
        }
    }
}

async fn generate_fingerprint_request(
    jwekey: &settings::Jwekey,
    locker: &settings::Locker,
    payload: &blocklist::GenerateFingerprintRequest,
    locker_choice: api_enums::LockerChoice,
    tenant_id: id_type::TenantId,
    request_id: Option<router_env::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 = generate_jwe_payload_for_request(jwekey, &jws, locker_choice).await?;
    let mut url = match locker_choice {
        api_enums::LockerChoice::HyperswitchCardVault => locker.host.to_owned(),
    };
    url.push_str(LOCKER_FINGERPRINT_PATH);
    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)
}

async fn generate_jwe_payload_for_request(
    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::GenerateFingerprintFailed)?;

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

    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::GenerateFingerprintFailed)?;

    Ok(jwe_body)
}

#[instrument(skip_all)]
pub async fn generate_fingerprint(
    state: &routes::SessionState,
    card_number: StrongSecret<String>,
    hash_key: StrongSecret<String>,
    locker_choice: api_enums::LockerChoice,
) -> CustomResult<blocklist::GenerateFingerprintResponsePayload, errors::VaultError> {
    let payload = blocklist::GenerateFingerprintRequest {
        data: card_number,
        key: hash_key,
    };

    let generate_fingerprint_resp =
        call_to_locker_for_fingerprint(state, &payload, locker_choice).await?;

    Ok(generate_fingerprint_resp)
}

#[instrument(skip_all)]
async fn call_to_locker_for_fingerprint(
    state: &routes::SessionState,
    payload: &blocklist::GenerateFingerprintRequest,
    locker_choice: api_enums::LockerChoice,
) -> CustomResult<blocklist::GenerateFingerprintResponsePayload, errors::VaultError> {
    let locker = &state.conf.locker;
    let jwekey = state.conf.jwekey.get_inner();

    let request = generate_fingerprint_request(
        jwekey,
        locker,
        payload,
        locker_choice,
        state.tenant.tenant_id.clone(),
        state.request_id.clone(),
    )
    .await?;
    let response = services::call_connector_api(state, request, "call_locker_to_get_fingerprint")
        .await
        .change_context(errors::VaultError::GenerateFingerprintFailed);
    let jwe_body: encryption::JweBody = response
        .get_response_inner("JweBody")
        .change_context(errors::VaultError::GenerateFingerprintFailed)?;

    let decrypted_payload = decrypt_generate_fingerprint_response_payload(
        jwekey,
        jwe_body,
        Some(locker_choice),
        locker.decryption_scheme.clone(),
    )
    .await
    .change_context(errors::VaultError::GenerateFingerprintFailed)
    .attach_printable("Error getting decrypted fingerprint response payload")?;
    let generate_fingerprint_response: blocklist::GenerateFingerprintResponsePayload =
        decrypted_payload
            .parse_struct("GenerateFingerprintResponse")
            .change_context(errors::VaultError::ResponseDeserializationFailed)?;

    Ok(generate_fingerprint_response)
}

async fn decrypt_generate_fingerprint_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 = payment_methods::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 = payment_methods::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")
}


// File: crates/router/src/core/blocklist/utils.rs

use api_models::blocklist as api_blocklist;
use common_enums::MerchantDecision;
use common_utils::errors::CustomResult;
use diesel_models::configs;
use error_stack::ResultExt;
use masking::StrongSecret;

use super::{errors, transformers::generate_fingerprint, SessionState};
use crate::{
    consts,
    core::{
        errors::{RouterResult, StorageErrorExt},
        payments::PaymentData,
    },
    logger,
    types::{domain, storage, transformers::ForeignInto},
    utils,
};

pub async fn delete_entry_from_blocklist(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    request: api_blocklist::DeleteFromBlocklistRequest,
) -> RouterResult<api_blocklist::DeleteFromBlocklistResponse> {
    let blocklist_entry = match request {
        api_blocklist::DeleteFromBlocklistRequest::CardBin(bin) => {
            delete_card_bin_blocklist_entry(state, &bin, merchant_id).await?
        }

        api_blocklist::DeleteFromBlocklistRequest::ExtendedCardBin(xbin) => {
            delete_card_bin_blocklist_entry(state, &xbin, merchant_id).await?
        }

        api_blocklist::DeleteFromBlocklistRequest::Fingerprint(fingerprint_id) => state
            .store
            .delete_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, &fingerprint_id)
            .await
            .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
                message: "no blocklist record for the given fingerprint id was found".to_string(),
            })?,
    };

    Ok(blocklist_entry.foreign_into())
}

pub async fn toggle_blocklist_guard_for_merchant(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    query: api_blocklist::ToggleBlocklistQuery,
) -> CustomResult<api_blocklist::ToggleBlocklistResponse, errors::ApiErrorResponse> {
    let key = merchant_id.get_blocklist_guard_key();
    let maybe_guard = state.store.find_config_by_key_from_db(&key).await;
    let new_config = configs::ConfigNew {
        key: key.clone(),
        config: query.status.to_string(),
    };
    match maybe_guard {
        Ok(_config) => {
            let updated_config = configs::ConfigUpdate::Update {
                config: Some(query.status.to_string()),
            };
            state
                .store
                .update_config_by_key(&key, updated_config)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error enabling the blocklist guard")?;
        }
        Err(e) if e.current_context().is_db_not_found() => {
            state
                .store
                .insert_config(new_config)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error enabling the blocklist guard")?;
        }
        Err(error) => {
            logger::error!(?error);
            Err(error)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Error enabling the blocklist guard")?;
        }
    };
    let guard_status = if query.status { "enabled" } else { "disabled" };
    Ok(api_blocklist::ToggleBlocklistResponse {
        blocklist_guard_status: guard_status.to_string(),
    })
}

pub async fn list_blocklist_entries_for_merchant(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    query: api_blocklist::ListBlocklistQuery,
) -> RouterResult<Vec<api_blocklist::BlocklistResponse>> {
    state
        .store
        .list_blocklist_entries_by_merchant_id_data_kind(
            merchant_id,
            query.data_kind,
            query.limit.into(),
            query.offset.into(),
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
            message: "no blocklist records found".to_string(),
        })
        .map(|v| v.into_iter().map(ForeignInto::foreign_into).collect())
}

fn validate_card_bin(bin: &str) -> RouterResult<()> {
    if bin.len() == 6 && bin.chars().all(|c| c.is_ascii_digit()) {
        Ok(())
    } else {
        Err(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "data".to_string(),
            expected_format: "a 6 digit number".to_string(),
        }
        .into())
    }
}

fn validate_extended_card_bin(bin: &str) -> RouterResult<()> {
    if bin.len() == 8 && bin.chars().all(|c| c.is_ascii_digit()) {
        Ok(())
    } else {
        Err(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "data".to_string(),
            expected_format: "an 8 digit number".to_string(),
        }
        .into())
    }
}

pub async fn insert_entry_into_blocklist(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    to_block: api_blocklist::AddToBlocklistRequest,
) -> RouterResult<api_blocklist::AddToBlocklistResponse> {
    let blocklist_entry = match &to_block {
        api_blocklist::AddToBlocklistRequest::CardBin(bin) => {
            validate_card_bin(bin)?;
            duplicate_check_insert_bin(
                bin,
                state,
                merchant_id,
                common_enums::BlocklistDataKind::CardBin,
            )
            .await?
        }

        api_blocklist::AddToBlocklistRequest::ExtendedCardBin(bin) => {
            validate_extended_card_bin(bin)?;
            duplicate_check_insert_bin(
                bin,
                state,
                merchant_id,
                common_enums::BlocklistDataKind::ExtendedCardBin,
            )
            .await?
        }

        api_blocklist::AddToBlocklistRequest::Fingerprint(fingerprint_id) => {
            let blocklist_entry_result = state
                .store
                .find_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, fingerprint_id)
                .await;

            match blocklist_entry_result {
                Ok(_) => {
                    return Err(errors::ApiErrorResponse::PreconditionFailed {
                        message: "data associated with the given fingerprint is already blocked"
                            .to_string(),
                    }
                    .into());
                }

                // if it is a db not found error, we can proceed as normal
                Err(inner) if inner.current_context().is_db_not_found() => {}

                err @ Err(_) => {
                    err.change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("error fetching blocklist entry from table")?;
                }
            }

            state
                .store
                .insert_blocklist_entry(storage::BlocklistNew {
                    merchant_id: merchant_id.to_owned(),
                    fingerprint_id: fingerprint_id.clone(),
                    data_kind: api_models::enums::enums::BlocklistDataKind::PaymentMethod,
                    metadata: None,
                    created_at: common_utils::date_time::now(),
                })
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("failed to add fingerprint to blocklist")?
        }
    };
    Ok(blocklist_entry.foreign_into())
}

pub async fn get_merchant_fingerprint_secret(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
) -> RouterResult<String> {
    let key = merchant_id.get_merchant_fingerprint_secret_key();
    let config_fetch_result = state.store.find_config_by_key(&key).await;

    match config_fetch_result {
        Ok(config) => Ok(config.config),

        Err(e) if e.current_context().is_db_not_found() => {
            let new_fingerprint_secret =
                utils::generate_id(consts::FINGERPRINT_SECRET_LENGTH, "fs");
            let new_config = storage::ConfigNew {
                key,
                config: new_fingerprint_secret.clone(),
            };

            state
                .store
                .insert_config(new_config)
                .await
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("unable to create new fingerprint secret for merchant")?;

            Ok(new_fingerprint_secret)
        }

        Err(e) => Err(e)
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("error fetching merchant fingerprint secret"),
    }
}

async fn duplicate_check_insert_bin(
    bin: &str,
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    data_kind: common_enums::BlocklistDataKind,
) -> RouterResult<storage::Blocklist> {
    let blocklist_entry_result = state
        .store
        .find_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, bin)
        .await;

    match blocklist_entry_result {
        Ok(_) => {
            return Err(errors::ApiErrorResponse::PreconditionFailed {
                message: "provided bin is already blocked".to_string(),
            }
            .into());
        }

        Err(e) if e.current_context().is_db_not_found() => {}

        err @ Err(_) => {
            return err
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("unable to fetch blocklist entry");
        }
    }

    state
        .store
        .insert_blocklist_entry(storage::BlocklistNew {
            merchant_id: merchant_id.to_owned(),
            fingerprint_id: bin.to_string(),
            data_kind,
            metadata: None,
            created_at: common_utils::date_time::now(),
        })
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("error inserting pm blocklist item")
}

async fn delete_card_bin_blocklist_entry(
    state: &SessionState,
    bin: &str,
    merchant_id: &common_utils::id_type::MerchantId,
) -> RouterResult<storage::Blocklist> {
    state
        .store
        .delete_blocklist_entry_by_merchant_id_fingerprint_id(merchant_id, bin)
        .await
        .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
            message: "could not find a blocklist entry for the given bin".to_string(),
        })
}

pub async fn should_payment_be_blocked(
    state: &SessionState,
    platform: &domain::Platform,
    payment_method_data: &Option<domain::PaymentMethodData>,
) -> CustomResult<bool, errors::ApiErrorResponse> {
    let db = &state.store;
    let merchant_id = platform.get_processor().get_account().get_id();
    let merchant_fingerprint_secret = get_merchant_fingerprint_secret(state, merchant_id).await?;

    // Hashed Fingerprint to check whether or not this payment should be blocked.
    let card_number_fingerprint =
        if let Some(domain::PaymentMethodData::Card(card)) = payment_method_data {
            generate_fingerprint(
                state,
                StrongSecret::new(card.card_number.get_card_no()),
                StrongSecret::new(merchant_fingerprint_secret.clone()),
                api_models::enums::LockerChoice::HyperswitchCardVault,
            )
            .await
            .attach_printable("error in pm fingerprint creation")
            .map_or_else(
                |error| {
                    logger::error!(?error);
                    None
                },
                Some,
            )
            .map(|payload| payload.fingerprint_id)
        } else {
            None
        };

    // Hashed Cardbin to check whether or not this payment should be blocked.
    let card_bin_fingerprint = payment_method_data
        .as_ref()
        .and_then(|pm_data| match pm_data {
            domain::PaymentMethodData::Card(card) => Some(card.card_number.get_card_isin()),
            _ => None,
        });

    // Hashed Extended Cardbin to check whether or not this payment should be blocked.
    let extended_card_bin_fingerprint =
        payment_method_data
            .as_ref()
            .and_then(|pm_data| match pm_data {
                domain::PaymentMethodData::Card(card) => {
                    Some(card.card_number.get_extended_card_bin())
                }
                _ => None,
            });

    //validating the payment method.
    let mut blocklist_futures = Vec::new();
    if let Some(card_number_fingerprint) = card_number_fingerprint.as_ref() {
        blocklist_futures.push(db.find_blocklist_entry_by_merchant_id_fingerprint_id(
            merchant_id,
            card_number_fingerprint,
        ));
    }

    if let Some(card_bin_fingerprint) = card_bin_fingerprint.as_ref() {
        blocklist_futures.push(
            db.find_blocklist_entry_by_merchant_id_fingerprint_id(
                merchant_id,
                card_bin_fingerprint,
            ),
        );
    }

    if let Some(extended_card_bin_fingerprint) = extended_card_bin_fingerprint.as_ref() {
        blocklist_futures.push(db.find_blocklist_entry_by_merchant_id_fingerprint_id(
            merchant_id,
            extended_card_bin_fingerprint,
        ));
    }

    let blocklist_lookups = futures::future::join_all(blocklist_futures).await;

    let mut should_payment_be_blocked = false;
    for lookup in blocklist_lookups {
        match lookup {
            Ok(_) => {
                should_payment_be_blocked = true;
            }
            Err(e) => {
                logger::error!(blocklist_db_error=?e, "failed db operations for blocklist");
            }
        }
    }
    Ok(should_payment_be_blocked)
}

pub async fn validate_data_for_blocklist<F>(
    state: &SessionState,
    platform: &domain::Platform,
    payment_data: &mut PaymentData<F>,
) -> CustomResult<bool, errors::ApiErrorResponse>
where
    F: Send + Clone,
{
    let db = &state.store;
    let should_payment_be_blocked =
        should_payment_be_blocked(state, platform, &payment_data.payment_method_data).await?;
    if should_payment_be_blocked {
        // Update db for attempt and intent status.
        db.update_payment_intent(
            payment_data.payment_intent.clone(),
            storage::PaymentIntentUpdate::RejectUpdate {
                status: common_enums::IntentStatus::Failed,
                merchant_decision: Some(MerchantDecision::Rejected.to_string()),
                updated_by: platform
                    .get_processor()
                    .get_account()
                    .storage_scheme
                    .to_string(),
            },
            platform.get_processor().get_key_store(),
            platform.get_processor().get_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
        .attach_printable(
            "Failed to update status in Payment Intent to failed due to it being blocklisted",
        )?;

        // If payment is blocked not showing connector details
        let attempt_update = storage::PaymentAttemptUpdate::BlocklistUpdate {
            status: common_enums::AttemptStatus::Failure,
            error_code: Some(Some("HE-03".to_string())),
            error_message: Some(Some("This payment method is blocked".to_string())),
            updated_by: platform
                .get_processor()
                .get_account()
                .storage_scheme
                .to_string(),
        };
        db.update_payment_attempt_with_attempt_id(
            payment_data.payment_attempt.clone(),
            attempt_update,
            platform.get_processor().get_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
        .attach_printable(
            "Failed to update status in Payment Attempt to failed, due to it being blocklisted",
        )?;

        Err(errors::ApiErrorResponse::PaymentBlockedError {
            code: 200,
            message: "This payment method is blocked".to_string(),
            status: "Failed".to_string(),
            reason: "Blocked".to_string(),
        }
        .into())
    } else {
        payment_data.payment_attempt.fingerprint_id = generate_payment_fingerprint(
            state,
            payment_data.payment_attempt.merchant_id.clone(),
            payment_data.payment_method_data.clone(),
        )
        .await?;
        Ok(false)
    }
}

pub async fn generate_payment_fingerprint(
    state: &SessionState,
    merchant_id: common_utils::id_type::MerchantId,
    payment_method_data: Option<domain::PaymentMethodData>,
) -> CustomResult<Option<String>, errors::ApiErrorResponse> {
    let merchant_fingerprint_secret = get_merchant_fingerprint_secret(state, &merchant_id).await?;

    Ok(
        if let Some(domain::PaymentMethodData::Card(card)) = payment_method_data.as_ref() {
            generate_fingerprint(
                state,
                StrongSecret::new(card.card_number.get_card_no()),
                StrongSecret::new(merchant_fingerprint_secret),
                api_models::enums::LockerChoice::HyperswitchCardVault,
            )
            .await
            .attach_printable("error in pm fingerprint creation")
            .map_or_else(
                |error| {
                    logger::error!(?error);
                    None
                },
                Some,
            )
            .map(|payload| payload.fingerprint_id)
        } else {
            logger::error!("failed to retrieve card fingerprint");
            None
        },
    )
}


// File: crates/router/src/core/mandate/helpers.rs

use api_models::payments as api_payments;
use common_enums::enums;
use common_types::payments as common_payments_types;
use common_utils::errors::CustomResult;
use diesel_models::Mandate;
use error_stack::ResultExt;
use hyperswitch_domain_models::mandates::MandateData;

use crate::{
    core::{errors, payments},
    routes::SessionState,
    types::{api, domain},
};

#[cfg(feature = "v1")]
pub async fn get_profile_id_for_mandate(
    state: &SessionState,
    platform: &domain::Platform,
    mandate: Mandate,
) -> CustomResult<common_utils::id_type::ProfileId, errors::ApiErrorResponse> {
    let profile_id = if let Some(ref payment_id) = mandate.original_payment_id {
        let pi = state
            .store
            .find_payment_intent_by_payment_id_merchant_id(
                payment_id,
                platform.get_processor().get_account().get_id(),
                platform.get_processor().get_key_store(),
                platform.get_processor().get_account().storage_scheme,
            )
            .await
            .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
        let profile_id =
            pi.profile_id
                .clone()
                .ok_or(errors::ApiErrorResponse::ProfileNotFound {
                    id: pi
                        .profile_id
                        .map(|profile_id| profile_id.get_string_repr().to_owned())
                        .unwrap_or_else(|| "Profile id is Null".to_string()),
                })?;
        Ok(profile_id)
    } else {
        Err(errors::ApiErrorResponse::PaymentNotFound)
    }?;
    Ok(profile_id)
}

pub fn get_mandate_type(
    mandate_data: Option<api_payments::MandateData>,
    off_session: Option<bool>,
    setup_future_usage: Option<enums::FutureUsage>,
    customer_acceptance: Option<common_payments_types::CustomerAcceptance>,
    token: Option<String>,
    payment_method: Option<enums::PaymentMethod>,
) -> CustomResult<Option<api::MandateTransactionType>, errors::ValidationError> {
    match (
        mandate_data.clone(),
        off_session,
        setup_future_usage,
        customer_acceptance.or(mandate_data.and_then(|m_data| m_data.customer_acceptance)),
        token,
        payment_method,
    ) {
        (Some(_), Some(_), Some(enums::FutureUsage::OffSession), Some(_), Some(_), _) => {
            Err(errors::ValidationError::InvalidValue {
                message: "Expected one out of recurring_details and mandate_data but got both"
                    .to_string(),
            }
            .into())
        }
        (_, _, Some(enums::FutureUsage::OffSession), Some(_), Some(_), _)
        | (_, _, Some(enums::FutureUsage::OffSession), Some(_), _, _)
        | (Some(_), _, Some(enums::FutureUsage::OffSession), _, _, _) => {
            Ok(Some(api::MandateTransactionType::NewMandateTransaction))
        }

        (_, _, Some(enums::FutureUsage::OffSession), _, Some(_), _)
        | (_, Some(_), _, _, _, _)
        | (_, _, Some(enums::FutureUsage::OffSession), _, _, Some(enums::PaymentMethod::Wallet)) => {
            Ok(Some(
                api::MandateTransactionType::RecurringMandateTransaction,
            ))
        }

        _ => Ok(None),
    }
}
#[derive(Clone)]
pub struct MandateGenericData {
    pub token: Option<String>,
    pub payment_method: Option<enums::PaymentMethod>,
    pub payment_method_type: Option<enums::PaymentMethodType>,
    pub mandate_data: Option<MandateData>,
    pub recurring_mandate_payment_data:
        Option<hyperswitch_domain_models::router_data::RecurringMandatePaymentData>,
    pub mandate_connector: Option<payments::MandateConnectorDetails>,
    pub payment_method_info: Option<domain::PaymentMethod>,
}


// File: crates/router/src/core/mandate/utils.rs

use std::marker::PhantomData;

use common_utils::{errors::CustomResult, ext_traits::ValueExt};
use diesel_models::Mandate;
use error_stack::ResultExt;

use crate::{
    core::{errors, payments::helpers},
    types::{self, domain, PaymentAddress},
    SessionState,
};

const IRRELEVANT_ATTEMPT_ID_IN_MANDATE_REVOKE_FLOW: &str =
    "irrelevant_attempt_id_in_mandate_revoke_flow";

const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_MANDATE_REVOKE_FLOW: &str =
    "irrelevant_connector_request_reference_id_in_mandate_revoke_flow";

pub async fn construct_mandate_revoke_router_data(
    state: &SessionState,
    merchant_connector_account: helpers::MerchantConnectorAccountType,
    platform: &domain::Platform,
    mandate: Mandate,
) -> CustomResult<types::MandateRevokeRouterData, errors::ApiErrorResponse> {
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;
    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().clone(),
        customer_id: Some(mandate.customer_id),
        tenant_id: state.tenant.tenant_id.clone(),
        connector_customer: None,
        connector: mandate.connector,
        payment_id: mandate
            .original_payment_id
            .unwrap_or_else(|| {
                common_utils::id_type::PaymentId::get_irrelevant_id("mandate_revoke")
            })
            .get_string_repr()
            .to_owned(),
        attempt_id: IRRELEVANT_ATTEMPT_ID_IN_MANDATE_REVOKE_FLOW.to_string(),
        status: diesel_models::enums::AttemptStatus::default(),
        payment_method: diesel_models::enums::PaymentMethod::default(),
        payment_method_type: None,
        connector_auth_type: auth_type,
        description: None,
        address: PaymentAddress::default(),
        auth_type: diesel_models::enums::AuthenticationType::default(),
        connector_meta_data: None,
        connector_wallets_details: None,
        amount_captured: None,
        minor_amount_captured: None,
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        payment_method_balance: None,
        connector_api_version: None,
        payment_method_status: None,
        request: types::MandateRevokeRequestData {
            mandate_id: mandate.mandate_id,
            connector_mandate_id: mandate.connector_mandate_id,
        },
        response: Err(types::ErrorResponse::get_not_implemented()),
        connector_request_reference_id:
            IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_MANDATE_REVOKE_FLOW.to_string(),
        test_mode: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        refund_id: None,
        dispute_id: None,
        connector_response: None,
        integrity_check: Ok(()),
        additional_merchant_data: None,
        header_payload: None,
        connector_mandate_request_reference_id: None,
        authentication_id: None,
        psd2_sca_exemption_type: None,
        raw_connector_response: None,
        is_payment_id_from_merchant: None,
        l2_l3_data: None,
        minor_amount_capturable: None,
        authorized_amount: None,
    };

    Ok(router_data)
}


// File: crates/router/src/core/unified_authentication_service/types.rs

use api_models::payments;
use hyperswitch_domain_models::{
    errors::api_error_response::{self as errors, NotImplementedMessage},
    router_request_types::{
        authentication::MessageCategory,
        unified_authentication_service::{
            UasAuthenticationRequestData, UasPostAuthenticationRequestData,
            UasPreAuthenticationRequestData,
        },
        BrowserInformation,
    },
};

use crate::{
    core::{errors::RouterResult, payments::helpers::MerchantConnectorAccountType},
    db::domain,
    routes::SessionState,
};

pub const CTP_MASTERCARD: &str = "ctp_mastercard";

pub const UNIFIED_AUTHENTICATION_SERVICE: &str = "unified_authentication_service";

pub const IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW: &str =
    "irrelevant_attempt_id_in_AUTHENTICATION_flow";

pub const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW: &str =
    "irrelevant_connector_request_reference_id_in_AUTHENTICATION_flow";

pub struct ClickToPay;

pub struct ExternalAuthentication;

#[async_trait::async_trait]
pub trait UnifiedAuthenticationService {
    #[allow(clippy::too_many_arguments)]
    fn get_pre_authentication_request_data(
        _payment_method_data: Option<&domain::PaymentMethodData>,
        _service_details: Option<payments::CtpServiceDetails>,
        _amount: common_utils::types::MinorUnit,
        _currency: Option<common_enums::Currency>,
        _merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        _billing_address: Option<&hyperswitch_domain_models::address::Address>,
        _acquirer_bin: Option<String>,
        _acquirer_merchant_id: Option<String>,
        _payment_method_type: Option<common_enums::PaymentMethodType>,
    ) -> RouterResult<UasPreAuthenticationRequestData> {
        Err(errors::ApiErrorResponse::NotImplemented {
            message: NotImplementedMessage::Reason(
                "get_pre_authentication_request_data".to_string(),
            ),
        }
        .into())
    }

    #[allow(clippy::too_many_arguments)]
    async fn pre_authentication(
        _state: &SessionState,
        _merchant_id: &common_utils::id_type::MerchantId,
        _payment_id: Option<&common_utils::id_type::PaymentId>,
        _payment_method_data: Option<&domain::PaymentMethodData>,
        _payment_method_type: Option<common_enums::PaymentMethodType>,
        _merchant_connector_account: &MerchantConnectorAccountType,
        _connector_name: &str,
        _authentication_id: &common_utils::id_type::AuthenticationId,
        _payment_method: common_enums::PaymentMethod,
        _amount: common_utils::types::MinorUnit,
        _currency: Option<common_enums::Currency>,
        _service_details: Option<payments::CtpServiceDetails>,
        _merchant_details: Option<&hyperswitch_domain_models::router_request_types::unified_authentication_service::MerchantDetails>,
        _billing_address: Option<&hyperswitch_domain_models::address::Address>,
        _acquirer_bin: Option<String>,
        _acquirer_merchant_id: Option<String>,
    ) -> RouterResult<hyperswitch_domain_models::types::UasPreAuthenticationRouterData> {
        Err(errors::ApiErrorResponse::NotImplemented {
            message: NotImplementedMessage::Reason("pre_authentication".to_string()),
        }
        .into())
    }

    #[allow(clippy::too_many_arguments)]
    fn get_authentication_request_data(
        _browser_details: Option<BrowserInformation>,
        _amount: Option<common_utils::types::MinorUnit>,
        _currency: Option<common_enums::Currency>,
        _message_category: MessageCategory,
        _device_channel: payments::DeviceChannel,
        _authentication: diesel_models::authentication::Authentication,
        _return_url: Option<String>,
        _sdk_information: Option<payments::SdkInformation>,
        _threeds_method_comp_ind: payments::ThreeDsCompletionIndicator,
        _email: Option<common_utils::pii::Email>,
        _webhook_url: String,
        _force_3ds_challenge: Option<bool>,
        _psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
    ) -> RouterResult<UasAuthenticationRequestData> {
        Err(errors::ApiErrorResponse::NotImplemented {
            message: NotImplementedMessage::Reason(
                "get_pre_authentication_request_data".to_string(),
            ),
        }
        .into())
    }

    #[allow(clippy::too_many_arguments)]
    async fn authentication(
        _state: &SessionState,
        _business_profile: &domain::Profile,
        _payment_method: &common_enums::PaymentMethod,
        _browser_details: Option<BrowserInformation>,
        _amount: Option<common_utils::types::MinorUnit>,
        _currency: Option<common_enums::Currency>,
        _message_category: MessageCategory,
        _device_channel: payments::DeviceChannel,
        _authentication_data: diesel_models::authentication::Authentication,
        _return_url: Option<String>,
        _sdk_information: Option<payments::SdkInformation>,
        _threeds_method_comp_ind: payments::ThreeDsCompletionIndicator,
        _email: Option<common_utils::pii::Email>,
        _webhook_url: String,
        _merchant_connector_account: &MerchantConnectorAccountType,
        _connector_name: &str,
        _payment_id: Option<common_utils::id_type::PaymentId>,
        _force_3ds_challenge: Option<bool>,
        _psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
    ) -> RouterResult<hyperswitch_domain_models::types::UasAuthenticationRouterData> {
        Err(errors::ApiErrorResponse::NotImplemented {
            message: NotImplementedMessage::Reason("authentication".to_string()),
        }
        .into())
    }

    fn get_post_authentication_request_data(
        _authentication: Option<diesel_models::authentication::Authentication>,
    ) -> RouterResult<UasPostAuthenticationRequestData> {
        Err(errors::ApiErrorResponse::NotImplemented {
            message: NotImplementedMessage::Reason("post_authentication".to_string()),
        }
        .into())
    }

    #[allow(clippy::too_many_arguments)]
    async fn post_authentication(
        _state: &SessionState,
        _business_profile: &domain::Profile,
        _payment_id: Option<&common_utils::id_type::PaymentId>,
        _merchant_connector_account: &MerchantConnectorAccountType,
        _connector_name: &str,
        _authentication_id: &common_utils::id_type::AuthenticationId,
        _payment_method: common_enums::PaymentMethod,
        _merchant_id: &common_utils::id_type::MerchantId,
        _authentication: Option<&diesel_models::authentication::Authentication>,
    ) -> RouterResult<hyperswitch_domain_models::types::UasPostAuthenticationRouterData> {
        Err(errors::ApiErrorResponse::NotImplemented {
            message: NotImplementedMessage::Reason("post_authentication".to_string()),
        }
        .into())
    }

    #[allow(clippy::too_many_arguments)]
    async fn confirmation(
        _state: &SessionState,
        _authentication_id: Option<&common_utils::id_type::AuthenticationId>,
        _currency: Option<common_enums::Currency>,
        _status: common_enums::AttemptStatus,
        _service_details: Option<payments::CtpServiceDetails>,
        _merchant_connector_account: &MerchantConnectorAccountType,
        _connector_name: &str,
        _payment_method: common_enums::PaymentMethod,
        _net_amount: common_utils::types::MinorUnit,
        _payment_id: Option<&common_utils::id_type::PaymentId>,
        _merchant_id: &common_utils::id_type::MerchantId,
    ) -> RouterResult<()> {
        Err(errors::ApiErrorResponse::NotImplemented {
            message: NotImplementedMessage::Reason("confirmation".to_string()),
        }
        .into())
    }
}


// File: crates/router/src/core/unified_authentication_service/utils.rs

use std::marker::PhantomData;

use common_enums::enums::PaymentMethod;
use common_utils::ext_traits::{AsyncExt, ValueExt};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    business_profile,
    errors::api_error_response::ApiErrorResponse,
    ext_traits::OptionExt,
    payment_address::PaymentAddress,
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_data_v2::UasFlowData,
    router_request_types::unified_authentication_service::{
        PostAuthenticationDetails, UasAuthenticationResponseData,
    },
};
use masking::ExposeInterface;

use super::types::{
    IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW,
    IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW,
};
use crate::{
    consts::DEFAULT_SESSION_EXPIRY,
    core::{
        errors::{
            utils::{ConnectorErrorExt, StorageErrorExt},
            RouterResult,
        },
        payment_methods, payments,
    },
    db::domain,
    services::{self, execute_connector_processing_step},
    types::{api, transformers::ForeignFrom},
    SessionState,
};

pub async fn do_auth_connector_call<F, Req, Res>(
    state: &SessionState,
    authentication_connector_name: String,
    router_data: RouterData<F, Req, Res>,
) -> RouterResult<RouterData<F, Req, Res>>
where
    Req: std::fmt::Debug + Clone + 'static,
    Res: std::fmt::Debug + Clone + 'static,
    F: std::fmt::Debug + Clone + 'static,
    dyn api::Connector + Sync: services::api::ConnectorIntegration<F, Req, Res>,
    dyn api::ConnectorV2 + Sync: services::api::ConnectorIntegrationV2<F, UasFlowData, Req, Res>,
{
    let connector_data =
        api::AuthenticationConnectorData::get_connector_by_name(&authentication_connector_name)?;
    let connector_integration: services::BoxedUnifiedAuthenticationServiceInterface<F, Req, Res> =
        connector_data.connector.get_connector_integration();
    let router_data = execute_connector_processing_step(
        state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await
    .to_payment_failed_response()?;
    Ok(router_data)
}

#[allow(clippy::too_many_arguments)]
pub fn construct_uas_router_data<F: Clone, Req, Res>(
    state: &SessionState,
    authentication_connector_name: String,
    payment_method: PaymentMethod,
    merchant_id: common_utils::id_type::MerchantId,
    address: Option<PaymentAddress>,
    request_data: Req,
    merchant_connector_account: &payments::helpers::MerchantConnectorAccountType,
    authentication_id: Option<common_utils::id_type::AuthenticationId>,
    payment_id: Option<common_utils::id_type::PaymentId>,
) -> RouterResult<RouterData<F, Req, Res>> {
    let auth_type: ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Error while parsing ConnectorAuthType")?;
    Ok(RouterData {
        flow: PhantomData,
        merchant_id,
        customer_id: None,
        connector_customer: None,
        connector: authentication_connector_name,
        payment_id: payment_id
            .map(|id| id.get_string_repr().to_owned())
            .unwrap_or_default(),
        tenant_id: state.tenant.tenant_id.clone(),
        attempt_id: IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW.to_owned(),
        status: common_enums::AttemptStatus::default(),
        payment_method,
        payment_method_type: None,
        connector_auth_type: auth_type,
        description: None,
        address: address.unwrap_or_default(),
        auth_type: common_enums::AuthenticationType::default(),
        connector_meta_data: merchant_connector_account.get_metadata().clone(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: None,
        minor_amount_captured: None,
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        payment_method_balance: None,
        connector_api_version: None,
        request: request_data,
        response: Err(ErrorResponse::default()),
        connector_request_reference_id:
            IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW.to_owned(),
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        dispute_id: None,
        refund_id: None,
        payment_method_status: None,
        connector_response: None,
        integrity_check: Ok(()),
        additional_merchant_data: None,
        header_payload: None,
        connector_mandate_request_reference_id: None,
        authentication_id,
        psd2_sca_exemption_type: None,
        raw_connector_response: None,
        is_payment_id_from_merchant: None,
        l2_l3_data: None,
        minor_amount_capturable: None,
        authorized_amount: None,
    })
}

#[allow(clippy::too_many_arguments)]
pub async fn external_authentication_update_trackers<F: Clone, Req>(
    state: &SessionState,
    router_data: RouterData<F, Req, UasAuthenticationResponseData>,
    authentication: diesel_models::authentication::Authentication,
    acquirer_details: Option<
        hyperswitch_domain_models::router_request_types::authentication::AcquirerDetails,
    >,
    merchant_key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
    billing_address: Option<common_utils::encryption::Encryption>,
    shipping_address: Option<common_utils::encryption::Encryption>,
    email: Option<common_utils::encryption::Encryption>,
    browser_info: Option<serde_json::Value>,
) -> RouterResult<diesel_models::authentication::Authentication> {
    let authentication_update = match router_data.response {
        Ok(response) => match response {
            UasAuthenticationResponseData::PreAuthentication {
                authentication_details,
            } => Ok(
                diesel_models::authentication::AuthenticationUpdate::PreAuthenticationUpdate {
                    threeds_server_transaction_id: authentication_details
                        .threeds_server_transaction_id
                        .ok_or(ApiErrorResponse::InternalServerError)
                        .attach_printable(
                            "missing threeds_server_transaction_id in PreAuthentication Details",
                        )?,
                    maximum_supported_3ds_version: authentication_details
                        .maximum_supported_3ds_version
                        .ok_or(ApiErrorResponse::InternalServerError)
                        .attach_printable(
                            "missing maximum_supported_3ds_version in PreAuthentication Details",
                        )?,
                    connector_authentication_id: authentication_details
                        .connector_authentication_id
                        .ok_or(ApiErrorResponse::InternalServerError)
                        .attach_printable(
                            "missing connector_authentication_id in PreAuthentication Details",
                        )?,
                    three_ds_method_data: authentication_details.three_ds_method_data,
                    three_ds_method_url: authentication_details.three_ds_method_url,
                    message_version: authentication_details
                        .message_version
                        .ok_or(ApiErrorResponse::InternalServerError)
                        .attach_printable("missing message_version in PreAuthentication Details")?,
                    connector_metadata: authentication_details.connector_metadata,
                    authentication_status: common_enums::AuthenticationStatus::Pending,
                    acquirer_bin: acquirer_details
                        .as_ref()
                        .map(|acquirer_details| acquirer_details.acquirer_bin.clone()),
                    acquirer_merchant_id: acquirer_details
                        .as_ref()
                        .map(|acquirer_details| acquirer_details.acquirer_merchant_id.clone()),
                    acquirer_country_code: acquirer_details
                        .and_then(|acquirer_details| acquirer_details.acquirer_country_code),
                    directory_server_id: authentication_details.directory_server_id,
                    browser_info: Box::new(browser_info),
                    email,
                    billing_address,
                    shipping_address,
                },
            ),
            UasAuthenticationResponseData::Authentication {
                authentication_details,
            } => {
                let authentication_status = common_enums::AuthenticationStatus::foreign_from(
                    authentication_details.trans_status.clone(),
                );
                authentication_details
                    .authentication_value
                    .async_map(|auth_val| {
                        payment_methods::vault::create_tokenize(
                            state,
                            auth_val.expose(),
                            None,
                            authentication
                                .authentication_id
                                .get_string_repr()
                                .to_string(),
                            merchant_key_store.key.get_inner(),
                        )
                    })
                    .await
                    .transpose()?;
                Ok(
                    diesel_models::authentication::AuthenticationUpdate::AuthenticationUpdate {
                        trans_status: authentication_details.trans_status,
                        acs_url: authentication_details.authn_flow_type.get_acs_url(),
                        challenge_request: authentication_details
                            .authn_flow_type
                            .get_challenge_request(),
                        challenge_request_key: authentication_details
                            .authn_flow_type
                            .get_challenge_request_key(),
                        acs_reference_number: authentication_details
                            .authn_flow_type
                            .get_acs_reference_number(),
                        acs_trans_id: authentication_details.authn_flow_type.get_acs_trans_id(),
                        acs_signed_content: authentication_details
                            .authn_flow_type
                            .get_acs_signed_content(),
                        authentication_type: authentication_details
                            .authn_flow_type
                            .get_decoupled_authentication_type(),
                        authentication_status,
                        connector_metadata: authentication_details.connector_metadata,
                        ds_trans_id: authentication_details.ds_trans_id,
                        eci: authentication_details.eci,
                        challenge_code: authentication_details.challenge_code,
                        challenge_cancel: authentication_details.challenge_cancel,
                        challenge_code_reason: authentication_details.challenge_code_reason,
                        message_extension: authentication_details.message_extension,
                    },
                )
            }
            UasAuthenticationResponseData::PostAuthentication {
                authentication_details,
            } => {
                let trans_status = authentication_details
                    .trans_status
                    .ok_or(ApiErrorResponse::InternalServerError)
                    .attach_printable("missing trans_status in PostAuthentication Details")?;

                authentication_details
                    .dynamic_data_details
                    .and_then(|details| details.dynamic_data_value)
                    .map(ExposeInterface::expose)
                    .async_map(|auth_val| {
                        payment_methods::vault::create_tokenize(
                            state,
                            auth_val,
                            None,
                            authentication
                                .authentication_id
                                .get_string_repr()
                                .to_string(),
                            merchant_key_store.key.get_inner(),
                        )
                    })
                    .await
                    .transpose()?;
                Ok(
                    diesel_models::authentication::AuthenticationUpdate::PostAuthenticationUpdate {
                        authentication_status: common_enums::AuthenticationStatus::foreign_from(
                            trans_status.clone(),
                        ),
                        trans_status,
                        eci: authentication_details.eci,
                        challenge_cancel: authentication_details.challenge_cancel,
                        challenge_code_reason: authentication_details.challenge_code_reason,
                    },
                )
            }

            UasAuthenticationResponseData::Confirmation { .. } => Err(
                ApiErrorResponse::InternalServerError,
            )
            .attach_printable("unexpected api confirmation in external authentication flow."),
        },
        Err(error) => Ok(
            diesel_models::authentication::AuthenticationUpdate::ErrorUpdate {
                connector_authentication_id: error.connector_transaction_id,
                authentication_status: common_enums::AuthenticationStatus::Failed,
                error_message: error
                    .reason
                    .map(|reason| format!("message: {}, reason: {}", error.message, reason))
                    .or(Some(error.message)),
                error_code: Some(error.code),
            },
        ),
    }?;

    state
        .store
        .update_authentication_by_merchant_id_authentication_id(
            authentication,
            authentication_update,
        )
        .await
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Error while updating authentication")
}

pub fn get_checkout_event_status_and_reason(
    attempt_status: common_enums::AttemptStatus,
) -> (Option<String>, Option<String>) {
    match attempt_status {
        common_enums::AttemptStatus::Charged | common_enums::AttemptStatus::Authorized => (
            Some("01".to_string()),
            Some("The payment was successful".to_string()),
        ),
        _ => (
            Some("03".to_string()),
            Some("The payment was not successful".to_string()),
        ),
    }
}

pub fn authenticate_authentication_client_secret_and_check_expiry(
    req_client_secret: &String,
    authentication: &diesel_models::authentication::Authentication,
) -> RouterResult<()> {
    let stored_client_secret = authentication
        .authentication_client_secret
        .clone()
        .get_required_value("authentication_client_secret")
        .change_context(ApiErrorResponse::MissingRequiredField {
            field_name: "client_secret",
        })
        .attach_printable("client secret not found in db")?;

    if req_client_secret != &stored_client_secret {
        Err(report!(ApiErrorResponse::ClientSecretInvalid))
    } else {
        let current_timestamp = common_utils::date_time::now();
        let session_expiry = authentication
            .created_at
            .saturating_add(time::Duration::seconds(DEFAULT_SESSION_EXPIRY));

        if current_timestamp > session_expiry {
            Err(report!(ApiErrorResponse::ClientSecretExpired))
        } else {
            Ok(())
        }
    }
}

#[cfg(feature = "v1")]
pub async fn get_auth_multi_token_from_external_vault<F, Req>(
    state: &SessionState,
    platform: &domain::Platform,
    business_profile: &domain::Profile,
    router_data: &RouterData<F, Req, UasAuthenticationResponseData>,
) -> RouterResult<Option<api_models::authentication::AuthenticationVaultTokenData>> {
    if let Ok(UasAuthenticationResponseData::PostAuthentication {
        authentication_details,
    }) = router_data.response.clone()
    {
        match business_profile.external_vault_details.clone() {
            business_profile::ExternalVaultDetails::ExternalVaultEnabled(
                external_vault_details,
            ) => {
                let external_vault_mca_id = external_vault_details.vault_connector_id.clone();

                let merchant_connector_account_details = state
                    .store
                    .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
                        platform.get_processor().get_account().get_id(),
                        &external_vault_mca_id,
                        platform.get_processor().get_key_store(),
                    )
                    .await
                    .to_not_found_response(ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: external_vault_mca_id.get_string_repr().to_string(),
                    })?;

                let vault_data = get_vault_details(authentication_details)?;

                // decide which fields to tokenize in vault
                let vault_custom_data = payment_methods::get_payment_method_custom_data(
                    vault_data,
                    external_vault_details.vault_token_selector,
                )?;

                let external_vault_response =
                    Box::pin(payment_methods::vault_payment_method_external_v1(
                        state,
                        &vault_custom_data,
                        platform.get_processor().get_account(),
                        merchant_connector_account_details,
                        Some(true),
                    ))
                    .await?;

                let vault_token_data = external_vault_response
                    .vault_id
                    .get_auth_vault_token_data()?;

                Ok(Some(vault_token_data))
            }
            business_profile::ExternalVaultDetails::Skip => {
                Err(ApiErrorResponse::InternalServerError)
                    .attach_printable("External Vault Details are missing")
            }
        }
    } else {
        Ok(None)
    }
}

#[cfg(feature = "v1")]
fn get_vault_details(
    auth_details: PostAuthenticationDetails,
) -> RouterResult<hyperswitch_domain_models::vault::PaymentMethodVaultingData> {
    // if raw card details are present, it have the highest priority
    // if network_token details are present, then it is returned if raw card details are not present
    // throw error if both are not present
    match (auth_details.raw_card_details, auth_details.token_details) {
        (Some(card_details), _) => Ok(
            hyperswitch_domain_models::vault::PaymentMethodVaultingData::Card(
                api_models::payment_methods::CardDetail {
                    card_number: card_details.pan.clone(),
                    card_exp_month: card_details.expiration_month,
                    card_exp_year: card_details.expiration_year,
                    card_cvc: card_details.card_security_code,
                    card_issuer: None,
                    card_network: None,
                    card_type: None,
                    card_issuing_country: None,
                    card_holder_name: None,
                    nick_name: None,
                },
            ),
        ),
        (None, Some(token_data)) => {
            let cryptogram = auth_details
                .dynamic_data_details
                .and_then(|dynamic_data| dynamic_data.dynamic_data_value);

            Ok(
                hyperswitch_domain_models::vault::PaymentMethodVaultingData::NetworkToken(
                    hyperswitch_domain_models::payment_method_data::NetworkTokenDetails {
                        network_token: token_data.payment_token.clone().to_network_token(),
                        network_token_exp_month: token_data.token_expiration_month.clone(),
                        network_token_exp_year: token_data.token_expiration_year.clone(),
                        cryptogram,
                        card_issuer: None,
                        card_network: None,
                        card_type: None,
                        card_issuing_country: None,
                        card_holder_name: None,
                        nick_name: None,
                    },
                ),
            )
        }
        (None, None) => Err(ApiErrorResponse::MissingRequiredField {
            field_name: "Either Card or Network Token details",
        }
        .into()),
    }
}

#[cfg(feature = "v1")]
pub fn get_authentication_payment_method_data<F, Req>(
    router_data: &RouterData<F, Req, UasAuthenticationResponseData>,
) -> Option<api_models::authentication::AuthenticationPaymentMethodDataResponse> {
    if let Ok(UasAuthenticationResponseData::PostAuthentication {
        authentication_details,
    }) = router_data.response.clone()
    {
        authentication_details.into()
    } else {
        None
    }
}