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

use std::{str::FromStr, sync::Arc};

use api_models::{
    admin::{self, PaymentMethodsEnabled},
    enums as api_enums,
    payment_methods::RequestPaymentMethodTypes,
};
use common_enums::enums;
#[cfg(feature = "v2")]
use common_utils::ext_traits::{OptionExt, StringExt};
#[cfg(feature = "v2")]
use error_stack::ResultExt;
use euclid::frontend::dir;
use hyperswitch_constraint_graph as cgraph;
use kgraph_utils::{error::KgraphError, transformers::IntoDirValue};
use masking::ExposeInterface;
#[cfg(feature = "v1")]
use router_env::logger;
use storage_impl::redis::cache::{CacheKey, PM_FILTERS_CGRAPH_CACHE};

use crate::{configs::settings, db::StorageInterface, routes::SessionState};
#[cfg(feature = "v2")]
use crate::{
    db::{
        errors,
        storage::{self, enums as storage_enums},
    },
    services::logger,
};

pub fn make_pm_graph(
    builder: &mut cgraph::ConstraintGraphBuilder<dir::DirValue>,
    domain_id: cgraph::DomainId,
    payment_methods: &[masking::Secret<serde_json::value::Value>],
    connector: String,
    pm_config_mapping: &settings::ConnectorFilters,
    supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate,
    supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate,
) -> Result<(), KgraphError> {
    for payment_method in payment_methods.iter() {
        let pm_enabled =
            serde_json::from_value::<PaymentMethodsEnabled>(payment_method.clone().expose());
        if let Ok(payment_methods_enabled) = pm_enabled {
            compile_pm_graph(
                builder,
                domain_id,
                payment_methods_enabled.clone(),
                connector.clone(),
                pm_config_mapping,
                supported_payment_methods_for_mandate,
                supported_payment_methods_for_update_mandate,
            )?;
        };
    }
    Ok(())
}

pub async fn get_merchant_pm_filter_graph(
    state: &SessionState,
    key: &str,
) -> Option<Arc<hyperswitch_constraint_graph::ConstraintGraph<dir::DirValue>>> {
    PM_FILTERS_CGRAPH_CACHE
        .get_val::<Arc<hyperswitch_constraint_graph::ConstraintGraph<dir::DirValue>>>(CacheKey {
            key: key.to_string(),
            prefix: state.tenant.redis_key_prefix.clone(),
        })
        .await
}

pub async fn refresh_pm_filters_cache(
    state: &SessionState,
    key: &str,
    graph: cgraph::ConstraintGraph<dir::DirValue>,
) -> Arc<hyperswitch_constraint_graph::ConstraintGraph<dir::DirValue>> {
    let pm_filter_graph = Arc::new(graph);
    PM_FILTERS_CGRAPH_CACHE
        .push(
            CacheKey {
                key: key.to_string(),
                prefix: state.tenant.redis_key_prefix.clone(),
            },
            pm_filter_graph.clone(),
        )
        .await;
    pm_filter_graph
}

fn compile_pm_graph(
    builder: &mut cgraph::ConstraintGraphBuilder<dir::DirValue>,
    domain_id: cgraph::DomainId,
    pm_enabled: PaymentMethodsEnabled,
    connector: String,
    config: &settings::ConnectorFilters,
    supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate,
    supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate,
) -> Result<(), KgraphError> {
    if let Some(payment_method_types) = pm_enabled.payment_method_types {
        for pmt in payment_method_types {
            let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> =
                Vec::new();
            let mut agg_or_nodes_for_mandate_filters: Vec<(
                cgraph::NodeId,
                cgraph::Relation,
                cgraph::Strength,
            )> = Vec::new();

            // Connector supported for Update mandate filter
            let res = construct_supported_connectors_for_update_mandate_node(
                builder,
                domain_id,
                supported_payment_methods_for_update_mandate,
                pmt.clone(),
                pm_enabled.payment_method,
            );
            if let Ok(Some(connector_eligible_for_update_mandates_node)) = res {
                agg_or_nodes_for_mandate_filters.push((
                    connector_eligible_for_update_mandates_node,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Strong,
                ))
            }

            // Connector supported for mandates filter
            if let Some(supported_pm_for_mandates) = supported_payment_methods_for_mandate
                .0
                .get(&pm_enabled.payment_method)
            {
                if let Some(supported_connector_for_mandates) =
                    supported_pm_for_mandates.0.get(&pmt.payment_method_type)
                {
                    let supported_connectors: Vec<api_enums::Connector> =
                        supported_connector_for_mandates
                            .connector_list
                            .clone()
                            .into_iter()
                            .collect();
                    if let Ok(Some(connector_eligible_for_mandates_node)) =
                        construct_supported_connectors_for_mandate_node(
                            builder,
                            domain_id,
                            supported_connectors,
                        )
                    {
                        agg_or_nodes_for_mandate_filters.push((
                            connector_eligible_for_mandates_node,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ))
                    }
                }
            }

            // Non Prominent Mandate flows
            let payment_type_non_mandate_value_node = builder.make_value_node(
                cgraph::NodeValue::Value(dir::DirValue::PaymentType(
                    euclid::enums::PaymentType::NonMandate,
                )),
                None,
                None::<()>,
            );
            let payment_type_setup_mandate_value_node = builder.make_value_node(
                cgraph::NodeValue::Value(dir::DirValue::PaymentType(
                    euclid::enums::PaymentType::SetupMandate,
                )),
                None,
                None::<()>,
            );

            let non_major_mandate_any_node = builder
                .make_any_aggregator(
                    &[
                        (
                            payment_type_non_mandate_value_node,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ),
                        (
                            payment_type_setup_mandate_value_node,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ),
                    ],
                    None,
                    None::<()>,
                    Some(domain_id),
                )
                .map_err(KgraphError::GraphConstructionError)?;

            agg_or_nodes_for_mandate_filters.push((
                non_major_mandate_any_node,
                cgraph::Relation::Positive,
                cgraph::Strength::Strong,
            ));

            let agg_or_node = builder
                .make_any_aggregator(
                    &agg_or_nodes_for_mandate_filters,
                    None,
                    None::<()>,
                    Some(domain_id),
                )
                .map_err(KgraphError::GraphConstructionError)?;

            agg_nodes.push((
                agg_or_node,
                cgraph::Relation::Positive,
                cgraph::Strength::Strong,
            ));

            // Capture Method filter
            config
                .0
                .get(connector.as_str())
                .or_else(|| config.0.get("default"))
                .map(|inner| {
                    if let Ok(Some(capture_method_filter)) =
                        construct_capture_method_node(builder, inner, pmt.payment_method_type)
                    {
                        agg_nodes.push((
                            capture_method_filter,
                            cgraph::Relation::Negative,
                            cgraph::Strength::Strong,
                        ))
                    }
                });

            // Country filter
            if let Ok(Some(country_node)) = compile_accepted_countries_for_mca(
                builder,
                domain_id,
                pmt.payment_method_type,
                pmt.accepted_countries,
                config,
                connector.clone(),
            ) {
                agg_nodes.push((
                    country_node,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Strong,
                ))
            }

            // Currency filter
            if let Ok(Some(currency_node)) = compile_accepted_currency_for_mca(
                builder,
                domain_id,
                pmt.payment_method_type,
                pmt.accepted_currencies,
                config,
                connector.clone(),
            ) {
                agg_nodes.push((
                    currency_node,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Strong,
                ))
            }

            let and_node_for_all_the_filters = builder
                .make_all_aggregator(&agg_nodes, None, None::<()>, Some(domain_id))
                .map_err(KgraphError::GraphConstructionError)?;

            // Making our output node
            let pmt_info = "PaymentMethodType";
            let dir_node: cgraph::NodeValue<dir::DirValue> =
                (pmt.payment_method_type, pm_enabled.payment_method)
                    .into_dir_value()
                    .map(Into::into)?;
            let payment_method_type_value_node =
                builder.make_value_node(dir_node, Some(pmt_info), None::<()>);

            builder
                .make_edge(
                    and_node_for_all_the_filters,
                    payment_method_type_value_node,
                    cgraph::Strength::Normal,
                    cgraph::Relation::Positive,
                    Some(domain_id),
                )
                .map_err(KgraphError::GraphConstructionError)?;
        }
    }
    Ok(())
}

fn construct_supported_connectors_for_update_mandate_node(
    builder: &mut cgraph::ConstraintGraphBuilder<dir::DirValue>,
    domain_id: cgraph::DomainId,
    supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate,
    pmt: RequestPaymentMethodTypes,
    payment_method: enums::PaymentMethod,
) -> Result<Option<cgraph::NodeId>, KgraphError> {
    let card_value_node = builder.make_value_node(
        cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Card)),
        None,
        None::<()>,
    );

    let payment_type_value_node = builder.make_value_node(
        cgraph::NodeValue::Value(dir::DirValue::PaymentType(
            euclid::enums::PaymentType::UpdateMandate,
        )),
        None,
        None::<()>,
    );

    let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new();
    let mut card_dir_values = Vec::new();
    let mut non_card_dir_values = Vec::new();

    if let Some(supported_pm_for_mandates) = supported_payment_methods_for_update_mandate
        .0
        .get(&payment_method)
    {
        if payment_method == enums::PaymentMethod::Card {
            if let Some(credit_connector_list) = supported_pm_for_mandates
                .0
                .get(&api_enums::PaymentMethodType::Credit)
            {
                card_dir_values.extend(
                    credit_connector_list
                        .connector_list
                        .clone()
                        .into_iter()
                        .filter_map(|connector| {
                            api_enums::RoutableConnectors::from_str(connector.to_string().as_str())
                                .ok()
                                .map(|connector| {
                                    dir::DirValue::Connector(Box::new(
                                        api_models::routing::ast::ConnectorChoice { connector },
                                    ))
                                })
                        }),
                );
            }

            if let Some(debit_connector_list) = supported_pm_for_mandates
                .0
                .get(&api_enums::PaymentMethodType::Debit)
            {
                card_dir_values.extend(
                    debit_connector_list
                        .connector_list
                        .clone()
                        .into_iter()
                        .filter_map(|connector| {
                            api_enums::RoutableConnectors::from_str(connector.to_string().as_str())
                                .ok()
                                .map(|connector| {
                                    dir::DirValue::Connector(Box::new(
                                        api_models::routing::ast::ConnectorChoice { connector },
                                    ))
                                })
                        }),
                );
            }
            let card_in_node = builder
                .make_in_aggregator(card_dir_values, None, None::<()>)
                .map_err(KgraphError::GraphConstructionError)?;

            let card_and_node = builder
                .make_all_aggregator(
                    &[
                        (
                            card_value_node,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ),
                        (
                            payment_type_value_node,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ),
                        (
                            card_in_node,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ),
                    ],
                    None,
                    None::<()>,
                    Some(domain_id),
                )
                .map_err(KgraphError::GraphConstructionError)?;

            agg_nodes.push((
                card_and_node,
                cgraph::Relation::Positive,
                cgraph::Strength::Strong,
            ));
        } else if let Some(connector_list) =
            supported_pm_for_mandates.0.get(&pmt.payment_method_type)
        {
            non_card_dir_values.extend(
                connector_list
                    .connector_list
                    .clone()
                    .into_iter()
                    .filter_map(|connector| {
                        api_enums::RoutableConnectors::from_str(connector.to_string().as_str())
                            .ok()
                            .map(|connector| {
                                dir::DirValue::Connector(Box::new(
                                    api_models::routing::ast::ConnectorChoice { connector },
                                ))
                            })
                    }),
            );
            let non_card_mandate_in_node = builder
                .make_in_aggregator(non_card_dir_values, None, None::<()>)
                .map_err(KgraphError::GraphConstructionError)?;

            let non_card_and_node = builder
                .make_all_aggregator(
                    &[
                        (
                            card_value_node,
                            cgraph::Relation::Negative,
                            cgraph::Strength::Strong,
                        ),
                        (
                            payment_type_value_node,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ),
                        (
                            non_card_mandate_in_node,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ),
                    ],
                    None,
                    None::<()>,
                    Some(domain_id),
                )
                .map_err(KgraphError::GraphConstructionError)?;

            agg_nodes.push((
                non_card_and_node,
                cgraph::Relation::Positive,
                cgraph::Strength::Strong,
            ));
        }
    }

    if !agg_nodes.is_empty() {
        Ok(Some(
            builder
                .make_any_aggregator(
                    &agg_nodes,
                    Some("any node for card and non card pm"),
                    None::<()>,
                    Some(domain_id),
                )
                .map_err(KgraphError::GraphConstructionError)?,
        ))
    } else {
        Ok(None)
    }
}

fn construct_supported_connectors_for_mandate_node(
    builder: &mut cgraph::ConstraintGraphBuilder<dir::DirValue>,
    domain_id: cgraph::DomainId,
    eligible_connectors: Vec<api_enums::Connector>,
) -> Result<Option<cgraph::NodeId>, KgraphError> {
    let payment_type_value_node = builder.make_value_node(
        cgraph::NodeValue::Value(dir::DirValue::PaymentType(
            euclid::enums::PaymentType::NewMandate,
        )),
        None,
        None::<()>,
    );
    let connectors_from_config: Vec<dir::DirValue> = eligible_connectors
        .into_iter()
        .filter_map(|connector| {
            match api_enums::RoutableConnectors::from_str(connector.to_string().as_str()) {
                Ok(connector) => Some(dir::DirValue::Connector(Box::new(
                    api_models::routing::ast::ConnectorChoice { connector },
                ))),
                Err(_) => None,
            }
        })
        .collect();

    if connectors_from_config.is_empty() {
        Ok(None)
    } else {
        let connector_in_aggregator = builder
            .make_in_aggregator(connectors_from_config, None, None::<()>)
            .map_err(KgraphError::GraphConstructionError)?;
        Ok(Some(
            builder
                .make_all_aggregator(
                    &[
                        (
                            payment_type_value_node,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ),
                        (
                            connector_in_aggregator,
                            cgraph::Relation::Positive,
                            cgraph::Strength::Strong,
                        ),
                    ],
                    None,
                    None::<()>,
                    Some(domain_id),
                )
                .map_err(KgraphError::GraphConstructionError)?,
        ))
    }
}

fn construct_capture_method_node(
    builder: &mut cgraph::ConstraintGraphBuilder<dir::DirValue>,
    payment_method_filters: &settings::PaymentMethodFilters,
    payment_method_type: api_enums::PaymentMethodType,
) -> Result<Option<cgraph::NodeId>, KgraphError> {
    if !payment_method_filters
        .0
        .get(&settings::PaymentMethodFilterKey::PaymentMethodType(
            payment_method_type,
        ))
        .and_then(|v| v.not_available_flows)
        .and_then(|v| v.capture_method)
        .map(|v| !matches!(v, api_enums::CaptureMethod::Manual))
        .unwrap_or(true)
    {
        return Ok(Some(builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::CaptureMethod(
                common_enums::CaptureMethod::Manual,
            )),
            None,
            None::<()>,
        )));
    }
    Ok(None)
}

// fn construct_card_network_nodes(
//     builder: &mut cgraph::ConstraintGraphBuilder<dir::DirValue>,
//     mca_card_networks: Vec<api_enums::CardNetwork>,
// ) -> Result<Option<cgraph::NodeId>, KgraphError> {
//     Ok(Some(
//         builder
//             .make_in_aggregator(
//                 mca_card_networks
//                     .into_iter()
//                     .map(dir::DirValue::CardNetwork)
//                     .collect(),
//                 None,
//                 None::<()>,
//             )
//             .map_err(KgraphError::GraphConstructionError)?,
//     ))
// }

fn compile_accepted_countries_for_mca(
    builder: &mut cgraph::ConstraintGraphBuilder<dir::DirValue>,
    domain_id: cgraph::DomainId,
    payment_method_type: enums::PaymentMethodType,
    pm_countries: Option<admin::AcceptedCountries>,
    config: &settings::ConnectorFilters,
    connector: String,
) -> Result<Option<cgraph::NodeId>, KgraphError> {
    let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new();

    // Country from the MCA
    if let Some(pm_obj_countries) = pm_countries {
        match pm_obj_countries {
            admin::AcceptedCountries::EnableOnly(countries) => {
                let pm_object_country_value_node = builder
                    .make_in_aggregator(
                        countries
                            .into_iter()
                            .map(|country| {
                                dir::DirValue::BillingCountry(common_enums::Country::from_alpha2(
                                    country,
                                ))
                            })
                            .collect(),
                        None,
                        None::<()>,
                    )
                    .map_err(KgraphError::GraphConstructionError)?;
                agg_nodes.push((
                    pm_object_country_value_node,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Weak,
                ));
            }
            admin::AcceptedCountries::DisableOnly(countries) => {
                let pm_object_country_value_node = builder
                    .make_in_aggregator(
                        countries
                            .into_iter()
                            .map(|country| {
                                dir::DirValue::BillingCountry(common_enums::Country::from_alpha2(
                                    country,
                                ))
                            })
                            .collect(),
                        None,
                        None::<()>,
                    )
                    .map_err(KgraphError::GraphConstructionError)?;
                agg_nodes.push((
                    pm_object_country_value_node,
                    cgraph::Relation::Negative,
                    cgraph::Strength::Weak,
                ));
            }
            admin::AcceptedCountries::AllAccepted => return Ok(None),
        }
    }

    // country from config
    if let Some(derived_config) = config
        .0
        .get(connector.as_str())
        .or_else(|| config.0.get("default"))
    {
        if let Some(value) =
            derived_config
                .0
                .get(&settings::PaymentMethodFilterKey::PaymentMethodType(
                    payment_method_type,
                ))
        {
            if let Some(config_countries) = value.country.as_ref() {
                let config_countries: Vec<common_enums::Country> = Vec::from_iter(config_countries)
                    .into_iter()
                    .map(|country| common_enums::Country::from_alpha2(*country))
                    .collect();
                let dir_countries: Vec<dir::DirValue> = config_countries
                    .into_iter()
                    .map(dir::DirValue::BillingCountry)
                    .collect();

                let config_country_agg_node = builder
                    .make_in_aggregator(dir_countries, None, None::<()>)
                    .map_err(KgraphError::GraphConstructionError)?;

                agg_nodes.push((
                    config_country_agg_node,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Weak,
                ));
            }
        } else if let Some(default_derived_config) = config.0.get("default") {
            if let Some(value) =
                default_derived_config
                    .0
                    .get(&settings::PaymentMethodFilterKey::PaymentMethodType(
                        payment_method_type,
                    ))
            {
                if let Some(config_countries) = value.country.as_ref() {
                    let config_countries: Vec<common_enums::Country> =
                        Vec::from_iter(config_countries)
                            .into_iter()
                            .map(|country| common_enums::Country::from_alpha2(*country))
                            .collect();
                    let dir_countries: Vec<dir::DirValue> = config_countries
                        .into_iter()
                        .map(dir::DirValue::BillingCountry)
                        .collect();

                    let config_country_agg_node = builder
                        .make_in_aggregator(dir_countries, None, None::<()>)
                        .map_err(KgraphError::GraphConstructionError)?;

                    agg_nodes.push((
                        config_country_agg_node,
                        cgraph::Relation::Positive,
                        cgraph::Strength::Weak,
                    ));
                }
            }
        };
    }
    Ok(Some(
        builder
            .make_all_aggregator(&agg_nodes, None, None::<()>, Some(domain_id))
            .map_err(KgraphError::GraphConstructionError)?,
    ))
}

fn compile_accepted_currency_for_mca(
    builder: &mut cgraph::ConstraintGraphBuilder<dir::DirValue>,
    domain_id: cgraph::DomainId,
    payment_method_type: enums::PaymentMethodType,
    pm_currency: Option<admin::AcceptedCurrencies>,
    config: &settings::ConnectorFilters,
    connector: String,
) -> Result<Option<cgraph::NodeId>, KgraphError> {
    let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new();
    // Currency from the MCA
    if let Some(pm_obj_currency) = pm_currency {
        match pm_obj_currency {
            admin::AcceptedCurrencies::EnableOnly(currency) => {
                let pm_object_currency_value_node = builder
                    .make_in_aggregator(
                        currency
                            .into_iter()
                            .map(dir::DirValue::PaymentCurrency)
                            .collect(),
                        None,
                        None::<()>,
                    )
                    .map_err(KgraphError::GraphConstructionError)?;
                agg_nodes.push((
                    pm_object_currency_value_node,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Weak,
                ));
            }
            admin::AcceptedCurrencies::DisableOnly(currency) => {
                let pm_object_currency_value_node = builder
                    .make_in_aggregator(
                        currency
                            .into_iter()
                            .map(dir::DirValue::PaymentCurrency)
                            .collect(),
                        None,
                        None::<()>,
                    )
                    .map_err(KgraphError::GraphConstructionError)?;
                agg_nodes.push((
                    pm_object_currency_value_node,
                    cgraph::Relation::Negative,
                    cgraph::Strength::Weak,
                ));
            }
            admin::AcceptedCurrencies::AllAccepted => return Ok(None),
        }
    }

    // currency from config
    if let Some(derived_config) = config
        .0
        .get(connector.as_str())
        .or_else(|| config.0.get("default"))
    {
        if let Some(value) =
            derived_config
                .0
                .get(&settings::PaymentMethodFilterKey::PaymentMethodType(
                    payment_method_type,
                ))
        {
            if let Some(config_currencies) = value.currency.as_ref() {
                let config_currency: Vec<common_enums::Currency> =
                    Vec::from_iter(config_currencies)
                        .into_iter()
                        .copied()
                        .collect();

                let dir_currencies: Vec<dir::DirValue> = config_currency
                    .into_iter()
                    .map(dir::DirValue::PaymentCurrency)
                    .collect();

                let config_currency_agg_node = builder
                    .make_in_aggregator(dir_currencies, None, None::<()>)
                    .map_err(KgraphError::GraphConstructionError)?;

                agg_nodes.push((
                    config_currency_agg_node,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Weak,
                ));
            }
        } else if let Some(default_derived_config) = config.0.get("default") {
            if let Some(value) =
                default_derived_config
                    .0
                    .get(&settings::PaymentMethodFilterKey::PaymentMethodType(
                        payment_method_type,
                    ))
            {
                if let Some(config_currencies) = value.currency.as_ref() {
                    let config_currency: Vec<common_enums::Currency> =
                        Vec::from_iter(config_currencies)
                            .into_iter()
                            .copied()
                            .collect();

                    let dir_currencies: Vec<dir::DirValue> = config_currency
                        .into_iter()
                        .map(dir::DirValue::PaymentCurrency)
                        .collect();

                    let config_currency_agg_node = builder
                        .make_in_aggregator(dir_currencies, None, None::<()>)
                        .map_err(KgraphError::GraphConstructionError)?;

                    agg_nodes.push((
                        config_currency_agg_node,
                        cgraph::Relation::Positive,
                        cgraph::Strength::Weak,
                    ))
                }
            }
        };
    }
    Ok(Some(
        builder
            .make_all_aggregator(&agg_nodes, None, None::<()>, Some(domain_id))
            .map_err(KgraphError::GraphConstructionError)?,
    ))
}

pub async fn get_merchant_config_for_eligibility_check(
    db: &dyn StorageInterface,
    merchant_id: &common_utils::id_type::MerchantId,
) -> bool {
    let config = db
        .find_config_by_key_unwrap_or(
            &merchant_id.get_should_perform_eligibility_check_key(),
            Some("false".to_string()),
        )
        .await;
    match config {
        Ok(conf) => conf.config == "true",
        Err(error) => {
            logger::error!(?error);
            false
        }
    }
}

pub async fn get_sdk_next_action_for_payment_method_list(
    db: &dyn StorageInterface,
    merchant_id: &common_utils::id_type::MerchantId,
) -> api_models::payments::SdkNextAction {
    let should_perform_eligibility_check =
        get_merchant_config_for_eligibility_check(db, merchant_id).await;
    let next_action_call = if should_perform_eligibility_check {
        api_models::payments::NextActionCall::EligibilityCheck
    } else {
        api_models::payments::NextActionCall::Confirm
    };
    api_models::payments::SdkNextAction {
        next_action: next_action_call,
    }
}

#[cfg(feature = "v2")]
pub(super) async fn retrieve_payment_token_data(
    state: &SessionState,
    token: String,
    payment_method: Option<&storage_enums::PaymentMethod>,
) -> errors::RouterResult<storage::PaymentTokenData> {
    let redis_conn = state
        .store
        .get_redis_conn()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get redis connection")?;

    let key = format!(
        "pm_token_{}_{}_hyperswitch",
        token,
        payment_method
            .get_required_value("payment_method")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Payment method is required")?
    );

    let token_data_string = redis_conn
        .get_key::<Option<String>>(&key.into())
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to fetch the token from redis")?
        .ok_or(error_stack::Report::new(
            errors::ApiErrorResponse::UnprocessableEntity {
                message: "Token is invalid or expired".to_owned(),
            },
        ))?;

    token_data_string
        .clone()
        .parse_struct("PaymentTokenData")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("failed to deserialize hyperswitch token data")
}

#[cfg(feature = "v2")]
pub(super) async fn delete_payment_token_data(
    state: &SessionState,
    key_for_token: &str,
) -> errors::RouterResult<()> {
    let redis_conn = state
        .store
        .get_redis_conn()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to get redis connection")?;
    match redis_conn.delete_key(&key_for_token.into()).await {
        Ok(_) => Ok(()),
        Err(err) => {
            {
                logger::error!("Error while deleting redis key: {:?}", err)
            };
            Ok(())
        }
    }
}


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

use std::str::FromStr;

use ::payment_methods::{controller::PaymentMethodsController, core::migration};
use api_models::{enums as api_enums, payment_methods as payment_methods_api};
use common_utils::{
    consts,
    ext_traits::OptionExt,
    generate_customer_id_of_default_length, id_type,
    pii::Email,
    type_name,
    types::keymanager::{Identifier, KeyManagerState, ToEncryptable},
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation};
use masking::{ExposeInterface, PeekInterface, SwitchStrategy};
use router_env::logger;

use super::{
    CardNetworkTokenizeExecutor, NetworkTokenizationBuilder, NetworkTokenizationProcess,
    NetworkTokenizationResponse, State, StoreLockerResponse, TransitionTo,
};
use crate::{
    core::payment_methods::{
        cards::{add_card_to_hs_locker, PmCards},
        transformers as pm_transformers,
    },
    errors::{self, RouterResult},
    types::{api, domain},
    utils,
};

// Available states for card tokenization
pub struct TokenizeWithCard;
pub struct CardRequestValidated;
pub struct CardDetailsAssigned;
pub struct CustomerAssigned;
pub struct CardTokenized;
pub struct CardStored;
pub struct CardTokenStored;
pub struct PaymentMethodCreated;

impl State for TokenizeWithCard {}
impl State for CustomerAssigned {}
impl State for CardRequestValidated {}
impl State for CardDetailsAssigned {}
impl State for CardTokenized {}
impl State for CardStored {}
impl State for CardTokenStored {}
impl State for PaymentMethodCreated {}

// State transitions for card tokenization
impl TransitionTo<CardRequestValidated> for TokenizeWithCard {}
impl TransitionTo<CardDetailsAssigned> for CardRequestValidated {}
impl TransitionTo<CustomerAssigned> for CardDetailsAssigned {}
impl TransitionTo<CardTokenized> for CustomerAssigned {}
impl TransitionTo<CardTokenStored> for CardTokenized {}
impl TransitionTo<PaymentMethodCreated> for CardTokenStored {}

impl Default for NetworkTokenizationBuilder<'_, TokenizeWithCard> {
    fn default() -> Self {
        Self::new()
    }
}

impl<'a> NetworkTokenizationBuilder<'a, TokenizeWithCard> {
    pub fn new() -> Self {
        Self {
            state: std::marker::PhantomData,
            customer: None,
            card: None,
            card_cvc: None,
            network_token: None,
            stored_card: None,
            stored_token: None,
            payment_method_response: None,
            card_tokenized: false,
            error_code: None,
            error_message: None,
        }
    }
    pub fn set_validate_result(self) -> NetworkTokenizationBuilder<'a, CardRequestValidated> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            network_token: self.network_token,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, CardRequestValidated> {
    pub fn set_card_details(
        self,
        card_req: &'a domain::TokenizeCardRequest,
        optional_card_info: Option<diesel_models::CardInfo>,
    ) -> NetworkTokenizationBuilder<'a, CardDetailsAssigned> {
        let card = domain::CardDetail {
            card_number: card_req.raw_card_number.clone(),
            card_exp_month: card_req.card_expiry_month.clone(),
            card_exp_year: card_req.card_expiry_year.clone(),
            bank_code: optional_card_info
                .as_ref()
                .and_then(|card_info| card_info.bank_code.clone()),
            nick_name: card_req.nick_name.clone(),
            card_holder_name: card_req.card_holder_name.clone(),
            card_issuer: optional_card_info
                .as_ref()
                .map_or(card_req.card_issuer.clone(), |card_info| {
                    card_info.card_issuer.clone()
                }),
            card_network: optional_card_info
                .as_ref()
                .map_or(card_req.card_network.clone(), |card_info| {
                    card_info.card_network.clone()
                }),
            card_type: optional_card_info.as_ref().map_or(
                card_req
                    .card_type
                    .as_ref()
                    .map(|card_type| card_type.to_string()),
                |card_info| card_info.card_type.clone(),
            ),
            card_issuing_country: optional_card_info
                .as_ref()
                .map_or(card_req.card_issuing_country.clone(), |card_info| {
                    card_info.card_issuing_country.clone()
                }),
            co_badged_card_data: None,
        };
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            card: Some(card),
            card_cvc: card_req.card_cvc.clone(),
            customer: self.customer,
            network_token: self.network_token,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, CardDetailsAssigned> {
    pub fn set_customer(
        self,
        customer: &'a api::CustomerDetails,
    ) -> NetworkTokenizationBuilder<'a, CustomerAssigned> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            customer: Some(customer),
            card: self.card,
            card_cvc: self.card_cvc,
            network_token: self.network_token,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, CustomerAssigned> {
    pub fn get_optional_card_and_cvc(
        &self,
    ) -> (Option<domain::CardDetail>, Option<masking::Secret<String>>) {
        (self.card.clone(), self.card_cvc.clone())
    }
    pub fn set_token_details(
        self,
        network_token: &'a NetworkTokenizationResponse,
    ) -> NetworkTokenizationBuilder<'a, CardTokenized> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            network_token: Some(&network_token.0),
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, CardTokenized> {
    pub fn set_stored_card_response(
        self,
        store_card_response: &'a StoreLockerResponse,
    ) -> NetworkTokenizationBuilder<'a, CardStored> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            stored_card: Some(&store_card_response.store_card_resp),
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            network_token: self.network_token,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, CardStored> {
    pub fn set_stored_token_response(
        self,
        store_token_response: &'a StoreLockerResponse,
    ) -> NetworkTokenizationBuilder<'a, CardTokenStored> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            card_tokenized: true,
            stored_token: Some(&store_token_response.store_token_resp),
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            network_token: self.network_token,
            stored_card: self.stored_card,
            payment_method_response: self.payment_method_response,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, CardTokenStored> {
    pub fn set_payment_method_response(
        self,
        payment_method: &'a domain::PaymentMethod,
    ) -> NetworkTokenizationBuilder<'a, PaymentMethodCreated> {
        let card_detail_from_locker = self.card.as_ref().map(|card| api::CardDetailFromLocker {
            scheme: None,
            issuer_country: card.card_issuing_country.clone(),
            last4_digits: Some(card.card_number.clone().get_last4()),
            card_number: None,
            expiry_month: Some(card.card_exp_month.clone().clone()),
            expiry_year: Some(card.card_exp_year.clone().clone()),
            card_token: None,
            card_holder_name: card.card_holder_name.clone(),
            card_fingerprint: None,
            nick_name: card.nick_name.clone(),
            card_network: card.card_network.clone(),
            card_isin: Some(card.card_number.clone().get_card_isin()),
            card_issuer: card.card_issuer.clone(),
            card_type: card.card_type.clone(),
            saved_to_locker: true,
        });
        let payment_method_response = api::PaymentMethodResponse {
            merchant_id: payment_method.merchant_id.clone(),
            customer_id: Some(payment_method.customer_id.clone()),
            payment_method_id: payment_method.payment_method_id.clone(),
            payment_method: payment_method.payment_method,
            payment_method_type: payment_method.payment_method_type,
            card: card_detail_from_locker,
            recurring_enabled: Some(true),
            installment_payment_enabled: Some(false),
            metadata: payment_method.metadata.clone(),
            created: Some(payment_method.created_at),
            last_used_at: Some(payment_method.last_used_at),
            client_secret: payment_method.client_secret.clone(),
            bank_transfer: None,
            payment_experience: None,
        };
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            payment_method_response: Some(payment_method_response),
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            network_token: self.network_token,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl NetworkTokenizationBuilder<'_, PaymentMethodCreated> {
    pub fn build(self) -> api::CardNetworkTokenizeResponse {
        api::CardNetworkTokenizeResponse {
            payment_method_response: self.payment_method_response,
            customer: self.customer.cloned(),
            card_tokenized: self.card_tokenized,
            error_code: self.error_code.cloned(),
            error_message: self.error_message.cloned(),
            // Below field is mutated by caller functions for batched API operations
            tokenization_data: None,
        }
    }
}

// Specific executor for card tokenization
impl CardNetworkTokenizeExecutor<'_, domain::TokenizeCardRequest> {
    pub async fn validate_request_and_fetch_optional_customer(
        &self,
    ) -> RouterResult<Option<api::CustomerDetails>> {
        // Validate card's expiry
        migration::validate_card_expiry(&self.data.card_expiry_month, &self.data.card_expiry_year)?;

        // Validate customer ID
        let customer_id = self
            .customer
            .customer_id
            .as_ref()
            .get_required_value("customer_id")
            .change_context(errors::ApiErrorResponse::MissingRequiredField {
                field_name: "customer.customer_id",
            })?;

        // Fetch customer details if present
        let db = &*self.state.store;
        db.find_customer_optional_by_customer_id_merchant_id(
            customer_id,
            self.merchant_account.get_id(),
            self.key_store,
            self.merchant_account.storage_scheme,
        )
        .await
        .inspect_err(|err| logger::info!("Error fetching customer: {:?}", err))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .map_or(
            // Validate if customer creation is feasible
            if self.customer.name.is_some()
                || self.customer.email.is_some()
                || self.customer.phone.is_some()
            {
                Ok(None)
            } else {
                Err(report!(errors::ApiErrorResponse::MissingRequiredFields {
                    field_names: vec!["customer.name", "customer.email", "customer.phone"],
                }))
            },
            // If found, send back CustomerDetails from DB
            |optional_customer| {
                Ok(optional_customer.map(|customer| api::CustomerDetails {
                    id: customer.customer_id.clone(),
                    name: customer.name.clone().map(|name| name.into_inner()),
                    email: customer.email.clone().map(Email::from),
                    phone: customer.phone.clone().map(|phone| phone.into_inner()),
                    phone_country_code: customer.phone_country_code.clone(),
                    tax_registration_id: customer
                        .tax_registration_id
                        .clone()
                        .map(|tax_registration_id| tax_registration_id.into_inner()),
                }))
            },
        )
    }

    pub async fn create_customer(&self) -> RouterResult<api::CustomerDetails> {
        let db = &*self.state.store;
        let customer_id = self
            .customer
            .customer_id
            .as_ref()
            .get_required_value("customer_id")
            .change_context(errors::ApiErrorResponse::MissingRequiredField {
                field_name: "customer_id",
            })?;
        let key_manager_state: &KeyManagerState = &self.state.into();

        let encrypted_data = crypto_operation(
            key_manager_state,
            type_name!(domain::Customer),
            CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableCustomer::to_encryptable(
                domain::FromRequestEncryptableCustomer {
                    name: self.customer.name.clone(),
                    email: self
                        .customer
                        .email
                        .clone()
                        .map(|email| email.expose().switch_strategy()),
                    phone: self.customer.phone.clone(),
                    tax_registration_id: self.customer.tax_registration_id.clone(),
                },
            )),
            Identifier::Merchant(self.merchant_account.get_id().clone()),
            self.key_store.key.get_inner().peek(),
        )
        .await
        .inspect_err(|err| logger::info!("Error encrypting customer: {:?}", err))
        .and_then(|val| val.try_into_batchoperation())
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to encrypt customer")?;

        let encryptable_customer =
            domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Failed to form EncryptableCustomer")?;

        let new_customer_id = generate_customer_id_of_default_length();
        let domain_customer = domain::Customer {
            customer_id: new_customer_id.clone(),
            merchant_id: self.merchant_account.get_id().clone(),
            name: encryptable_customer.name,
            email: encryptable_customer.email.map(|email| {
                utils::Encryptable::new(
                    email.clone().into_inner().switch_strategy(),
                    email.into_encrypted(),
                )
            }),
            phone: encryptable_customer.phone,
            description: None,
            phone_country_code: self.customer.phone_country_code.to_owned(),
            metadata: None,
            connector_customer: None,
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            address_id: None,
            default_payment_method_id: None,
            updated_by: None,
            version: common_types::consts::API_VERSION,
            tax_registration_id: encryptable_customer.tax_registration_id,
            // TODO: Populate created_by from authentication context once it is integrated in auth data
            created_by: None,
            last_modified_by: None, // Same as created_by on creation
        };

        db.insert_customer(
            domain_customer,
            self.key_store,
            self.merchant_account.storage_scheme,
        )
        .await
        .inspect_err(|err| logger::info!("Error creating a customer: {:?}", err))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| {
            format!(
                "Failed to insert customer [id - {:?}] for merchant [id - {:?}]",
                customer_id,
                self.merchant_account.get_id()
            )
        })?;

        Ok(api::CustomerDetails {
            id: new_customer_id,
            name: self.customer.name.clone(),
            email: self.customer.email.clone(),
            phone: self.customer.phone.clone(),
            phone_country_code: self.customer.phone_country_code.clone(),
            tax_registration_id: self.customer.tax_registration_id.clone(),
        })
    }

    pub async fn store_card_and_token_in_locker(
        &self,
        network_token: &NetworkTokenizationResponse,
        card: &domain::CardDetail,
        customer_id: &id_type::CustomerId,
    ) -> RouterResult<StoreLockerResponse> {
        let stored_card_resp = self.store_card_in_locker(card, customer_id).await?;
        let stored_token_resp = self
            .store_network_token_in_locker(
                network_token,
                customer_id,
                card.card_holder_name.clone(),
                card.nick_name.clone(),
            )
            .await?;
        let store_locker_response = StoreLockerResponse {
            store_card_resp: stored_card_resp,
            store_token_resp: stored_token_resp,
        };
        Ok(store_locker_response)
    }

    pub async fn store_card_in_locker(
        &self,
        card: &domain::CardDetail,
        customer_id: &id_type::CustomerId,
    ) -> RouterResult<pm_transformers::StoreCardRespPayload> {
        let merchant_id = self.merchant_account.get_id();
        let locker_req =
            pm_transformers::StoreLockerReq::LockerCard(pm_transformers::StoreCardReq {
                merchant_id: merchant_id.clone(),
                merchant_customer_id: customer_id.clone(),
                card: payment_methods_api::Card {
                    card_number: card.card_number.clone(),
                    card_exp_month: card.card_exp_month.clone(),
                    card_exp_year: card.card_exp_year.clone(),
                    card_isin: Some(card.card_number.get_card_isin().clone()),
                    name_on_card: card.card_holder_name.clone(),
                    nick_name: card
                        .nick_name
                        .as_ref()
                        .map(|nick_name| nick_name.clone().expose()),
                    card_brand: None,
                },
                requestor_card_reference: None,
                ttl: self.state.conf.locker.ttl_for_storage_in_secs,
            });

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

        Ok(stored_resp)
    }

    pub async fn create_payment_method(
        &self,
        stored_locker_resp: &StoreLockerResponse,
        network_token_details: &NetworkTokenizationResponse,
        card_details: &domain::CardDetail,
        customer_id: &id_type::CustomerId,
    ) -> RouterResult<domain::PaymentMethod> {
        let payment_method_id = common_utils::generate_id(consts::ID_LENGTH, "pm");

        // Form encrypted PM data (original card)
        let enc_pm_data = self.encrypt_card(card_details, true).await?;

        // Form encrypted network token data
        let enc_token_data = self
            .encrypt_network_token(network_token_details, card_details, true)
            .await?;

        // Form PM create entry
        let payment_method_create = api::PaymentMethodCreate {
            payment_method: Some(api_enums::PaymentMethod::Card),
            payment_method_type: card_details
                .card_type
                .as_ref()
                .and_then(|card_type| api_enums::PaymentMethodType::from_str(card_type).ok()),
            payment_method_issuer: card_details.card_issuer.clone(),
            payment_method_issuer_code: None,
            card: Some(api::CardDetail {
                card_number: card_details.card_number.clone(),
                card_exp_month: card_details.card_exp_month.clone(),
                card_exp_year: card_details.card_exp_year.clone(),
                card_holder_name: card_details.card_holder_name.clone(),
                nick_name: card_details.nick_name.clone(),
                card_issuing_country: card_details.card_issuing_country.clone(),
                card_network: card_details.card_network.clone(),
                card_issuer: card_details.card_issuer.clone(),
                card_type: card_details.card_type.clone(),
                card_cvc: None, // DO NOT POPULATE CVC FOR ADDITIONAL PAYMENT METHOD DATA
            }),
            metadata: None,
            customer_id: Some(customer_id.clone()),
            card_network: card_details
                .card_network
                .as_ref()
                .map(|network| network.to_string()),
            bank_transfer: None,
            wallet: None,
            client_secret: None,
            payment_method_data: None,
            billing: None,
            connector_mandate_details: None,
            network_transaction_id: None,
        };
        PmCards {
            state: self.state,
            platform: &domain::Platform::new(
                self.merchant_account.clone(),
                self.key_store.clone(),
                self.merchant_account.clone(),
                self.key_store.clone(),
            ),
        }
        .create_payment_method(
            &payment_method_create,
            customer_id,
            &payment_method_id,
            Some(stored_locker_resp.store_card_resp.card_reference.clone()),
            self.merchant_account.get_id(),
            None,
            None,
            Some(enc_pm_data),
            None,
            None,
            None,
            None,
            None,
            network_token_details.1.clone(),
            Some(stored_locker_resp.store_token_resp.card_reference.clone()),
            Some(enc_token_data),
            Default::default(), // this method is used only for card bulk tokenization, and currently external vault is not supported for this hence passing Default i.e. InternalVault
        )
        .await
    }
}


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

use api_models::enums as api_enums;
use common_utils::{ext_traits::OptionExt, fp_utils::when, pii::Email};
use error_stack::{report, ResultExt};
use masking::Secret;
use router_env::logger;

use super::{
    CardNetworkTokenizeExecutor, NetworkTokenizationBuilder, NetworkTokenizationProcess,
    NetworkTokenizationResponse, State, TransitionTo,
};
use crate::{
    core::payment_methods::transformers as pm_transformers,
    errors::{self, RouterResult},
    types::{api, domain},
};

// Available states for payment method tokenization
pub struct TokenizeWithPmId;
pub struct PmValidated;
pub struct PmFetched;
pub struct PmAssigned;
pub struct PmTokenized;
pub struct PmTokenStored;
pub struct PmTokenUpdated;

impl State for TokenizeWithPmId {}
impl State for PmValidated {}
impl State for PmFetched {}
impl State for PmAssigned {}
impl State for PmTokenized {}
impl State for PmTokenStored {}
impl State for PmTokenUpdated {}

// State transitions for payment method tokenization
impl TransitionTo<PmFetched> for TokenizeWithPmId {}
impl TransitionTo<PmValidated> for PmFetched {}
impl TransitionTo<PmAssigned> for PmValidated {}
impl TransitionTo<PmTokenized> for PmAssigned {}
impl TransitionTo<PmTokenStored> for PmTokenized {}
impl TransitionTo<PmTokenUpdated> for PmTokenStored {}

impl Default for NetworkTokenizationBuilder<'_, TokenizeWithPmId> {
    fn default() -> Self {
        Self::new()
    }
}

impl<'a> NetworkTokenizationBuilder<'a, TokenizeWithPmId> {
    pub fn new() -> Self {
        Self {
            state: std::marker::PhantomData,
            customer: None,
            card: None,
            card_cvc: None,
            network_token: None,
            stored_card: None,
            stored_token: None,
            payment_method_response: None,
            card_tokenized: false,
            error_code: None,
            error_message: None,
        }
    }
    pub fn set_payment_method(
        self,
        payment_method: &domain::PaymentMethod,
    ) -> NetworkTokenizationBuilder<'a, PmFetched> {
        let payment_method_response = api::PaymentMethodResponse {
            merchant_id: payment_method.merchant_id.clone(),
            customer_id: Some(payment_method.customer_id.clone()),
            payment_method_id: payment_method.payment_method_id.clone(),
            payment_method: payment_method.payment_method,
            payment_method_type: payment_method.payment_method_type,
            recurring_enabled: Some(true),
            installment_payment_enabled: Some(false),
            metadata: payment_method.metadata.clone(),
            created: Some(payment_method.created_at),
            last_used_at: Some(payment_method.last_used_at),
            client_secret: payment_method.client_secret.clone(),
            card: None,
            bank_transfer: None,
            payment_experience: None,
        };
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            payment_method_response: Some(payment_method_response),
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            network_token: self.network_token,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, PmFetched> {
    pub fn set_validate_result(
        self,
        customer: &'a api::CustomerDetails,
    ) -> NetworkTokenizationBuilder<'a, PmValidated> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            customer: Some(customer),
            card: self.card,
            card_cvc: self.card_cvc,
            network_token: self.network_token,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, PmValidated> {
    pub fn set_card_details(
        self,
        card_from_locker: &'a api_models::payment_methods::Card,
        optional_card_info: Option<diesel_models::CardInfo>,
        card_cvc: Option<Secret<String>>,
    ) -> NetworkTokenizationBuilder<'a, PmAssigned> {
        let card = domain::CardDetail {
            card_number: card_from_locker.card_number.clone(),
            card_exp_month: card_from_locker.card_exp_month.clone(),
            card_exp_year: card_from_locker.card_exp_year.clone(),
            bank_code: optional_card_info
                .as_ref()
                .and_then(|card_info| card_info.bank_code.clone()),
            nick_name: card_from_locker
                .nick_name
                .as_ref()
                .map(|nick_name| Secret::new(nick_name.clone())),
            card_holder_name: card_from_locker.name_on_card.clone(),
            card_issuer: optional_card_info
                .as_ref()
                .and_then(|card_info| card_info.card_issuer.clone()),
            card_network: optional_card_info
                .as_ref()
                .and_then(|card_info| card_info.card_network.clone()),
            card_type: optional_card_info
                .as_ref()
                .and_then(|card_info| card_info.card_type.clone()),
            card_issuing_country: optional_card_info
                .as_ref()
                .and_then(|card_info| card_info.card_issuing_country.clone()),
            co_badged_card_data: None,
        };
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            card: Some(card),
            card_cvc,
            customer: self.customer,
            network_token: self.network_token,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, PmAssigned> {
    pub fn get_optional_card_and_cvc(
        &self,
    ) -> (Option<domain::CardDetail>, Option<Secret<String>>) {
        (self.card.clone(), self.card_cvc.clone())
    }
    pub fn set_token_details(
        self,
        network_token: &'a NetworkTokenizationResponse,
    ) -> NetworkTokenizationBuilder<'a, PmTokenized> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            network_token: Some(&network_token.0),
            card_tokenized: true,
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            stored_card: self.stored_card,
            stored_token: self.stored_token,
            payment_method_response: self.payment_method_response,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, PmTokenized> {
    pub fn set_stored_token_response(
        self,
        store_token_response: &'a pm_transformers::StoreCardRespPayload,
    ) -> NetworkTokenizationBuilder<'a, PmTokenStored> {
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            stored_token: Some(store_token_response),
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            network_token: self.network_token,
            stored_card: self.stored_card,
            payment_method_response: self.payment_method_response,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl<'a> NetworkTokenizationBuilder<'a, PmTokenStored> {
    pub fn set_payment_method(
        self,
        payment_method: &'a domain::PaymentMethod,
    ) -> NetworkTokenizationBuilder<'a, PmTokenUpdated> {
        let payment_method_response = api::PaymentMethodResponse {
            merchant_id: payment_method.merchant_id.clone(),
            customer_id: Some(payment_method.customer_id.clone()),
            payment_method_id: payment_method.payment_method_id.clone(),
            payment_method: payment_method.payment_method,
            payment_method_type: payment_method.payment_method_type,
            recurring_enabled: Some(true),
            installment_payment_enabled: Some(false),
            metadata: payment_method.metadata.clone(),
            created: Some(payment_method.created_at),
            last_used_at: Some(payment_method.last_used_at),
            client_secret: payment_method.client_secret.clone(),
            card: None,
            bank_transfer: None,
            payment_experience: None,
        };
        NetworkTokenizationBuilder {
            state: std::marker::PhantomData,
            payment_method_response: Some(payment_method_response),
            customer: self.customer,
            card: self.card,
            card_cvc: self.card_cvc,
            stored_token: self.stored_token,
            network_token: self.network_token,
            stored_card: self.stored_card,
            card_tokenized: self.card_tokenized,
            error_code: self.error_code,
            error_message: self.error_message,
        }
    }
}

impl NetworkTokenizationBuilder<'_, PmTokenUpdated> {
    pub fn build(self) -> api::CardNetworkTokenizeResponse {
        api::CardNetworkTokenizeResponse {
            payment_method_response: self.payment_method_response,
            customer: self.customer.cloned(),
            card_tokenized: self.card_tokenized,
            error_code: self.error_code.cloned(),
            error_message: self.error_message.cloned(),
            // Below field is mutated by caller functions for batched API operations
            tokenization_data: None,
        }
    }
}

// Specific executor for payment method tokenization
impl CardNetworkTokenizeExecutor<'_, domain::TokenizePaymentMethodRequest> {
    pub async fn fetch_payment_method(
        &self,
        payment_method_id: &str,
    ) -> RouterResult<domain::PaymentMethod> {
        self.state
            .store
            .find_payment_method(
                self.key_store,
                payment_method_id,
                self.merchant_account.storage_scheme,
            )
            .await
            .map_err(|err| match err.current_context() {
                errors::StorageError::DatabaseError(err)
                    if matches!(
                        err.current_context(),
                        diesel_models::errors::DatabaseError::NotFound
                    ) =>
                {
                    report!(errors::ApiErrorResponse::InvalidRequestData {
                        message: "Invalid payment_method_id".into(),
                    })
                }
                errors::StorageError::ValueNotFound(_) => {
                    report!(errors::ApiErrorResponse::InvalidRequestData {
                        message: "Invalid payment_method_id".to_string(),
                    })
                }
                err => {
                    logger::info!("Error fetching payment_method: {:?}", err);
                    report!(errors::ApiErrorResponse::InternalServerError)
                }
            })
    }
    pub async fn validate_request_and_locker_reference_and_customer(
        &self,
        payment_method: &domain::PaymentMethod,
    ) -> RouterResult<(String, api::CustomerDetails)> {
        // Ensure customer ID matches
        let customer_id_in_req = self
            .customer
            .customer_id
            .clone()
            .get_required_value("customer_id")
            .change_context(errors::ApiErrorResponse::MissingRequiredField {
                field_name: "customer",
            })?;
        when(payment_method.customer_id != customer_id_in_req, || {
            Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                message: "Payment method does not belong to the customer".to_string()
            }))
        })?;

        // Ensure payment method is card
        match payment_method.payment_method {
            Some(api_enums::PaymentMethod::Card) => Ok(()),
            Some(_) => Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                message: "Payment method is not card".to_string()
            })),
            None => Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                message: "Payment method is empty".to_string()
            })),
        }?;

        // Ensure card is not tokenized already
        when(
            payment_method
                .network_token_requestor_reference_id
                .is_some(),
            || {
                Err(report!(errors::ApiErrorResponse::InvalidRequestData {
                    message: "Card is already tokenized".to_string()
                }))
            },
        )?;

        // Ensure locker reference is present
        let locker_id = payment_method.locker_id.clone().ok_or(report!(
            errors::ApiErrorResponse::InvalidRequestData {
                message: "locker_id not found for given payment_method_id".to_string()
            }
        ))?;

        // Fetch customer
        let db = &*self.state.store;
        let customer = db
            .find_customer_by_customer_id_merchant_id(
                &payment_method.customer_id,
                self.merchant_account.get_id(),
                self.key_store,
                self.merchant_account.storage_scheme,
            )
            .await
            .inspect_err(|err| logger::info!("Error fetching customer: {:?}", err))
            .change_context(errors::ApiErrorResponse::InternalServerError)?;

        let customer_details = api::CustomerDetails {
            id: customer.customer_id.clone(),
            name: customer.name.clone().map(|name| name.into_inner()),
            email: customer.email.clone().map(Email::from),
            phone: customer.phone.clone().map(|phone| phone.into_inner()),
            phone_country_code: customer.phone_country_code.clone(),
            tax_registration_id: customer
                .tax_registration_id
                .clone()
                .map(|tax_registration_id| tax_registration_id.into_inner()),
        };

        Ok((locker_id, customer_details))
    }
    pub async fn update_payment_method(
        &self,
        store_token_response: &pm_transformers::StoreCardRespPayload,
        payment_method: domain::PaymentMethod,
        network_token_details: &NetworkTokenizationResponse,
        card_details: &domain::CardDetail,
    ) -> RouterResult<domain::PaymentMethod> {
        // Form encrypted network token data
        let enc_token_data = self
            .encrypt_network_token(network_token_details, card_details, true)
            .await?;

        // Update payment method
        let payment_method_update = diesel_models::PaymentMethodUpdate::NetworkTokenDataUpdate {
            network_token_requestor_reference_id: network_token_details.1.clone(),
            network_token_locker_id: Some(store_token_response.card_reference.clone()),
            network_token_payment_method_data: Some(enc_token_data.into()),
            last_modified_by: None,
        };
        self.state
            .store
            .update_payment_method(
                self.key_store,
                payment_method,
                payment_method_update,
                self.merchant_account.storage_scheme,
            )
            .await
            .inspect_err(|err| logger::info!("Error updating payment method: {:?}", err))
            .change_context(errors::ApiErrorResponse::InternalServerError)
    }
}


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

use pm_auth::types as pm_auth_types;

use crate::{core::errors, types, types::transformers::ForeignTryFrom};

impl From<types::MerchantAccountData> for pm_auth_types::MerchantAccountData {
    fn from(from: types::MerchantAccountData) -> Self {
        match from {
            types::MerchantAccountData::Iban { iban, name, .. } => Self::Iban { iban, name },
            types::MerchantAccountData::Bacs {
                account_number,
                sort_code,
                name,
                ..
            } => Self::Bacs {
                account_number,
                sort_code,
                name,
            },
            types::MerchantAccountData::FasterPayments {
                account_number,
                sort_code,
                name,
                ..
            } => Self::FasterPayments {
                account_number,
                sort_code,
                name,
            },
            types::MerchantAccountData::Sepa { iban, name, .. } => Self::Sepa { iban, name },
            types::MerchantAccountData::SepaInstant { iban, name, .. } => {
                Self::SepaInstant { iban, name }
            }
            types::MerchantAccountData::Elixir {
                account_number,
                iban,
                name,
                ..
            } => Self::Elixir {
                account_number,
                iban,
                name,
            },
            types::MerchantAccountData::Bankgiro { number, name, .. } => {
                Self::Bankgiro { number, name }
            }
            types::MerchantAccountData::Plusgiro { number, name, .. } => {
                Self::Plusgiro { number, name }
            }
        }
    }
}

impl From<types::MerchantRecipientData> for pm_auth_types::MerchantRecipientData {
    fn from(value: types::MerchantRecipientData) -> Self {
        match value {
            types::MerchantRecipientData::ConnectorRecipientId(id) => {
                Self::ConnectorRecipientId(id)
            }
            types::MerchantRecipientData::WalletId(id) => Self::WalletId(id),
            types::MerchantRecipientData::AccountData(data) => Self::AccountData(data.into()),
        }
    }
}

impl ForeignTryFrom<types::ConnectorAuthType> for pm_auth_types::ConnectorAuthType {
    type Error = errors::ConnectorError;
    fn foreign_try_from(auth_type: types::ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            types::ConnectorAuthType::BodyKey { api_key, key1 } => {
                Ok::<Self, errors::ConnectorError>(Self::BodyKey {
                    client_id: api_key.to_owned(),
                    secret: key1.to_owned(),
                })
            }
            _ => Err(errors::ConnectorError::FailedToObtainAuthType),
        }
    }
}


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

use common_utils::ext_traits::ValueExt;
use error_stack::ResultExt;
use pm_auth::types::{self as pm_auth_types, api::BoxedPaymentAuthConnector};

use crate::{
    core::errors::{self, ApiErrorResponse},
    types::{self, domain, transformers::ForeignTryFrom},
};

pub trait PaymentAuthConnectorDataExt {
    fn get_connector_by_name(name: &str) -> errors::CustomResult<Self, ApiErrorResponse>
    where
        Self: Sized;
    fn convert_connector(
        connector_name: pm_auth_types::PaymentMethodAuthConnectors,
    ) -> errors::CustomResult<BoxedPaymentAuthConnector, ApiErrorResponse>;
}

pub fn get_connector_auth_type(
    merchant_connector_account: domain::MerchantConnectorAccount,
) -> errors::CustomResult<pm_auth_types::ConnectorAuthType, ApiErrorResponse> {
    let auth_type: types::ConnectorAuthType = merchant_connector_account
        .connector_account_details
        .parse_value("ConnectorAuthType")
        .change_context(ApiErrorResponse::MerchantConnectorAccountNotFound {
            id: "ConnectorAuthType".to_string(),
        })?;

    pm_auth_types::ConnectorAuthType::foreign_try_from(auth_type)
        .change_context(ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while converting ConnectorAuthType")
}


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

use error_stack::{Report, ResultExt};
pub use hyperswitch_domain_models::router_request_types::authentication::{
    AcquirerDetails, ExternalThreeDSConnectorMetadata, PreAuthenticationData, ThreeDsMethodData,
};

use crate::{
    core::errors,
    types::{storage, transformers::ForeignTryFrom},
    utils::OptionExt,
};

impl ForeignTryFrom<&storage::Authentication> for PreAuthenticationData {
    type Error = Report<errors::ApiErrorResponse>;

    fn foreign_try_from(authentication: &storage::Authentication) -> Result<Self, Self::Error> {
        let error_message = errors::ApiErrorResponse::UnprocessableEntity { message: "Pre Authentication must be completed successfully before Authentication can be performed".to_string() };
        let threeds_server_transaction_id = authentication
            .threeds_server_transaction_id
            .clone()
            .get_required_value("threeds_server_transaction_id")
            .change_context(error_message)?;
        let message_version = authentication
            .message_version
            .clone()
            .get_required_value("message_version")?;
        Ok(Self {
            threeds_server_transaction_id,
            message_version,
            acquirer_bin: authentication.acquirer_bin.clone(),
            acquirer_merchant_id: authentication.acquirer_merchant_id.clone(),
            acquirer_country_code: authentication.acquirer_country_code.clone(),
            connector_metadata: authentication.connector_metadata.clone(),
        })
    }
}


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

use std::marker::PhantomData;

use api_models::payments;
use common_enums::PaymentMethod;
use common_utils::ext_traits::ValueExt;
use error_stack::ResultExt;

use crate::{
    core::{
        errors::{self, RouterResult},
        payments::helpers as payments_helpers,
    },
    types::{
        self, domain, storage,
        transformers::{ForeignFrom, ForeignTryFrom},
    },
    utils::ext_traits::OptionExt,
    SessionState,
};

const IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW: &str =
    "irrelevant_attempt_id_in_AUTHENTICATION_flow";
const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW: &str =
    "irrelevant_connector_request_reference_id_in_AUTHENTICATION_flow";

#[allow(clippy::too_many_arguments)]
pub fn construct_authentication_router_data(
    state: &SessionState,
    merchant_id: common_utils::id_type::MerchantId,
    authentication_connector: String,
    payment_method_data: domain::PaymentMethodData,
    payment_method: PaymentMethod,
    billing_address: hyperswitch_domain_models::address::Address,
    shipping_address: Option<hyperswitch_domain_models::address::Address>,
    browser_details: Option<types::BrowserInformation>,
    amount: Option<common_utils::types::MinorUnit>,
    currency: Option<common_enums::Currency>,
    message_category: types::api::authentication::MessageCategory,
    device_channel: payments::DeviceChannel,
    merchant_connector_account: payments_helpers::MerchantConnectorAccountType,
    authentication_data: storage::Authentication,
    return_url: Option<String>,
    sdk_information: Option<payments::SdkInformation>,
    threeds_method_comp_ind: payments::ThreeDsCompletionIndicator,
    email: Option<common_utils::pii::Email>,
    webhook_url: String,
    three_ds_requestor_url: String,
    psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
    payment_id: common_utils::id_type::PaymentId,
    force_3ds_challenge: bool,
) -> RouterResult<types::authentication::ConnectorAuthenticationRouterData> {
    let router_request = types::authentication::ConnectorAuthenticationRequestData {
        payment_method_data,
        billing_address,
        shipping_address,
        browser_details,
        amount: amount.map(|amt| amt.get_amount_as_i64()),
        currency,
        message_category,
        device_channel,
        pre_authentication_data: super::types::PreAuthenticationData::foreign_try_from(
            &authentication_data,
        )?,
        return_url,
        sdk_information,
        email,
        three_ds_requestor_url,
        threeds_method_comp_ind,
        webhook_url,
        force_3ds_challenge,
    };
    construct_router_data(
        state,
        authentication_connector,
        payment_method,
        merchant_id.clone(),
        types::PaymentAddress::default(),
        router_request,
        &merchant_connector_account,
        psd2_sca_exemption_type,
        payment_id,
    )
}

pub fn construct_post_authentication_router_data(
    state: &SessionState,
    authentication_connector: String,
    business_profile: domain::Profile,
    merchant_connector_account: payments_helpers::MerchantConnectorAccountType,
    authentication_data: &storage::Authentication,
    payment_id: &common_utils::id_type::PaymentId,
) -> RouterResult<types::authentication::ConnectorPostAuthenticationRouterData> {
    let threeds_server_transaction_id = authentication_data
        .threeds_server_transaction_id
        .clone()
        .get_required_value("threeds_server_transaction_id")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;
    let router_request = types::authentication::ConnectorPostAuthenticationRequestData {
        threeds_server_transaction_id,
    };
    construct_router_data(
        state,
        authentication_connector,
        PaymentMethod::default(),
        business_profile.merchant_id.clone(),
        types::PaymentAddress::default(),
        router_request,
        &merchant_connector_account,
        None,
        payment_id.clone(),
    )
}

pub fn construct_pre_authentication_router_data<F: Clone>(
    state: &SessionState,
    authentication_connector: String,
    card: hyperswitch_domain_models::payment_method_data::Card,
    merchant_connector_account: &payments_helpers::MerchantConnectorAccountType,
    merchant_id: common_utils::id_type::MerchantId,
    payment_id: common_utils::id_type::PaymentId,
) -> RouterResult<
    types::RouterData<
        F,
        types::authentication::PreAuthNRequestData,
        types::authentication::AuthenticationResponseData,
    >,
> {
    let router_request = types::authentication::PreAuthNRequestData { card };
    construct_router_data(
        state,
        authentication_connector,
        PaymentMethod::default(),
        merchant_id,
        types::PaymentAddress::default(),
        router_request,
        merchant_connector_account,
        None,
        payment_id,
    )
}

#[allow(clippy::too_many_arguments)]
pub fn construct_router_data<F: Clone, Req, Res>(
    state: &SessionState,
    authentication_connector_name: String,
    payment_method: PaymentMethod,
    merchant_id: common_utils::id_type::MerchantId,
    address: types::PaymentAddress,
    request_data: Req,
    merchant_connector_account: &payments_helpers::MerchantConnectorAccountType,
    psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
    payment_id: common_utils::id_type::PaymentId,
) -> RouterResult<types::RouterData<F, Req, Res>> {
    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)?;
    Ok(types::RouterData {
        flow: PhantomData,
        merchant_id,
        customer_id: None,
        tenant_id: state.tenant.tenant_id.clone(),
        connector_customer: None,
        connector: authentication_connector_name,
        payment_id: payment_id.get_string_repr().to_owned(),
        attempt_id: IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW.to_owned(),
        status: common_enums::AttemptStatus::default(),
        payment_method,
        payment_method_type: None,
        connector_auth_type: auth_type,
        description: None,
        address,
        auth_type: common_enums::AuthenticationType::NoThreeDs,
        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,
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        payment_method_balance: None,
        connector_api_version: None,
        request: request_data,
        response: Err(types::ErrorResponse::default()),
        connector_request_reference_id:
            IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW.to_owned(),
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode,
        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,
        raw_connector_response: None,
        is_payment_id_from_merchant: None,
        l2_l3_data: None,
        minor_amount_capturable: None,
        authorized_amount: None,
    })
}

impl ForeignFrom<common_enums::TransactionStatus> for common_enums::AuthenticationStatus {
    fn foreign_from(trans_status: common_enums::TransactionStatus) -> Self {
        match trans_status {
            common_enums::TransactionStatus::Success => Self::Success,
            common_enums::TransactionStatus::Failure
            | common_enums::TransactionStatus::Rejected
            | common_enums::TransactionStatus::VerificationNotPerformed
            | common_enums::TransactionStatus::NotVerified => Self::Failed,
            common_enums::TransactionStatus::ChallengeRequired
            | common_enums::TransactionStatus::ChallengeRequiredDecoupledAuthentication
            | common_enums::TransactionStatus::InformationOnly => Self::Pending,
        }
    }
}