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

pub mod refunds_transformers;
pub mod refunds_validator;

use std::{collections::HashSet, marker::PhantomData, str::FromStr};

use api_models::enums::{Connector, DisputeStage, DisputeStatus};
#[cfg(feature = "payouts")]
use api_models::payouts::PayoutVendorAccountDetails;
use common_enums::{IntentStatus, RequestIncrementalAuthorization};
#[cfg(feature = "payouts")]
use common_utils::{crypto::Encryptable, pii::Email};
use common_utils::{
    errors::CustomResult,
    ext_traits::AsyncExt,
    types::{ConnectorTransactionIdTrait, MinorUnit},
};
use diesel_models::refund as diesel_refund;
use error_stack::{report, ResultExt};
#[cfg(feature = "v2")]
use hyperswitch_domain_models::types::VaultRouterData;
use hyperswitch_domain_models::{
    merchant_connector_account::MerchantConnectorAccount,
    payment_address::PaymentAddress,
    router_data::ErrorResponse,
    router_data_v2::flow_common_types::VaultConnectorFlowData,
    router_request_types,
    types::{OrderDetailsWithAmount, VaultRouterDataV2},
};
use hyperswitch_interfaces::api::ConnectorSpecifications;
#[cfg(feature = "v2")]
use masking::ExposeOptionInterface;
use masking::Secret;
#[cfg(feature = "payouts")]
use masking::{ExposeInterface, PeekInterface};
use maud::{html, PreEscaped};
use regex::Regex;
use router_env::{instrument, tracing};

use super::payments::helpers;
#[cfg(feature = "payouts")]
use super::payouts::{helpers as payout_helpers, PayoutData};
#[cfg(feature = "payouts")]
use crate::core::payments;
#[cfg(feature = "v2")]
use crate::core::payments::helpers as payment_helpers;
use crate::{
    configs::Settings,
    consts,
    core::{
        errors::{self, RouterResult, StorageErrorExt},
        payments::PaymentData,
    },
    db::StorageInterface,
    routes::SessionState,
    types::{
        self, api, domain,
        storage::{self, enums},
        PollConfig,
    },
    utils::{generate_id, OptionExt, ValueExt},
};

pub const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_DISPUTE_FLOW: &str =
    "irrelevant_connector_request_reference_id_in_dispute_flow";
const IRRELEVANT_ATTEMPT_ID_IN_DISPUTE_FLOW: &str = "irrelevant_attempt_id_in_dispute_flow";

#[cfg(all(feature = "payouts", feature = "v2"))]
#[instrument(skip_all)]
pub async fn construct_payout_router_data<'a, F>(
    _state: &SessionState,
    _connector_data: &api::ConnectorData,
    _platform: &domain::Platform,
    _payout_data: &mut PayoutData,
) -> RouterResult<types::PayoutsRouterData<F>> {
    todo!()
}

#[cfg(all(feature = "payouts", feature = "v1"))]
#[instrument(skip_all)]
pub async fn construct_payout_router_data<'a, F>(
    state: &SessionState,
    connector_data: &api::ConnectorData,
    platform: &domain::Platform,
    payout_data: &mut PayoutData,
) -> RouterResult<types::PayoutsRouterData<F>> {
    let merchant_connector_account = payout_data
        .merchant_connector_account
        .clone()
        .get_required_value("merchant_connector_account")?;
    let connector_name = connector_data.connector_name;
    let connector_auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let billing = payout_data.billing_address.to_owned();

    let billing_address = billing.map(api_models::payments::Address::from);

    let address = PaymentAddress::new(None, billing_address.map(From::from), None, None);

    let test_mode: Option<bool> = merchant_connector_account.is_test_mode_on();
    let payouts = &payout_data.payouts;
    let payout_attempt = &payout_data.payout_attempt;
    let customer_details = &payout_data.customer_details;
    let connector_label = format!(
        "{}_{}",
        payout_data.profile_id.get_string_repr(),
        connector_name
    );
    let connector_customer_id = customer_details
        .as_ref()
        .and_then(|c| c.connector_customer.as_ref())
        .and_then(|connector_customer_value| {
            connector_customer_value
                .clone()
                .expose()
                .get(connector_label)
                .cloned()
        })
        .and_then(|id| serde_json::from_value::<String>(id).ok());

    let vendor_details: Option<PayoutVendorAccountDetails> =
        match api_models::enums::PayoutConnectors::try_from(connector_name.to_owned()).map_err(
            |err| report!(errors::ApiErrorResponse::InternalServerError).attach_printable(err),
        )? {
            api_models::enums::PayoutConnectors::Stripe => {
                payout_data.payouts.metadata.to_owned().and_then(|meta| {
                    let val = meta
                        .peek()
                        .to_owned()
                        .parse_value("PayoutVendorAccountDetails")
                        .ok();
                    val
                })
            }
            _ => None,
        };

    let webhook_url = helpers::create_webhook_url(
        &state.base_url,
        &platform.get_processor().get_account().get_id().to_owned(),
        merchant_connector_account
            .get_mca_id()
            .get_required_value("merchant_connector_id")?
            .get_string_repr(),
    );

    let connector_transfer_method_id =
        payout_helpers::should_create_connector_transfer_method(&*payout_data, connector_data)?;

    let browser_info = payout_data.browser_info.to_owned();

    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().to_owned(),
        customer_id: customer_details.to_owned().map(|c| c.customer_id),
        tenant_id: state.tenant.tenant_id.clone(),
        connector_customer: get_payout_connector_customer_id(
            connector_data,
            connector_customer_id.clone(),
            &customer_details.to_owned().map(|c| c.customer_id),
            &payout_data.payment_method,
            &payout_data.payout_attempt,
        )?,
        connector: connector_name.to_string(),
        payment_id: common_utils::id_type::PaymentId::get_irrelevant_id("payout")
            .get_string_repr()
            .to_owned(),
        attempt_id: "".to_string(),
        status: enums::AttemptStatus::Failure,
        payment_method: enums::PaymentMethod::default(),
        payment_method_type: None,
        connector_auth_type,
        description: payout_data.payouts.description.clone(),
        address,
        auth_type: enums::AuthenticationType::default(),
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: None,
        minor_amount_captured: None,
        payment_method_status: None,
        request: types::PayoutsData {
            payout_id: payouts.payout_id.clone(),
            amount: payouts.amount.get_amount_as_i64(),
            minor_amount: payouts.amount,
            connector_payout_id: payout_attempt.connector_payout_id.clone(),
            destination_currency: payouts.destination_currency,
            source_currency: payouts.source_currency,
            entity_type: payouts.entity_type.to_owned(),
            payout_type: payouts.payout_type,
            vendor_details,
            priority: payouts.priority,
            customer_details: customer_details
                .to_owned()
                .map(|c| payments::CustomerDetails {
                    customer_id: Some(c.customer_id),
                    name: c.name.map(Encryptable::into_inner),
                    email: c.email.map(Email::from),
                    phone: c.phone.map(Encryptable::into_inner),
                    phone_country_code: c.phone_country_code,
                    tax_registration_id: c.tax_registration_id.map(Encryptable::into_inner),
                }),
            connector_transfer_method_id,
            webhook_url: Some(webhook_url),
            browser_info,
            payout_connector_metadata: payout_attempt.payout_connector_metadata.to_owned(),
        },
        response: Ok(types::PayoutsResponseData::default()),
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        connector_request_reference_id: payout_attempt.payout_attempt_id.clone(),
        payout_method_data: payout_data.payout_method_data.to_owned(),
        quote_id: None,
        test_mode,
        payment_method_balance: None,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: 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)
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn construct_refund_router_data<'a, F>(
    state: &'a SessionState,
    connector_enum: Connector,
    platform: &domain::Platform,
    payment_intent: &'a storage::PaymentIntent,
    payment_attempt: &storage::PaymentAttempt,
    refund: &'a diesel_refund::Refund,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
) -> RouterResult<types::RefundsRouterData<F>> {
    let auth_type = merchant_connector_account
        .get_connector_account_details()
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let status = payment_attempt.status;

    let payment_amount = payment_attempt.get_total_amount();
    let currency = payment_intent.get_currency();

    let payment_method_type = payment_attempt.payment_method_type;

    let webhook_url = match merchant_connector_account {
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(
            merchant_connector_account,
        ) => Some(helpers::create_webhook_url(
            &state.base_url.clone(),
            platform.get_processor().get_account().get_id(),
            merchant_connector_account.get_id().get_string_repr(),
        )),
        // TODO: Implement for connectors that require a webhook URL to be included in the request payload.
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => None,
    };

    let supported_connector = &state
        .conf
        .multiple_api_version_supported_connectors
        .supported_connectors;

    let connector_api_version = if supported_connector.contains(&connector_enum) {
        state
            .store
            .find_config_by_key(&format!("connector_api_version_{connector_enum}"))
            .await
            .map(|value| value.config)
            .ok()
    } else {
        None
    };

    let browser_info = payment_attempt
        .browser_info
        .clone()
        .map(types::BrowserInformation::from);

    let connector_refund_id = refund.get_optional_connector_refund_id().cloned();
    let capture_method = payment_intent.capture_method;

    let customer_id = payment_intent
        .get_optional_customer_id()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get optional customer id")?;

    let braintree_metadata = payment_intent
        .connector_metadata
        .as_ref()
        .and_then(|cm| cm.braintree.clone());

    let merchant_account_id = braintree_metadata
        .as_ref()
        .and_then(|braintree| braintree.merchant_account_id.clone());

    let merchant_config_currency =
        braintree_metadata.and_then(|braintree| braintree.merchant_config_currency);

    let connector_wallets_details = match merchant_connector_account {
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorAccount(
            merchant_connector_account,
        ) => merchant_connector_account.get_connector_wallets_details(),
        domain::MerchantConnectorAccountTypeDetails::MerchantConnectorDetails(_) => None,
    };

    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().clone(),
        customer_id,
        tenant_id: state.tenant.tenant_id.clone(),
        connector: connector_enum.to_string(),
        payment_id: payment_attempt.payment_id.get_string_repr().to_owned(),
        attempt_id: payment_attempt.id.get_string_repr().to_string().clone(),
        status,
        payment_method: payment_method_type,
        payment_method_type: Some(payment_attempt.payment_method_subtype),
        connector_auth_type: auth_type,
        description: None,
        // Does refund need shipping/billing address ?
        address: PaymentAddress::default(),
        auth_type: payment_attempt.authentication_type,
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details,
        amount_captured: payment_intent
            .amount_captured
            .map(|amt| amt.get_amount_as_i64()),
        payment_method_status: None,
        minor_amount_captured: payment_intent.amount_captured,
        request: types::RefundsData {
            refund_id: refund.id.get_string_repr().to_string(),
            connector_transaction_id: refund.get_connector_transaction_id().clone(),
            refund_amount: refund.refund_amount.get_amount_as_i64(),
            minor_refund_amount: refund.refund_amount,
            currency,
            payment_amount: payment_amount.get_amount_as_i64(),
            minor_payment_amount: payment_amount,
            webhook_url,
            connector_metadata: payment_attempt.connector_metadata.clone().expose_option(),
            reason: refund.refund_reason.clone(),
            connector_refund_id: connector_refund_id.clone(),
            browser_info,
            split_refunds: None,
            integrity_object: None,
            refund_status: refund.refund_status,
            merchant_account_id,
            merchant_config_currency,
            refund_connector_metadata: refund.metadata.clone(),
            capture_method: Some(capture_method),
            additional_payment_method_data: None,
        },

        response: Ok(types::RefundsResponseData {
            connector_refund_id: connector_refund_id.unwrap_or_default(),
            refund_status: refund.refund_status,
        }),
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        connector_customer: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        connector_request_reference_id: refund
            .merchant_reference_id
            .get_string_repr()
            .to_string()
            .clone(),
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode: None,
        payment_method_balance: None,
        connector_api_version,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: Some(refund.id.get_string_repr().to_string().clone()),
        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)
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn construct_refund_router_data<'a, F>(
    state: &'a SessionState,
    connector_id: &str,
    platform: &domain::Platform,
    money: (MinorUnit, enums::Currency),
    payment_intent: &'a storage::PaymentIntent,
    payment_attempt: &storage::PaymentAttempt,
    refund: &'a diesel_refund::Refund,
    split_refunds: Option<router_request_types::SplitRefundsRequest>,
    merchant_connector_account: &helpers::MerchantConnectorAccountType,
) -> RouterResult<types::RefundsRouterData<F>> {
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let status = payment_attempt.status;

    let (payment_amount, currency) = money;

    let payment_method = payment_attempt
        .payment_method
        .get_required_value("payment_method")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let merchant_connector_account_id_or_connector_name = payment_attempt
        .merchant_connector_id
        .as_ref()
        .map(|mca_id| mca_id.get_string_repr())
        .unwrap_or(connector_id);

    let webhook_url = Some(helpers::create_webhook_url(
        &state.base_url.clone(),
        platform.get_processor().get_account().get_id(),
        merchant_connector_account_id_or_connector_name,
    ));
    let test_mode: Option<bool> = merchant_connector_account.is_test_mode_on();

    let supported_connector = &state
        .conf
        .multiple_api_version_supported_connectors
        .supported_connectors;
    let connector_enum = Connector::from_str(connector_id)
        .change_context(errors::ConnectorError::InvalidConnectorName)
        .change_context(errors::ApiErrorResponse::InvalidDataValue {
            field_name: "connector",
        })
        .attach_printable_lazy(|| format!("unable to parse connector name {connector_id:?}"))?;

    let connector_api_version = if supported_connector.contains(&connector_enum) {
        state
            .store
            .find_config_by_key(&format!("connector_api_version_{connector_id}"))
            .await
            .map(|value| value.config)
            .ok()
    } else {
        None
    };

    let browser_info: Option<types::BrowserInformation> = payment_attempt
        .browser_info
        .clone()
        .map(|b| b.parse_value("BrowserInformation"))
        .transpose()
        .change_context(errors::ApiErrorResponse::InvalidDataValue {
            field_name: "browser_info",
        })?;

    let connector_refund_id = refund.get_optional_connector_refund_id().cloned();
    let capture_method = payment_attempt.capture_method;

    let braintree_metadata = payment_intent
        .connector_metadata
        .clone()
        .map(|cm| {
            cm.parse_value::<api_models::payments::ConnectorMetadata>("ConnectorMetadata")
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed parsing ConnectorMetadata")
        })
        .transpose()?
        .and_then(|cm| cm.braintree);

    let merchant_account_id = braintree_metadata
        .as_ref()
        .and_then(|braintree| braintree.merchant_account_id.clone());
    let merchant_config_currency =
        braintree_metadata.and_then(|braintree| braintree.merchant_config_currency);
    let additional_payment_method_data: Option<api_models::payments::AdditionalPaymentData> =
        payment_attempt
            .payment_method_data
            .clone()
            .and_then(|value| match serde_json::from_value(value) {
                Ok(data) => Some(data),
                Err(e) => {
                    router_env::logger::error!("Failed to deserialize payment_method_data: {}", e);
                    None
                }
            });

    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().clone(),
        customer_id: payment_intent.customer_id.to_owned(),
        tenant_id: state.tenant.tenant_id.clone(),
        connector: connector_id.to_string(),
        payment_id: payment_attempt.payment_id.get_string_repr().to_owned(),
        attempt_id: payment_attempt.attempt_id.clone(),
        status,
        payment_method,
        payment_method_type: payment_attempt.payment_method_type,
        connector_auth_type: auth_type,
        description: None,
        // Does refund need shipping/billing address ?
        address: PaymentAddress::default(),
        auth_type: payment_attempt.authentication_type.unwrap_or_default(),
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: payment_intent
            .amount_captured
            .map(|amt| amt.get_amount_as_i64()),
        payment_method_status: None,
        minor_amount_captured: payment_intent.amount_captured,
        request: types::RefundsData {
            refund_id: refund.refund_id.clone(),
            connector_transaction_id: refund.get_connector_transaction_id().clone(),
            refund_amount: refund.refund_amount.get_amount_as_i64(),
            minor_refund_amount: refund.refund_amount,
            currency,
            payment_amount: payment_amount.get_amount_as_i64(),
            minor_payment_amount: payment_amount,
            webhook_url,
            connector_metadata: payment_attempt.connector_metadata.clone(),
            refund_connector_metadata: refund.metadata.clone(),
            reason: refund.refund_reason.clone(),
            connector_refund_id: connector_refund_id.clone(),
            browser_info,
            split_refunds,
            integrity_object: None,
            refund_status: refund.refund_status,
            merchant_account_id,
            merchant_config_currency,
            capture_method,
            additional_payment_method_data,
        },

        response: Ok(types::RefundsResponseData {
            connector_refund_id: connector_refund_id.unwrap_or_default(),
            refund_status: refund.refund_status,
        }),
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        connector_customer: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        connector_request_reference_id: refund.refund_id.clone(),
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        payment_method_balance: None,
        connector_api_version,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: Some(refund.refund_id.clone()),
        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)
}

pub fn get_or_generate_id(
    key: &str,
    provided_id: &Option<String>,
    prefix: &str,
) -> Result<String, errors::ApiErrorResponse> {
    let validate_id = |id| validate_id(id, key);
    provided_id
        .clone()
        .map_or(Ok(generate_id(consts::ID_LENGTH, prefix)), validate_id)
}

fn invalid_id_format_error(key: &str) -> errors::ApiErrorResponse {
    errors::ApiErrorResponse::InvalidDataFormat {
        field_name: key.to_string(),
        expected_format: format!(
            "length should be less than {} characters",
            consts::MAX_ID_LENGTH
        ),
    }
}

pub fn validate_id(id: String, key: &str) -> Result<String, errors::ApiErrorResponse> {
    if id.len() > consts::MAX_ID_LENGTH {
        Err(invalid_id_format_error(key))
    } else {
        Ok(id)
    }
}

#[cfg(feature = "v1")]
pub fn get_split_refunds(
    split_refund_input: refunds_transformers::SplitRefundInput,
) -> RouterResult<Option<router_request_types::SplitRefundsRequest>> {
    match split_refund_input.split_payment_request.as_ref() {
        Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment(stripe_payment)) => {
            let (charge_id_option, charge_type_option) = match (
                &split_refund_input.payment_charges,
                &split_refund_input.split_payment_request,
            ) {
                (
                    Some(common_types::payments::ConnectorChargeResponseData::StripeSplitPayment(
                        stripe_split_payment_response,
                    )),
                    _,
                ) => (
                    stripe_split_payment_response.charge_id.clone(),
                    Some(stripe_split_payment_response.charge_type.clone()),
                ),
                (
                    _,
                    Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment(
                        stripe_split_payment_request,
                    )),
                ) => (
                    split_refund_input.charge_id,
                    Some(stripe_split_payment_request.charge_type.clone()),
                ),
                (_, _) => (None, None),
            };

            if let Some(charge_id) = charge_id_option {
                let options = refunds_validator::validate_stripe_charge_refund(
                    charge_type_option,
                    &split_refund_input.refund_request,
                )?;

                Ok(Some(
                    router_request_types::SplitRefundsRequest::StripeSplitRefund(
                        router_request_types::StripeSplitRefund {
                            charge_id,
                            charge_type: stripe_payment.charge_type.clone(),
                            transfer_account_id: stripe_payment.transfer_account_id.clone(),
                            options,
                        },
                    ),
                ))
            } else {
                Ok(None)
            }
        }
        Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) => {
            match &split_refund_input.payment_charges {
                Some(common_types::payments::ConnectorChargeResponseData::AdyenSplitPayment(
                    adyen_split_payment_response,
                )) => {
                    if let Some(common_types::refunds::SplitRefund::AdyenSplitRefund(
                        split_refund_request,
                    )) = split_refund_input.refund_request.clone()
                    {
                        refunds_validator::validate_adyen_charge_refund(
                            adyen_split_payment_response,
                            &split_refund_request,
                        )?;

                        Ok(Some(
                            router_request_types::SplitRefundsRequest::AdyenSplitRefund(
                                split_refund_request,
                            ),
                        ))
                    } else {
                        Ok(None)
                    }
                }
                _ => Ok(None),
            }
        }
        Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment(_)) => {
            match (
                &split_refund_input.payment_charges,
                &split_refund_input.refund_request,
            ) {
                (
                    Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment(
                        xendit_split_payment_response,
                    )),
                    Some(common_types::refunds::SplitRefund::XenditSplitRefund(
                        split_refund_request,
                    )),
                ) => {
                    let user_id = refunds_validator::validate_xendit_charge_refund(
                        xendit_split_payment_response,
                        split_refund_request,
                    )?;

                    Ok(user_id.map(|for_user_id| {
                        router_request_types::SplitRefundsRequest::XenditSplitRefund(
                            common_types::domain::XenditSplitSubMerchantData { for_user_id },
                        )
                    }))
                }
                (
                    Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment(
                        xendit_split_payment_response,
                    )),
                    None,
                ) => {
                    let option_for_user_id = match xendit_split_payment_response {
                        common_types::payments::XenditChargeResponseData::MultipleSplits(
                            common_types::payments::XenditMultipleSplitResponse {
                                for_user_id, ..
                            },
                        ) => for_user_id.clone(),
                        common_types::payments::XenditChargeResponseData::SingleSplit(
                            common_types::domain::XenditSplitSubMerchantData { for_user_id },
                        ) => Some(for_user_id.clone()),
                    };

                    if option_for_user_id.is_some() {
                        Err(errors::ApiErrorResponse::MissingRequiredField {
                            field_name: "split_refunds.xendit_split_refund.for_user_id",
                        })?
                    } else {
                        Ok(None)
                    }
                }
                _ => Ok(None),
            }
        }
        _ => Ok(None),
    }
}
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn validate_id_length_constraint() {
        let payment_id =
            "abcdefghijlkmnopqrstuvwzyzabcdefghijknlmnopsjkdnfjsknfkjsdnfspoig".to_string(); //length = 65

        let result = validate_id(payment_id, "payment_id");
        assert!(result.is_err());
    }

    #[test]
    fn validate_id_proper_response() {
        let payment_id = "abcdefghijlkmnopqrstjhbjhjhkhbhgcxdfxvmhb".to_string();

        let result = validate_id(payment_id.clone(), "payment_id");
        assert!(result.is_ok());
        let result = result.unwrap_or_default();
        assert_eq!(result, payment_id);
    }

    #[test]
    fn test_generate_id() {
        let generated_id = generate_id(consts::ID_LENGTH, "ref");
        assert_eq!(generated_id.len(), consts::ID_LENGTH + 4)
    }

    #[test]
    fn test_filter_objects_based_on_profile_id_list() {
        #[derive(PartialEq, Debug, Clone)]
        struct Object {
            profile_id: Option<common_utils::id_type::ProfileId>,
        }

        impl Object {
            pub fn new(profile_id: &'static str) -> Self {
                Self {
                    profile_id: Some(
                        common_utils::id_type::ProfileId::try_from(std::borrow::Cow::from(
                            profile_id,
                        ))
                        .expect("invalid profile ID"),
                    ),
                }
            }
        }

        impl GetProfileId for Object {
            fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
                self.profile_id.as_ref()
            }
        }

        fn new_profile_id(profile_id: &'static str) -> common_utils::id_type::ProfileId {
            common_utils::id_type::ProfileId::try_from(std::borrow::Cow::from(profile_id))
                .expect("invalid profile ID")
        }

        // non empty object_list and profile_id_list
        let object_list = vec![
            Object::new("p1"),
            Object::new("p2"),
            Object::new("p2"),
            Object::new("p4"),
            Object::new("p5"),
        ];
        let profile_id_list = vec![
            new_profile_id("p1"),
            new_profile_id("p2"),
            new_profile_id("p3"),
        ];
        let filtered_list =
            filter_objects_based_on_profile_id_list(Some(profile_id_list), object_list.clone());
        let expected_result = vec![Object::new("p1"), Object::new("p2"), Object::new("p2")];
        assert_eq!(filtered_list, expected_result);

        // non empty object_list and empty profile_id_list
        let empty_profile_id_list = vec![];
        let filtered_list = filter_objects_based_on_profile_id_list(
            Some(empty_profile_id_list),
            object_list.clone(),
        );
        let expected_result = vec![];
        assert_eq!(filtered_list, expected_result);

        // non empty object_list and None profile_id_list
        let profile_id_list_as_none = None;
        let filtered_list =
            filter_objects_based_on_profile_id_list(profile_id_list_as_none, object_list);
        let expected_result = vec![
            Object::new("p1"),
            Object::new("p2"),
            Object::new("p2"),
            Object::new("p4"),
            Object::new("p5"),
        ];
        assert_eq!(filtered_list, expected_result);
    }
}

// Dispute Stage can move linearly from PreDispute -> Dispute -> PreArbitration -> Arbitration -> DisputeReversal
pub fn validate_dispute_stage(
    prev_dispute_stage: DisputeStage,
    dispute_stage: DisputeStage,
) -> bool {
    match prev_dispute_stage {
        DisputeStage::PreDispute => true,
        DisputeStage::Dispute => !matches!(dispute_stage, DisputeStage::PreDispute),
        DisputeStage::PreArbitration => matches!(
            dispute_stage,
            DisputeStage::PreArbitration
                | DisputeStage::Arbitration
                | DisputeStage::DisputeReversal
        ),
        DisputeStage::Arbitration => matches!(
            dispute_stage,
            DisputeStage::Arbitration | DisputeStage::DisputeReversal
        ),
        DisputeStage::DisputeReversal => matches!(dispute_stage, DisputeStage::DisputeReversal),
    }
}

//Dispute status can go from Opened -> (Expired | Accepted | Cancelled | Challenged -> (Won | Lost))
pub fn validate_dispute_status(
    prev_dispute_status: DisputeStatus,
    dispute_status: DisputeStatus,
) -> bool {
    match prev_dispute_status {
        DisputeStatus::DisputeOpened => true,
        DisputeStatus::DisputeExpired => {
            matches!(dispute_status, DisputeStatus::DisputeExpired)
        }
        DisputeStatus::DisputeAccepted => {
            matches!(dispute_status, DisputeStatus::DisputeAccepted)
        }
        DisputeStatus::DisputeCancelled => {
            matches!(dispute_status, DisputeStatus::DisputeCancelled)
        }
        DisputeStatus::DisputeChallenged => matches!(
            dispute_status,
            DisputeStatus::DisputeChallenged
                | DisputeStatus::DisputeWon
                | DisputeStatus::DisputeLost
                | DisputeStatus::DisputeAccepted
                | DisputeStatus::DisputeCancelled
                | DisputeStatus::DisputeExpired
        ),
        DisputeStatus::DisputeWon => matches!(dispute_status, DisputeStatus::DisputeWon),
        DisputeStatus::DisputeLost => matches!(dispute_status, DisputeStatus::DisputeLost),
    }
}

pub fn validate_dispute_stage_and_dispute_status(
    prev_dispute_stage: DisputeStage,
    prev_dispute_status: DisputeStatus,
    dispute_stage: DisputeStage,
    dispute_status: DisputeStatus,
) -> CustomResult<(), errors::WebhooksFlowError> {
    let dispute_stage_validation = validate_dispute_stage(prev_dispute_stage, dispute_stage);
    let dispute_status_validation = if dispute_stage == prev_dispute_stage {
        validate_dispute_status(prev_dispute_status, dispute_status)
    } else {
        true
    };
    common_utils::fp_utils::when(
        !(dispute_stage_validation && dispute_status_validation),
        || {
            super::metrics::INCOMING_DISPUTE_WEBHOOK_VALIDATION_FAILURE_METRIC.add(1, &[]);
            Err(errors::WebhooksFlowError::DisputeWebhookValidationFailed)?
        },
    )
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn construct_accept_dispute_router_data<'a>(
    state: &'a SessionState,
    payment_intent: &'a storage::PaymentIntent,
    payment_attempt: &storage::PaymentAttempt,
    platform: &domain::Platform,
    dispute: &storage::Dispute,
) -> RouterResult<types::AcceptDisputeRouterData> {
    let profile_id = payment_intent
        .profile_id
        .as_ref()
        .get_required_value("profile_id")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("profile_id is not set in payment_intent")?
        .clone();

    let merchant_connector_account = helpers::get_merchant_connector_account(
        state,
        platform.get_processor().get_account().get_id(),
        None,
        platform.get_processor().get_key_store(),
        &profile_id,
        &dispute.connector,
        payment_attempt.merchant_connector_id.as_ref(),
    )
    .await?;

    let test_mode: Option<bool> = merchant_connector_account.is_test_mode_on();
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;
    let payment_method = payment_attempt
        .payment_method
        .get_required_value("payment_method")?;

    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().clone(),
        connector: dispute.connector.to_string(),
        tenant_id: state.tenant.tenant_id.clone(),
        payment_id: payment_attempt.payment_id.get_string_repr().to_owned(),
        attempt_id: payment_attempt.attempt_id.clone(),
        status: payment_attempt.status,
        payment_method,
        payment_method_type: payment_attempt.payment_method_type,
        connector_auth_type: auth_type,
        description: None,
        address: PaymentAddress::default(),
        auth_type: payment_attempt.authentication_type.unwrap_or_default(),
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: payment_intent
            .amount_captured
            .map(|amt| amt.get_amount_as_i64()),
        minor_amount_captured: payment_intent.amount_captured,
        payment_method_status: None,
        request: types::AcceptDisputeRequestData {
            dispute_id: dispute.dispute_id.clone(),
            connector_dispute_id: dispute.connector_dispute_id.clone(),
            dispute_status: dispute.dispute_status,
        },
        response: Err(ErrorResponse::default()),
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        connector_customer: None,
        customer_id: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        connector_request_reference_id: get_connector_request_reference_id(
            &state.conf,
            platform.get_processor().get_account().get_id(),
            payment_intent,
            payment_attempt,
            &dispute.connector,
        )?,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        payment_method_balance: None,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        dispute_id: Some(dispute.dispute_id.clone()),
        refund_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)
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn construct_submit_evidence_router_data<'a>(
    state: &'a SessionState,
    payment_intent: &'a storage::PaymentIntent,
    payment_attempt: &storage::PaymentAttempt,
    platform: &domain::Platform,
    dispute: &storage::Dispute,
    submit_evidence_request_data: types::SubmitEvidenceRequestData,
) -> RouterResult<types::SubmitEvidenceRouterData> {
    let connector_id = &dispute.connector;
    let profile_id = payment_intent
        .profile_id
        .as_ref()
        .get_required_value("profile_id")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("profile_id is not set in payment_intent")?
        .clone();

    let merchant_connector_account = helpers::get_merchant_connector_account(
        state,
        platform.get_processor().get_account().get_id(),
        None,
        platform.get_processor().get_key_store(),
        &profile_id,
        connector_id,
        payment_attempt.merchant_connector_id.as_ref(),
    )
    .await?;

    let test_mode: Option<bool> = merchant_connector_account.is_test_mode_on();
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;
    let payment_method = payment_attempt
        .payment_method
        .get_required_value("payment_method_type")?;

    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().clone(),
        connector: connector_id.to_string(),
        payment_id: payment_attempt.payment_id.get_string_repr().to_owned(),
        tenant_id: state.tenant.tenant_id.clone(),
        attempt_id: payment_attempt.attempt_id.clone(),
        status: payment_attempt.status,
        payment_method,
        payment_method_type: payment_attempt.payment_method_type,
        connector_auth_type: auth_type,
        description: None,
        address: PaymentAddress::default(),
        auth_type: payment_attempt.authentication_type.unwrap_or_default(),
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: payment_intent
            .amount_captured
            .map(|amt| amt.get_amount_as_i64()),
        minor_amount_captured: payment_intent.amount_captured,
        request: submit_evidence_request_data,
        response: Err(ErrorResponse::default()),
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        connector_customer: None,
        customer_id: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        payment_method_balance: None,
        payment_method_status: None,
        connector_request_reference_id: get_connector_request_reference_id(
            &state.conf,
            platform.get_processor().get_account().get_id(),
            payment_intent,
            payment_attempt,
            connector_id,
        )?,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: None,
        dispute_id: Some(dispute.dispute_id.clone()),
        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)
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn construct_upload_file_router_data<'a>(
    state: &'a SessionState,
    payment_intent: &'a storage::PaymentIntent,
    payment_attempt: &storage::PaymentAttempt,
    platform: &domain::Platform,
    create_file_request: &api::CreateFileRequest,
    dispute_data: storage::Dispute,
    connector_id: &str,
    file_key: String,
) -> RouterResult<types::UploadFileRouterData> {
    let profile_id = payment_intent
        .profile_id
        .as_ref()
        .get_required_value("profile_id")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("profile_id is not set in payment_intent")?
        .clone();

    let merchant_connector_account = helpers::get_merchant_connector_account(
        state,
        platform.get_processor().get_account().get_id(),
        None,
        platform.get_processor().get_key_store(),
        &profile_id,
        connector_id,
        payment_attempt.merchant_connector_id.as_ref(),
    )
    .await?;

    let test_mode: Option<bool> = merchant_connector_account.is_test_mode_on();
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;
    let payment_method = payment_attempt
        .payment_method
        .get_required_value("payment_method_type")?;

    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().clone(),
        connector: connector_id.to_string(),
        payment_id: payment_attempt.payment_id.get_string_repr().to_owned(),
        tenant_id: state.tenant.tenant_id.clone(),
        attempt_id: payment_attempt.attempt_id.clone(),
        status: payment_attempt.status,
        payment_method,
        payment_method_type: payment_attempt.payment_method_type,
        connector_auth_type: auth_type,
        description: None,
        address: PaymentAddress::default(),
        auth_type: payment_attempt.authentication_type.unwrap_or_default(),
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: payment_intent
            .amount_captured
            .map(|amt| amt.get_amount_as_i64()),
        minor_amount_captured: payment_intent.amount_captured,
        payment_method_status: None,
        request: types::UploadFileRequestData {
            file_key,
            file: create_file_request.file.clone(),
            file_type: create_file_request.file_type.clone(),
            file_size: create_file_request.file_size,
            dispute_id: dispute_data.dispute_id.clone(),
            connector_dispute_id: dispute_data.connector_dispute_id.clone(),
        },
        response: Err(ErrorResponse::default()),
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        connector_customer: None,
        customer_id: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        payment_method_balance: None,
        connector_request_reference_id: get_connector_request_reference_id(
            &state.conf,
            platform.get_processor().get_account().get_id(),
            payment_intent,
            payment_attempt,
            connector_id,
        )?,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: 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)
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn construct_dispute_list_router_data<'a>(
    state: &'a SessionState,
    merchant_connector_account: MerchantConnectorAccount,
    req: types::FetchDisputesRequestData,
) -> RouterResult<types::FetchDisputesRouterData> {
    let merchant_id = merchant_connector_account.merchant_id.clone();
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
            id: "ConnectorAuthType".to_string(),
        })?;

    Ok(types::RouterData {
        flow: PhantomData,
        merchant_id,
        customer_id: None,
        connector_customer: None,
        connector: merchant_connector_account.connector_name.clone(),
        payment_id: consts::IRRELEVANT_PAYMENT_INTENT_ID.to_owned(),
        tenant_id: state.tenant.tenant_id.clone(),
        attempt_id: consts::IRRELEVANT_PAYMENT_ATTEMPT_ID.to_owned(),
        status: common_enums::AttemptStatus::default(),
        payment_method: common_enums::PaymentMethod::default(),
        payment_method_type: None,
        connector_auth_type: auth_type,
        description: None,
        address: PaymentAddress::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: req,
        response: Err(ErrorResponse::default()),
        //TODO
        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: 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,
    })
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn construct_dispute_sync_router_data<'a>(
    state: &'a SessionState,
    payment_intent: &'a storage::PaymentIntent,
    payment_attempt: &storage::PaymentAttempt,
    platform: &domain::Platform,
    dispute: &storage::Dispute,
) -> RouterResult<types::DisputeSyncRouterData> {
    let _db = &*state.store;
    let connector_id = &dispute.connector;
    let profile_id = payment_intent
        .profile_id
        .as_ref()
        .get_required_value("profile_id")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("profile_id is not set in payment_intent")?
        .clone();

    let merchant_connector_account = helpers::get_merchant_connector_account(
        state,
        platform.get_processor().get_account().get_id(),
        None,
        platform.get_processor().get_key_store(),
        &profile_id,
        connector_id,
        payment_attempt.merchant_connector_id.as_ref(),
    )
    .await?;

    let test_mode: Option<bool> = merchant_connector_account.is_test_mode_on();
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;
    let payment_method = payment_attempt
        .payment_method
        .get_required_value("payment_method")?;

    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().clone(),
        connector: connector_id.to_string(),
        payment_id: payment_attempt.payment_id.get_string_repr().to_owned(),
        tenant_id: state.tenant.tenant_id.clone(),
        attempt_id: payment_attempt.attempt_id.clone(),
        status: payment_attempt.status,
        payment_method,
        payment_method_type: payment_attempt.payment_method_type,
        connector_auth_type: auth_type,
        description: None,
        address: PaymentAddress::default(),
        auth_type: payment_attempt.authentication_type.unwrap_or_default(),
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: payment_intent
            .amount_captured
            .map(|amt| amt.get_amount_as_i64()),
        minor_amount_captured: payment_intent.amount_captured,
        payment_method_status: None,
        request: types::DisputeSyncData {
            dispute_id: dispute.dispute_id.clone(),
            connector_dispute_id: dispute.connector_dispute_id.clone(),
        },
        response: Err(ErrorResponse::get_not_implemented()),
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        customer_id: None,
        connector_customer: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        payment_method_balance: None,
        connector_request_reference_id: get_connector_request_reference_id(
            &state.conf,
            platform.get_processor().get_account().get_id(),
            payment_intent,
            payment_attempt,
            connector_id,
        )?,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: None,
        dispute_id: Some(dispute.dispute_id.clone()),
        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)
}

#[cfg(feature = "v2")]
pub async fn construct_payments_dynamic_tax_calculation_router_data<F: Clone>(
    state: &SessionState,
    platform: &domain::Platform,
    payment_data: &mut PaymentData<F>,
    merchant_connector_account: &MerchantConnectorAccount,
) -> RouterResult<types::PaymentsTaxCalculationRouterData> {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn construct_payments_dynamic_tax_calculation_router_data<F: Clone>(
    state: &SessionState,
    platform: &domain::Platform,
    payment_data: &mut PaymentData<F>,
    merchant_connector_account: &MerchantConnectorAccount,
) -> RouterResult<types::PaymentsTaxCalculationRouterData> {
    let payment_intent = &payment_data.payment_intent.clone();
    let payment_attempt = &payment_data.payment_attempt.clone();

    #[cfg(feature = "v1")]
    let test_mode: Option<bool> = merchant_connector_account.test_mode;

    #[cfg(feature = "v2")]
    let test_mode = None;

    let connector_auth_type: types::ConnectorAuthType = merchant_connector_account
        .connector_account_details
        .clone()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while parsing value for ConnectorAuthType")?;

    let shipping_address = payment_data
        .tax_data
        .clone()
        .map(|tax_data| tax_data.shipping_details)
        .clone()
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Missing shipping_details")?;

    let order_details: Option<Vec<OrderDetailsWithAmount>> = payment_intent
        .order_details
        .clone()
        .map(|order_details| {
            order_details
                .iter()
                .map(|data| {
                    data.to_owned()
                        .parse_value("OrderDetailsWithAmount")
                        .change_context(errors::ApiErrorResponse::InvalidDataValue {
                            field_name: "OrderDetailsWithAmount",
                        })
                        .attach_printable("Unable to parse OrderDetailsWithAmount")
                })
                .collect::<Result<Vec<_>, _>>()
        })
        .transpose()?;

    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().to_owned(),
        customer_id: None,
        connector_customer: None,
        connector: merchant_connector_account.connector_name.clone(),
        payment_id: payment_attempt.payment_id.get_string_repr().to_owned(),
        attempt_id: payment_attempt.attempt_id.clone(),
        tenant_id: state.tenant.tenant_id.clone(),
        status: payment_attempt.status,
        payment_method: diesel_models::enums::PaymentMethod::default(),
        payment_method_type: payment_attempt.payment_method_type,
        connector_auth_type,
        description: None,
        address: payment_data.address.clone(),
        auth_type: payment_attempt.authentication_type.unwrap_or_default(),
        connector_meta_data: None,
        connector_wallets_details: None,
        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: types::PaymentsTaxCalculationData {
            amount: payment_intent.amount,
            shipping_cost: payment_intent.shipping_cost,
            order_details,
            currency: payment_data.currency,
            shipping_address,
        },
        response: Err(ErrorResponse::default()),
        connector_request_reference_id: get_connector_request_reference_id(
            &state.conf,
            platform.get_processor().get_account().get_id(),
            payment_intent,
            payment_attempt,
            &merchant_connector_account.connector_name,
        )?,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: None,
        dispute_id: None,
        connector_response: None,
        payment_method_status: None,
        minor_amount_captured: 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)
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn construct_defend_dispute_router_data<'a>(
    state: &'a SessionState,
    payment_intent: &'a storage::PaymentIntent,
    payment_attempt: &storage::PaymentAttempt,
    platform: &domain::Platform,
    dispute: &storage::Dispute,
) -> RouterResult<types::DefendDisputeRouterData> {
    let _db = &*state.store;
    let connector_id = &dispute.connector;
    let profile_id = payment_intent
        .profile_id
        .as_ref()
        .get_required_value("profile_id")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("profile_id is not set in payment_intent")?
        .clone();

    let merchant_connector_account = helpers::get_merchant_connector_account(
        state,
        platform.get_processor().get_account().get_id(),
        None,
        platform.get_processor().get_key_store(),
        &profile_id,
        connector_id,
        payment_attempt.merchant_connector_id.as_ref(),
    )
    .await?;

    let test_mode: Option<bool> = merchant_connector_account.is_test_mode_on();
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .get_connector_account_details()
        .parse_value("ConnectorAuthType")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;
    let payment_method = payment_attempt
        .payment_method
        .get_required_value("payment_method_type")?;

    let router_data = types::RouterData {
        flow: PhantomData,
        merchant_id: platform.get_processor().get_account().get_id().clone(),
        connector: connector_id.to_string(),
        payment_id: payment_attempt.payment_id.get_string_repr().to_owned(),
        tenant_id: state.tenant.tenant_id.clone(),
        attempt_id: payment_attempt.attempt_id.clone(),
        status: payment_attempt.status,
        payment_method,
        payment_method_type: payment_attempt.payment_method_type,
        connector_auth_type: auth_type,
        description: None,
        address: PaymentAddress::default(),
        auth_type: payment_attempt.authentication_type.unwrap_or_default(),
        connector_meta_data: merchant_connector_account.get_metadata(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: payment_intent
            .amount_captured
            .map(|amt| amt.get_amount_as_i64()),
        minor_amount_captured: payment_intent.amount_captured,
        payment_method_status: None,
        request: types::DefendDisputeRequestData {
            dispute_id: dispute.dispute_id.clone(),
            connector_dispute_id: dispute.connector_dispute_id.clone(),
        },
        response: Err(ErrorResponse::get_not_implemented()),
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        customer_id: None,
        connector_customer: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        payment_method_balance: None,
        connector_request_reference_id: get_connector_request_reference_id(
            &state.conf,
            platform.get_processor().get_account().get_id(),
            payment_intent,
            payment_attempt,
            connector_id,
        )?,
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: None,
        dispute_id: Some(dispute.dispute_id.clone()),
        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)
}

#[instrument(skip_all)]
pub async fn construct_retrieve_file_router_data<'a>(
    state: &'a SessionState,
    platform: &domain::Platform,
    file_metadata: &diesel_models::file::FileMetadata,
    dispute: Option<storage::Dispute>,
    connector_id: &str,
) -> RouterResult<types::RetrieveFileRouterData> {
    let profile_id = file_metadata
        .profile_id
        .as_ref()
        .ok_or(errors::ApiErrorResponse::MissingRequiredField {
            field_name: "profile_id",
        })
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("profile_id is not set in file_metadata")?;

    let merchant_connector_account = helpers::get_merchant_connector_account(
        state,
        platform.get_processor().get_account().get_id(),
        None,
        platform.get_processor().get_key_store(),
        profile_id,
        connector_id,
        file_metadata.merchant_connector_id.as_ref(),
    )
    .await?;

    let test_mode: Option<bool> = merchant_connector_account.is_test_mode_on();
    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(),
        connector: connector_id.to_string(),
        tenant_id: state.tenant.tenant_id.clone(),
        customer_id: None,
        connector_customer: None,
        payment_id: common_utils::id_type::PaymentId::get_irrelevant_id("dispute")
            .get_string_repr()
            .to_owned(),
        attempt_id: IRRELEVANT_ATTEMPT_ID_IN_DISPUTE_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: merchant_connector_account.get_metadata(),
        connector_wallets_details: merchant_connector_account.get_connector_wallets_details(),
        amount_captured: None,
        minor_amount_captured: None,
        payment_method_status: None,
        request: types::RetrieveFileRequestData {
            provider_file_id: file_metadata
                .provider_file_id
                .clone()
                .ok_or(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Missing provider file id")?,
            connector_dispute_id: dispute.map(|dispute_data| dispute_data.connector_dispute_id),
        },
        response: Err(ErrorResponse::default()),
        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_request_reference_id: IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_DISPUTE_FLOW
            .to_string(),
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        connector_api_version: None,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: 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)
}

pub fn is_merchant_enabled_for_payment_id_as_connector_request_id(
    conf: &Settings,
    merchant_id: &common_utils::id_type::MerchantId,
) -> bool {
    let config_map = &conf
        .connector_request_reference_id_config
        .merchant_ids_send_payment_id_as_connector_request_id;
    config_map.contains(merchant_id)
}

#[cfg(feature = "v1")]
pub fn get_connector_request_reference_id(
    conf: &Settings,
    merchant_id: &common_utils::id_type::MerchantId,
    payment_intent: &hyperswitch_domain_models::payments::PaymentIntent,
    payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
    connector_name: &str,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let is_config_enabled_to_send_payment_id_as_connector_request_id =
        is_merchant_enabled_for_payment_id_as_connector_request_id(conf, merchant_id);

    let connector_data = api::ConnectorData::get_connector_by_name(
        &conf.connectors,
        connector_name,
        api::GetToken::Connector,
        payment_attempt.merchant_connector_id.clone(),
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable_lazy(|| "Failed to construct connector data")?;

    let connector_request_reference_id = connector_data
        .connector
        .generate_connector_request_reference_id(
            payment_intent,
            payment_attempt,
            is_config_enabled_to_send_payment_id_as_connector_request_id,
        );
    Ok(connector_request_reference_id)
}

// TODO: Based on the connector configuration, the connector_request_reference_id should be generated
#[cfg(feature = "v2")]
pub fn get_connector_request_reference_id(
    conf: &Settings,
    merchant_id: &common_utils::id_type::MerchantId,
    payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
) -> String {
    todo!()
}

#[cfg(feature = "v1")]
pub fn get_connector_customer_id(
    conf: &Settings,
    connector_name: &str,
    connector_customer_id: Option<String>,
    customer_id: &Option<common_utils::id_type::CustomerId>,
    payment_method_info: &Option<hyperswitch_domain_models::payment_methods::PaymentMethod>,
    payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
) -> CustomResult<Option<String>, errors::ApiErrorResponse> {
    let connector_data = api::ConnectorData::get_connector_by_name(
        &conf.connectors,
        connector_name,
        api::GetToken::Connector,
        payment_attempt.merchant_connector_id.clone(),
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to construct connector data")?;

    let mandate_customer_id = payment_method_info
        .as_ref()
        .zip(payment_attempt.merchant_connector_id.as_ref())
        .map(|(pm, mci)| pm.get_payment_connector_customer_id(mci.clone()))
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to construct mandate reference")?
        .flatten();

    Ok(mandate_customer_id.or(connector_customer_id).or_else(|| {
        connector_data
            .connector
            .generate_connector_customer_id(customer_id, &payment_attempt.merchant_id)
    }))
}

#[cfg(feature = "v2")]
pub fn get_connector_customer_id(
    conf: &Settings,
    connector_name: &str,
    connector_customer_id: Option<String>,
    customer_id: &Option<common_utils::id_type::CustomerId>,
    payment_method_info: &Option<hyperswitch_domain_models::payment_methods::PaymentMethod>,
    payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
) -> CustomResult<Option<String>, errors::ApiErrorResponse> {
    todo!()
}

#[cfg(feature = "v1")]
pub fn get_payout_connector_customer_id(
    connector_data: &api::ConnectorData,
    connector_customer_id: Option<String>,
    customer_id: &Option<common_utils::id_type::CustomerId>,
    payment_method_info: &Option<hyperswitch_domain_models::payment_methods::PaymentMethod>,
    payout_attempt: &hyperswitch_domain_models::payouts::payout_attempt::PayoutAttempt,
) -> CustomResult<Option<String>, errors::ApiErrorResponse> {
    let mandate_customer_id = payment_method_info
        .as_ref()
        .zip(payout_attempt.merchant_connector_id.as_ref())
        .map(|(pm, mci)| pm.get_payout_connector_customer_id(mci.clone()))
        .transpose()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to construct mandate reference")?
        .flatten();
    Ok(mandate_customer_id.or(connector_customer_id).or_else(|| {
        connector_data
            .connector
            .generate_connector_customer_id(customer_id, &payout_attempt.merchant_id)
    }))
}

#[cfg(feature = "v2")]
pub fn get_payout_connector_customer_id(
    connector_data: &api::ConnectorData,
    connector_customer_id: Option<String>,
    customer_id: &Option<common_utils::id_type::CustomerId>,
    payment_method_info: &Option<hyperswitch_domain_models::payment_methods::PaymentMethod>,
    payout_attempt: &hyperswitch_domain_models::payouts::payout_attempt::PayoutAttempt,
) -> Option<String> {
    todo!()
}

/// Validate whether the profile_id exists and is associated with the merchant_id
pub async fn validate_and_get_business_profile(
    db: &dyn StorageInterface,
    merchant_key_store: &domain::MerchantKeyStore,
    profile_id: Option<&common_utils::id_type::ProfileId>,
    merchant_id: &common_utils::id_type::MerchantId,
) -> RouterResult<Option<domain::Profile>> {
    profile_id
        .async_map(|profile_id| async {
            db.find_business_profile_by_merchant_id_profile_id(
                merchant_key_store,
                merchant_id,
                profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_owned(),
            })
        })
        .await
        .transpose()
}

fn connector_needs_business_sub_label(connector_name: &str) -> bool {
    let connectors_list = [Connector::Cybersource];
    connectors_list
        .map(|connector| connector.to_string())
        .contains(&connector_name.to_string())
}

/// Create the connector label
/// {connector_name}_{country}_{business_label}
pub fn get_connector_label(
    business_country: Option<api_models::enums::CountryAlpha2>,
    business_label: Option<&String>,
    business_sub_label: Option<&String>,
    connector_name: &str,
) -> Option<String> {
    business_country
        .zip(business_label)
        .map(|(business_country, business_label)| {
            let mut connector_label =
                format!("{connector_name}_{business_country}_{business_label}");

            // Business sub label is currently being used only for cybersource
            // To ensure backwards compatibality, cybersource mca's created before this change
            // will have the business_sub_label value as default.
            //
            // Even when creating the connector account, if no sub label is provided, default will be used
            if connector_needs_business_sub_label(connector_name) {
                if let Some(sub_label) = business_sub_label {
                    connector_label.push_str(&format!("_{sub_label}"));
                } else {
                    connector_label.push_str("_default"); // For backwards compatibality
                }
            }

            connector_label
        })
}

#[cfg(feature = "v1")]
/// If profile_id is not passed, use default profile if available, or
/// If business_details (business_country and business_label) are passed, get the business_profile
/// or return a `MissingRequiredField` error
#[allow(clippy::too_many_arguments)]
pub async fn get_profile_id_from_business_details(
    business_country: Option<api_models::enums::CountryAlpha2>,
    business_label: Option<&String>,
    platform: &domain::Platform,
    request_profile_id: Option<&common_utils::id_type::ProfileId>,
    db: &dyn StorageInterface,
    should_validate: bool,
) -> RouterResult<common_utils::id_type::ProfileId> {
    match request_profile_id.or(platform
        .get_processor()
        .get_account()
        .default_profile
        .as_ref())
    {
        Some(profile_id) => {
            // Check whether this business profile belongs to the merchant
            if should_validate {
                let _ = validate_and_get_business_profile(
                    db,
                    platform.get_processor().get_key_store(),
                    Some(profile_id),
                    platform.get_processor().get_account().get_id(),
                )
                .await?;
            }
            Ok(profile_id.clone())
        }
        None => match business_country.zip(business_label) {
            Some((business_country, business_label)) => {
                let profile_name = format!("{business_country}_{business_label}");
                let business_profile = db
                    .find_business_profile_by_profile_name_merchant_id(
                        platform.get_processor().get_key_store(),
                        &profile_name,
                        platform.get_processor().get_account().get_id(),
                    )
                    .await
                    .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                        id: profile_name,
                    })?;

                Ok(business_profile.get_id().to_owned())
            }
            _ => Err(report!(errors::ApiErrorResponse::MissingRequiredField {
                field_name: "profile_id or business_country, business_label"
            })),
        },
    }
}

pub fn get_poll_id(merchant_id: &common_utils::id_type::MerchantId, unique_id: String) -> String {
    merchant_id.get_poll_id(&unique_id)
}

pub fn get_external_authentication_request_poll_id(
    payment_id: &common_utils::id_type::PaymentId,
) -> String {
    payment_id.get_external_authentication_request_poll_id()
}

pub fn get_modular_authentication_request_poll_id(
    authentication_id: &common_utils::id_type::AuthenticationId,
) -> String {
    authentication_id.get_external_authentication_request_poll_id()
}

pub fn get_html_redirect_response_popup(
    return_url_with_query_params: String,
) -> RouterResult<String> {
    Ok(html! {
        head {
            title { "Redirect Form" }
            (PreEscaped(format!(r#"
                    <script>
                        let return_url = "{return_url_with_query_params}";
                        try {{
                            // if inside iframe, send post message to parent for redirection
                            if (window.self !== window.parent) {{
                                window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                            // if parent, redirect self to return_url
                            }} else {{
                                window.location.href = return_url
                            }}
                        }}
                        catch(err) {{
                            // if error occurs, send post message to parent and wait for 10 secs to redirect. if doesn't redirect, redirect self to return_url
                            window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                            setTimeout(function() {{
                                window.location.href = return_url
                            }}, 10000);
                            console.log(err.message)
                        }}
                    </script>
                    "#)))
        }
    }
    .into_string())
}

#[cfg(feature = "v1")]
pub fn get_html_redirect_response_for_external_authentication(
    return_url_with_query_params: String,
    payment_response: &api_models::payments::PaymentsResponse,
    payment_id: common_utils::id_type::PaymentId,
    poll_config: &PollConfig,
) -> RouterResult<String> {
    // if intent_status is requires_customer_action then set poll_id, fetch poll config and do a poll_status post message, else do open_url post message to redirect to return_url
    let html = match payment_response.status {
            IntentStatus::RequiresCustomerAction => {
                // Request poll id sent to client for retrieve_poll_status api
                let req_poll_id = get_external_authentication_request_poll_id(&payment_id);
                let poll_frequency = poll_config.frequency;
                let poll_delay_in_secs = poll_config.delay_in_secs;
                html! {
                    head {
                        title { "Redirect Form" }
                        (PreEscaped(format!(r#"
                                <script>
                                    let return_url = "{return_url_with_query_params}";
                                    let poll_status_data = {{
                                        'poll_id': '{req_poll_id}',
                                        'frequency': '{poll_frequency}',
                                        'delay_in_secs': '{poll_delay_in_secs}',
                                        'return_url_with_query_params': return_url
                                    }};
                                    try {{
                                        // if inside iframe, send post message to parent for redirection
                                        if (window.self !== window.parent) {{
                                            window.parent.postMessage({{poll_status: poll_status_data}}, '*')
                                        // if parent, redirect self to return_url
                                        }} else {{
                                            window.location.href = return_url
                                        }}
                                    }}
                                    catch(err) {{
                                        // if error occurs, send post message to parent and wait for 10 secs to redirect. if doesn't redirect, redirect self to return_url
                                        window.parent.postMessage({{poll_status: poll_status_data}}, '*')
                                        setTimeout(function() {{
                                            window.location.href = return_url
                                        }}, 10000);
                                        console.log(err.message)
                                    }}
                                </script>
                                "#)))
                    }
                }
                .into_string()
            },
            _ => {
                html! {
                    head {
                        title { "Redirect Form" }
                        (PreEscaped(format!(r#"
                                <script>
                                    let return_url = "{return_url_with_query_params}";
                                    try {{
                                        // if inside iframe, send post message to parent for redirection
                                        if (window.self !== window.parent) {{
                                            window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                                        // if parent, redirect self to return_url
                                        }} else {{
                                            window.location.href = return_url
                                        }}
                                    }}
                                    catch(err) {{
                                        // if error occurs, send post message to parent and wait for 10 secs to redirect. if doesn't redirect, redirect self to return_url
                                        window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                                        setTimeout(function() {{
                                            window.location.href = return_url
                                        }}, 10000);
                                        console.log(err.message)
                                    }}
                                </script>
                                "#)))
                    }
                }
                .into_string()
            },
        };
    Ok(html)
}

#[cfg(feature = "v1")]
pub fn get_html_redirect_response_for_external_modular_authentication(
    return_url_with_query_params: String,
    authentication_response: &api_models::authentication::AuthenticationResponse,
    authentication_id: common_utils::id_type::AuthenticationId,
    poll_config: &PollConfig,
) -> RouterResult<String> {
    // if authentication_status is pending then set poll_id, fetch poll config and do a poll_status post message, else do open_url post message to redirect to return_url
    let html = match authentication_response.status {
            common_enums::AuthenticationStatus::Pending => {
                // Request poll id sent to client for retrieve_poll_status api
                let req_poll_id = get_modular_authentication_request_poll_id(&authentication_id);
                let poll_frequency = poll_config.frequency;
                let poll_delay_in_secs = poll_config.delay_in_secs;
                html! {
                    head {
                        title { "Redirect Form" }
                        (PreEscaped(format!(r#"
                                <script>
                                    let return_url = "{return_url_with_query_params}";
                                    let poll_status_data = {{
                                        'poll_id': '{req_poll_id}',
                                        'frequency': '{poll_frequency}',
                                        'delay_in_secs': '{poll_delay_in_secs}',
                                        'return_url_with_query_params': return_url
                                    }};
                                    try {{
                                        // if inside iframe, send post message to parent for redirection
                                        if (window.self !== window.parent) {{
                                            window.parent.postMessage({{poll_status: poll_status_data}}, '*')
                                        // if parent, redirect self to return_url
                                        }} else {{
                                            window.location.href = return_url
                                        }}
                                    }}
                                    catch(err) {{
                                        // if error occurs, send post message to parent and wait for 10 secs to redirect. if doesn't redirect, redirect self to return_url
                                        window.parent.postMessage({{poll_status: poll_status_data}}, '*')
                                        setTimeout(function() {{
                                            window.location.href = return_url
                                        }}, 10000);
                                        console.log(err.message)
                                    }}
                                </script>
                                "#)))
                    }
                }
                .into_string()
            },
            _ => {
                html! {
                    head {
                        title { "Redirect Form" }
                        (PreEscaped(format!(r#"
                                <script>
                                    let return_url = "{return_url_with_query_params}";
                                    try {{
                                        // if inside iframe, send post message to parent for redirection
                                        if (window.self !== window.parent) {{
                                            window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                                        // if parent, redirect self to return_url
                                        }} else {{
                                            window.location.href = return_url
                                        }}
                                    }}
                                    catch(err) {{
                                        // if error occurs, send post message to parent and wait for 10 secs to redirect. if doesn't redirect, redirect self to return_url
                                        window.parent.postMessage({{openurl_if_required: return_url}}, '*')
                                        setTimeout(function() {{
                                            window.location.href = return_url
                                        }}, 10000);
                                        console.log(err.message)
                                    }}
                                </script>
                                "#)))
                    }
                }
                .into_string()
            },
        };
    Ok(html)
}

#[inline]
pub fn get_flow_name<F>() -> RouterResult<String> {
    Ok(std::any::type_name::<F>()
        .to_string()
        .rsplit("::")
        .next()
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Flow stringify failed")?
        .to_string())
}

pub fn get_request_incremental_authorization_value(
    request_incremental_authorization: Option<bool>,
    capture_method: Option<common_enums::CaptureMethod>,
) -> RouterResult<Option<RequestIncrementalAuthorization>> {
    Some(request_incremental_authorization
        .map(|request_incremental_authorization| {
            if request_incremental_authorization {
                if matches!(
                    capture_method,
                    Some(common_enums::CaptureMethod::Automatic) | Some(common_enums::CaptureMethod::SequentialAutomatic)
                ) {
                    Err(errors::ApiErrorResponse::NotSupported { message: "incremental authorization is not supported when capture_method is automatic".to_owned() })?
                }
                Ok(RequestIncrementalAuthorization::True)
            } else {
                Ok(RequestIncrementalAuthorization::False)
            }
        })
        .unwrap_or(Ok(RequestIncrementalAuthorization::default()))).transpose()
}

pub fn get_incremental_authorization_allowed_value(
    incremental_authorization_allowed: Option<bool>,
    request_incremental_authorization: Option<RequestIncrementalAuthorization>,
) -> Option<bool> {
    if request_incremental_authorization == Some(RequestIncrementalAuthorization::False) {
        Some(false)
    } else {
        incremental_authorization_allowed
    }
}

pub(crate) trait GetProfileId {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId>;
}

impl GetProfileId for MerchantConnectorAccount {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}

impl GetProfileId for storage::PaymentIntent {
    #[cfg(feature = "v1")]
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }

    // TODO: handle this in a better way for v2
    #[cfg(feature = "v2")]
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}

impl<A> GetProfileId for (storage::PaymentIntent, A) {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.0.get_profile_id()
    }
}

impl GetProfileId for diesel_models::Dispute {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }
}

impl GetProfileId for diesel_models::Refund {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }
}

#[cfg(feature = "v1")]
impl GetProfileId for api_models::routing::RoutingConfigRequest {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }
}

#[cfg(feature = "v2")]
impl GetProfileId for api_models::routing::RoutingConfigRequest {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}

impl GetProfileId for api_models::routing::RoutingRetrieveLinkQuery {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.profile_id.as_ref()
    }
}

impl GetProfileId for diesel_models::routing_algorithm::RoutingProfileMetadata {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}

impl GetProfileId for domain::Profile {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(self.get_id())
    }
}

#[cfg(feature = "payouts")]
impl GetProfileId for storage::Payouts {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        Some(&self.profile_id)
    }
}
#[cfg(feature = "payouts")]
impl<T, F, R> GetProfileId for (storage::Payouts, T, F, R) {
    fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
        self.0.get_profile_id()
    }
}

/// Filter Objects based on profile ids
pub(super) fn filter_objects_based_on_profile_id_list<
    T: GetProfileId,
    U: IntoIterator<Item = T> + FromIterator<T>,
>(
    profile_id_list_auth_layer: Option<Vec<common_utils::id_type::ProfileId>>,
    object_list: U,
) -> U {
    if let Some(profile_id_list) = profile_id_list_auth_layer {
        let profile_ids_to_filter: HashSet<_> = profile_id_list.iter().collect();
        object_list
            .into_iter()
            .filter_map(|item| {
                if item
                    .get_profile_id()
                    .is_some_and(|profile_id| profile_ids_to_filter.contains(profile_id))
                {
                    Some(item)
                } else {
                    None
                }
            })
            .collect()
    } else {
        object_list
    }
}

pub(crate) fn validate_profile_id_from_auth_layer<T: GetProfileId + std::fmt::Debug>(
    profile_id_auth_layer: Option<common_utils::id_type::ProfileId>,
    object: &T,
) -> RouterResult<()> {
    match (profile_id_auth_layer, object.get_profile_id()) {
        (Some(auth_profile_id), Some(object_profile_id)) => {
            auth_profile_id.eq(object_profile_id).then_some(()).ok_or(
                errors::ApiErrorResponse::PreconditionFailed {
                    message: "Profile id authentication failed. Please use the correct JWT token"
                        .to_string(),
                }
                .into(),
            )
        }
        (Some(_auth_profile_id), None) => RouterResult::Err(
            errors::ApiErrorResponse::PreconditionFailed {
                message: "Couldn't find profile_id in record for authentication".to_string(),
            }
            .into(),
        )
        .attach_printable(format!("Couldn't find profile_id in entity {object:?}")),
        (None, None) | (None, Some(_)) => Ok(()),
    }
}

pub async fn construct_vault_router_data<F>(
    state: &SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    merchant_connector_account: &MerchantConnectorAccount,
    payment_method_custom_vaulting_data: Option<
        hyperswitch_domain_models::vault::PaymentMethodCustomVaultingData,
    >,
    connector_vault_id: Option<String>,
    connector_customer_id: Option<String>,
    should_generate_multiple_tokens: Option<bool>,
) -> RouterResult<VaultRouterDataV2<F>> {
    let connector_auth_type = merchant_connector_account
        .get_connector_account_details()
        .change_context(errors::ApiErrorResponse::InternalServerError)?;

    let resource_common_data = VaultConnectorFlowData {
        merchant_id: merchant_id.to_owned(),
    };

    let router_data = types::RouterDataV2 {
        flow: PhantomData,
        resource_common_data,
        tenant_id: state.tenant.tenant_id.clone(),
        connector_auth_type,
        request: types::VaultRequestData {
            payment_method_vaulting_data: payment_method_custom_vaulting_data,
            connector_vault_id,
            connector_customer_id,
            should_generate_multiple_tokens,
        },
        response: Ok(types::VaultResponseData::default()),
    };

    Ok(router_data)
}

pub fn validate_bank_account_data(data: &types::MerchantAccountData) -> RouterResult<()> {
    match data {
        types::MerchantAccountData::Iban { iban, .. } => validate_iban(iban),

        types::MerchantAccountData::Sepa { iban, .. } => validate_iban(iban),

        types::MerchantAccountData::SepaInstant { iban, .. } => validate_iban(iban),

        types::MerchantAccountData::Bacs {
            account_number,
            sort_code,
            ..
        } => validate_uk_account(account_number, sort_code),

        types::MerchantAccountData::FasterPayments {
            account_number,
            sort_code,
            ..
        } => validate_uk_account(account_number, sort_code),

        types::MerchantAccountData::Elixir { iban, .. } => validate_elixir_account(iban),

        types::MerchantAccountData::Bankgiro { number, .. } => validate_bankgiro_number(number),

        types::MerchantAccountData::Plusgiro { number, .. } => validate_plusgiro_number(number),
    }
}

fn validate_iban(iban: &Secret<String>) -> RouterResult<()> {
    let iban_str = iban.peek();

    if iban_str.len() > consts::IBAN_MAX_LENGTH || iban_str.len() < consts::IBAN_MIN_LENGTH {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: format!(
                "IBAN length must be between {} and {} characters",
                consts::IBAN_MIN_LENGTH,
                consts::IBAN_MAX_LENGTH
            ),
        }
        .into());
    }

    if iban.peek().len() > consts::IBAN_MAX_LENGTH {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "IBAN length must be up to 34 characters".to_string(),
        }
        .into());
    }
    let pattern = Regex::new(r"^[A-Z0-9]*$")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("failed to create regex pattern")?;

    let mut iban = iban.peek().to_string();

    if !pattern.is_match(iban.as_str()) {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "IBAN data must be alphanumeric".to_string(),
        }
        .into());
    }

    // MOD check
    let first_4 = iban.chars().take(4).collect::<String>();
    iban.push_str(first_4.as_str());
    let len = iban.len();

    let rearranged_iban = iban
        .chars()
        .rev()
        .take(len - 4)
        .collect::<String>()
        .chars()
        .rev()
        .collect::<String>();

    let mut result = String::new();

    rearranged_iban.chars().for_each(|c| {
        if c.is_ascii_uppercase() {
            let digit = (u32::from(c) - u32::from('A')) + 10;
            result.push_str(&format!("{digit:02}"));
        } else {
            result.push(c);
        }
    });

    let num = result
        .parse::<u128>()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("failed to validate IBAN")?;

    if num % 97 != 1 {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Invalid IBAN".to_string(),
        }
        .into());
    }

    Ok(())
}

fn validate_uk_account(
    account_number: &Secret<String>,
    sort_code: &Secret<String>,
) -> RouterResult<()> {
    if account_number.peek().len() > consts::BACS_MAX_ACCOUNT_NUMBER_LENGTH
        || sort_code.peek().len() != consts::BACS_SORT_CODE_LENGTH
    {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Invalid BACS numbers".to_string(),
        }
        .into());
    }

    Ok(())
}

fn validate_elixir_account(iban: &Secret<String>) -> RouterResult<()> {
    let iban_str = iban.peek();

    // Validate IBAN first
    validate_iban(iban)?;

    // Check if IBAN is Polish
    if !iban_str.starts_with("PL") {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Elixir IBAN must be Polish (PL)".to_string(),
        }
        .into());
    }

    // Validate IBAN length for Poland
    if iban_str.len() != consts::ELIXIR_IBAN_LENGTH {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Polish IBAN must be 28 characters".to_string(),
        }
        .into());
    }

    Ok(())
}

fn validate_bankgiro_number(number: &Secret<String>) -> RouterResult<()> {
    let num_str = number.peek();
    let clean_number = num_str.replace("-", "").replace(" ", "");

    // Length validation
    if clean_number.len() < consts::BANKGIRO_MIN_LENGTH
        || clean_number.len() > consts::BANKGIRO_MAX_LENGTH
    {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: format!(
                "Bankgiro number must be between {} and {} digits",
                consts::BANKGIRO_MIN_LENGTH,
                consts::BANKGIRO_MAX_LENGTH
            ),
        }
        .into());
    }

    // Must be numeric
    if !clean_number.chars().all(|c| c.is_ascii_digit()) {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Bankgiro number must be numeric".to_string(),
        }
        .into());
    }
    Ok(())
}

fn validate_plusgiro_number(number: &Secret<String>) -> RouterResult<()> {
    let num_str = number.peek();
    let clean_number = num_str.replace("-", "").replace(" ", "");

    // Length validation
    if clean_number.len() < consts::PLUSGIRO_MIN_LENGTH
        || clean_number.len() > consts::PLUSGIRO_MAX_LENGTH
    {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: format!(
                "Plusgiro number must be between {} and {} digits",
                consts::PLUSGIRO_MIN_LENGTH,
                consts::PLUSGIRO_MAX_LENGTH
            ),
        }
        .into());
    }

    // Must be numeric
    if !clean_number.chars().all(|c| c.is_ascii_digit()) {
        return Err(errors::ApiErrorResponse::InvalidRequestData {
            message: "Plusgiro number must be numeric".to_string(),
        }
        .into());
    }
    Ok(())
}

pub fn should_add_dispute_sync_task_to_pt(state: &SessionState, connector_name: Connector) -> bool {
    let list_dispute_supported_connectors = state
        .conf
        .list_dispute_supported_connectors
        .connector_list
        .clone();
    list_dispute_supported_connectors.contains(&connector_name)
}

pub fn should_proceed_with_submit_evidence(
    dispute_stage: DisputeStage,
    dispute_status: DisputeStatus,
) -> bool {
    matches!(
        dispute_stage,
        DisputeStage::PreDispute
            | DisputeStage::Dispute
            | DisputeStage::PreArbitration
            | DisputeStage::Arbitration
    ) && matches!(
        dispute_status,
        DisputeStatus::DisputeOpened | DisputeStatus::DisputeChallenged
    )
}

pub fn should_proceed_with_accept_dispute(
    dispute_stage: DisputeStage,
    dispute_status: DisputeStatus,
) -> bool {
    matches!(
        dispute_stage,
        DisputeStage::PreDispute
            | DisputeStage::Dispute
            | DisputeStage::PreArbitration
            | DisputeStage::Arbitration
    ) && matches!(
        dispute_status,
        DisputeStatus::DisputeChallenged | DisputeStatus::DisputeOpened
    )
}