use std::vec::IntoIter;

use common_utils::{ext_traits::Encode, types::MinorUnit};
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use hyperswitch_domain_models::{ext_traits::OptionExt, mandates};
use router_env::{
    logger,
    tracing::{self, instrument},
};

use crate::{
    consts,
    core::{
        configs::dimension_state,
        errors::{self, RouterResult, StorageErrorExt},
        payments::{
            self, complete_connector_service,
            flows::{ConstructFlowSpecificData, Feature},
            helpers as payments_helpers, operations,
        },
        routing::helpers as routing_helpers,
        utils as core_utils,
    },
    db::StorageInterface,
    routes::{
        self,
        app::{self, ReqState},
        metrics,
    },
    services,
    types::{self, api, domain, storage, transformers::ForeignFrom},
};

#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "v1")]
pub async fn do_gsm_actions<'a, F, ApiRequest, FData, D>(
    state: &app::SessionState,
    req_state: ReqState,
    payment_data: &mut D,
    mut connector_routing_data: IntoIter<api::ConnectorRoutingData>,
    original_connector_data: &api::ConnectorData,
    mut router_data: types::RouterData<F, FData, types::PaymentsResponseData>,
    platform: &domain::Platform,
    operation: &operations::BoxedOperation<'_, F, ApiRequest, D>,
    customer: &Option<domain::Customer>,
    validate_result: &operations::ValidateResult,
    schedule_time: Option<time::PrimitiveDateTime>,
    frm_suggestion: Option<storage_enums::FrmSuggestion>,
    business_profile: &domain::Profile,
    feature_config: &core_utils::FeatureConfig,
    _dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndProfileId,
) -> RouterResult<types::RouterData<F, FData, types::PaymentsResponseData>>
where
    F: Clone + Send + Sync + std::fmt::Debug + 'static,
    FData: Send + Sync + types::Capturable + Clone + 'static + serde::Serialize,
    payments::PaymentResponse: operations::Operation<F, FData>,
    D: payments::OperationSessionGetters<F>
        + payments::OperationSessionSetters<F>
        + Send
        + Sync
        + Clone,
    D: ConstructFlowSpecificData<F, FData, types::PaymentsResponseData>,
    types::RouterData<F, FData, types::PaymentsResponseData>: Feature<F, FData>,
    dyn api::Connector: services::api::ConnectorIntegration<F, FData, types::PaymentsResponseData>,
{
    let mut retries = None;

    metrics::AUTO_RETRY_ELIGIBLE_REQUEST_COUNT.add(1, &[]);

    let card_network = payment_data.get_payment_attempt().extract_card_network();

    let mut initial_gsm = get_gsm(state, &router_data, card_network.clone()).await?;

    let step_up_possible = initial_gsm
        .as_ref()
        .and_then(|data| data.feature_data.get_retry_feature_data())
        .map(|data| data.is_step_up_possible())
        .unwrap_or(false);

    #[cfg(feature = "v1")]
    let is_no_three_ds_payment = matches!(
        router_data.auth_type,
        storage_enums::AuthenticationType::NoThreeDs
    );

    #[cfg(feature = "v2")]
    let is_no_three_ds_payment = matches!(
        payment_data.get_payment_attempt().authentication_type,
        storage_enums::AuthenticationType::NoThreeDs
    );

    let should_step_up = if step_up_possible && is_no_three_ds_payment {
        is_step_up_enabled_for_merchant_connector(
            state,
            platform.get_processor().get_account().get_id(),
            original_connector_data.connector_name,
        )
        .await
    } else {
        false
    };

    if should_step_up {
        router_data = Box::pin(do_retry(
            &state.clone(),
            req_state.clone(),
            original_connector_data,
            operation,
            customer,
            platform,
            payment_data,
            router_data,
            validate_result,
            schedule_time,
            true,
            frm_suggestion,
            business_profile,
            false, //should_retry_with_pan is not applicable for step-up
            None,
            initial_gsm.clone(),
            feature_config,
        ))
        .await?;
    }
    // Step up is not applicable so proceed with auto retries flow
    else {
        loop {
            // Use initial_gsm for first time alone
            let gsm = match initial_gsm.as_ref() {
                Some(gsm) => Some(gsm.clone()),
                None => get_gsm(state, &router_data, card_network.clone()).await?,
            };

            match get_gsm_decision(gsm.clone()) {
                storage_enums::GsmDecision::Retry => {
                    retries = get_retries(
                        state,
                        retries,
                        platform.get_processor().get_account().get_id(),
                        business_profile,
                    )
                    .await;

                    if retries.is_none() || retries == Some(0) {
                        metrics::AUTO_RETRY_EXHAUSTED_COUNT.add(1, &[]);
                        logger::info!("retries exhausted for auto_retry payment");
                        break;
                    }

                    if connector_routing_data.len() == 0 {
                        logger::info!("connectors exhausted for auto_retry payment");
                        metrics::AUTO_RETRY_EXHAUSTED_COUNT.add(1, &[]);
                        break;
                    }

                    let is_network_token = payment_data
                        .get_payment_method_data()
                        .map(|pmd| pmd.is_network_token_payment_method_data())
                        .unwrap_or(false);

                    let clear_pan_possible = initial_gsm
                        .and_then(|data| data.feature_data.get_retry_feature_data())
                        .map(|data| data.is_clear_pan_possible())
                        .unwrap_or(false);

                    let should_retry_with_pan = is_network_token
                        && clear_pan_possible
                        && business_profile.is_clear_pan_retries_enabled;

                    // Currently we are taking off_session as a source of truth to identify MIT payments.
                    let is_mit_payment = payment_data
                        .get_payment_intent()
                        .off_session
                        .unwrap_or(false);

                    let (connector, routing_decision) = if is_mit_payment {
                        let connector_routing_data =
                            super::get_connector_data(&mut connector_routing_data)?;
                        let payment_method_info = payment_data
                            .get_payment_method_info()
                            .get_required_value("payment_method_info")?
                            .clone();
                        let mandate_reference_id = payments::get_mandate_reference_id(
                            connector_routing_data.action_type.clone(),
                            connector_routing_data.clone(),
                            payment_data,
                            &payment_method_info,
                        )?;
                        payment_data.set_mandate_id(mandates::MandateIds {
                            mandate_id: None,
                            mandate_reference_id, //mandate_ref_id
                        });
                        (connector_routing_data.connector_data, None)
                    } else if should_retry_with_pan {
                        // If should_retry_with_pan is true, it indicates that we are retrying with PAN using the same connector.
                        (original_connector_data.clone(), None)
                    } else {
                        let connector_routing_data =
                            super::get_connector_data(&mut connector_routing_data)?;
                        let routing_decision = connector_routing_data.network.map(|card_network| {
                            routing_helpers::RoutingDecisionData::get_debit_routing_decision_data(
                                card_network,
                                None,
                            )
                        });

                        (connector_routing_data.connector_data, routing_decision)
                    };

                    router_data = Box::pin(do_retry(
                        &state.clone(),
                        req_state.clone(),
                        &connector,
                        operation,
                        customer,
                        platform,
                        payment_data,
                        router_data,
                        validate_result,
                        schedule_time,
                        //this is an auto retry payment, but not step-up
                        false,
                        frm_suggestion,
                        business_profile,
                        should_retry_with_pan,
                        routing_decision,
                        gsm.clone(),
                        feature_config,
                    ))
                    .await?;

                    retries = retries.map(|i| i - 1);
                }
                storage_enums::GsmDecision::DoDefault => break,
            }
            initial_gsm = None;
        }
    }
    Ok(router_data)
}

#[instrument(skip_all)]
pub async fn is_step_up_enabled_for_merchant_connector(
    state: &app::SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    connector_name: types::Connector,
) -> bool {
    let key = merchant_id.get_step_up_enabled_key();
    let db = &*state.store;
    db.find_config_by_key_unwrap_or(key.as_str(), Some("[]".to_string()))
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .and_then(|step_up_config| {
            serde_json::from_str::<Vec<types::Connector>>(&step_up_config.config)
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Step-up config parsing failed")
        })
        .map_err(|err| {
            logger::error!(step_up_config_error=?err);
        })
        .ok()
        .map(|connectors_enabled| connectors_enabled.contains(&connector_name))
        .unwrap_or(false)
}

#[cfg(feature = "v1")]
pub async fn get_merchant_max_auto_retries_enabled(
    db: &dyn StorageInterface,
    merchant_id: &common_utils::id_type::MerchantId,
) -> Option<i32> {
    let key = merchant_id.get_max_auto_retries_enabled();

    db.find_config_by_key(key.as_str())
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .and_then(|retries_config| {
            retries_config
                .config
                .parse::<i32>()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Retries config parsing failed")
        })
        .map_err(|err| {
            logger::error!(retries_error=?err);
            None::<i32>
        })
        .ok()
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn get_retries(
    state: &app::SessionState,
    retries: Option<i32>,
    merchant_id: &common_utils::id_type::MerchantId,
    profile: &domain::Profile,
) -> Option<i32> {
    match retries {
        Some(retries) => Some(retries),
        None => get_merchant_max_auto_retries_enabled(state.store.as_ref(), merchant_id)
            .await
            .or(profile.max_auto_retries_enabled.map(i32::from)),
    }
}

#[instrument(skip_all)]
pub async fn get_gsm<F, FData>(
    state: &app::SessionState,
    router_data: &types::RouterData<F, FData, types::PaymentsResponseData>,
    card_network: Option<common_enums::CardNetwork>,
) -> RouterResult<Option<hyperswitch_domain_models::gsm::GatewayStatusMap>> {
    let error_response = router_data.response.as_ref().err();
    let subflow = get_flow_name::<F>()?;
    let error_code = error_response.map(|err| err.code.to_owned());
    let err_message = error_response.map(|err| err.message.to_owned());
    let issuer_error_code = error_response.and_then(|err| err.network_decline_code.clone());
    let connector_str = router_data.connector.to_string();

    Ok(payments::helpers::get_gsm_record(
        state,
        connector_str,
        consts::PAYMENT_FLOW_STR,
        &subflow,
        error_code,
        err_message,
        issuer_error_code,
        card_network,
    )
    .await)
}

#[instrument(skip_all)]
pub fn get_gsm_decision(
    option_gsm: Option<hyperswitch_domain_models::gsm::GatewayStatusMap>,
) -> storage_enums::GsmDecision {
    let option_gsm_decision = option_gsm
        .as_ref()
        .map(|gsm| gsm.feature_data.get_decision());

    if option_gsm_decision.is_some() {
        metrics::AUTO_RETRY_GSM_MATCH_COUNT.add(1, &[]);
    }
    option_gsm_decision.unwrap_or_default()
}

#[inline]
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())
}

#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn do_retry<'a, F, ApiRequest, FData, D>(
    state: &'a routes::SessionState,
    req_state: ReqState,
    connector: &'a api::ConnectorData,
    operation: &'a operations::BoxedOperation<'a, F, ApiRequest, D>,
    customer: &'a Option<domain::Customer>,
    platform: &domain::Platform,
    payment_data: &'a mut D,
    router_data: types::RouterData<F, FData, types::PaymentsResponseData>,
    validate_result: &operations::ValidateResult,
    schedule_time: Option<time::PrimitiveDateTime>,
    is_step_up: bool,
    frm_suggestion: Option<storage_enums::FrmSuggestion>,
    business_profile: &domain::Profile,
    should_retry_with_pan: bool,
    routing_decision: Option<routing_helpers::RoutingDecisionData>,
    initial_gsm: Option<hyperswitch_domain_models::gsm::GatewayStatusMap>,
    feature_config: &core_utils::FeatureConfig,
) -> RouterResult<types::RouterData<F, FData, types::PaymentsResponseData>>
where
    F: Clone + Send + Sync + std::fmt::Debug + 'static,
    FData: Send + Sync + types::Capturable + Clone + 'static + serde::Serialize,
    payments::PaymentResponse: operations::Operation<F, FData>,
    D: payments::OperationSessionGetters<F>
        + payments::OperationSessionSetters<F>
        + Send
        + Sync
        + Clone,
    D: ConstructFlowSpecificData<F, FData, types::PaymentsResponseData>,
    types::RouterData<F, FData, types::PaymentsResponseData>: Feature<F, FData>,
    dyn api::Connector: services::api::ConnectorIntegration<F, FData, types::PaymentsResponseData>,
{
    let dimensions = dimension_state::Dimensions::new()
        .with_processor_merchant_id(platform.get_processor().get_processor_merchant_id())
        .with_provider_merchant_id(platform.get_provider().get_provider_merchant_id());

    metrics::AUTO_RETRY_PAYMENT_COUNT.add(1, &[]);

    modify_trackers(
        state,
        connector.connector_name.to_string(),
        payment_data,
        platform.get_processor().get_key_store(),
        platform.get_processor().get_account().storage_scheme,
        router_data,
        is_step_up,
        initial_gsm,
    )
    .await?;

    let (merchant_connector_account, router_data, tokenization_action) =
        Box::pin(payments::call_connector_service_prerequisites(
            state,
            platform,
            connector.clone(),
            operation,
            payment_data,
            validate_result,
            business_profile,
            should_retry_with_pan,
            routing_decision,
            feature_config,
        ))
        .await?;

    let connector_customer_map = customer
        .as_ref()
        .and_then(|customer| customer.connector_customer.as_ref());

    let (updated_customer, call_connector_service_response, updated_state) =
        payments::decide_unified_connector_service_call(
            state,
            platform.get_processor(),
            platform.get_initiator(),
            connector.clone(),
            operation,
            payment_data,
            connector_customer_map,
            payments::CallConnectorAction::Trigger,
            None,
            validate_result,
            schedule_time,
            hyperswitch_domain_models::payments::HeaderPayload::default(),
            business_profile,
            true,
            merchant_connector_account.clone(),
            router_data,
            tokenization_action,
        )
        .await?;
    // Update customer at provider level after connector operations complete
    operation
        .to_domain()?
        .update_customer(
            &updated_state,
            platform.get_provider(),
            customer.clone(),
            updated_customer,
        )
        .await?;

    let (router_data, _mca) = complete_connector_service(
        &updated_state,
        platform.get_processor(),
        operation,
        payment_data,
        business_profile,
        None,
        connector,
        payments::CallConnectorAction::Trigger,
        merchant_connector_account,
        req_state.clone(),
        hyperswitch_domain_models::payments::HeaderPayload::default(),
        frm_suggestion,
        call_connector_service_response,
        &dimensions,
    )
    .await?;
    Ok(router_data)
}

#[cfg(feature = "v2")]
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn modify_trackers<F, FData, D>(
    state: &routes::SessionState,
    connector: String,
    payment_data: &mut D,
    key_store: &domain::MerchantKeyStore,
    storage_scheme: storage_enums::MerchantStorageScheme,
    router_data: types::RouterData<F, FData, types::PaymentsResponseData>,
    is_step_up: bool,
    initial_gsm: Option<hyperswitch_domain_models::gsm::GatewayStatusMap>,
) -> RouterResult<()>
where
    F: Clone + Send,
    FData: Send,
    D: payments::OperationSessionGetters<F> + payments::OperationSessionSetters<F> + Send + Sync,
{
    todo!()
}

#[cfg(feature = "v1")]
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn modify_trackers<F, FData, D>(
    state: &routes::SessionState,
    connector: String,
    payment_data: &mut D,
    key_store: &domain::MerchantKeyStore,
    storage_scheme: storage_enums::MerchantStorageScheme,
    router_data: types::RouterData<F, FData, types::PaymentsResponseData>,
    is_step_up: bool,
    initial_gsm: Option<hyperswitch_domain_models::gsm::GatewayStatusMap>,
) -> RouterResult<()>
where
    F: Clone + Send,
    FData: Send + types::Capturable,
    D: payments::OperationSessionGetters<F> + payments::OperationSessionSetters<F> + Send + Sync,
{
    let new_attempt_count = payment_data.get_payment_intent().attempt_count + 1;
    let new_payment_attempt = make_new_auto_retry_payment_attempt(
        connector,
        payment_data.get_payment_attempt().clone(),
        new_attempt_count,
        is_step_up,
        payment_data.get_payment_intent().setup_future_usage,
    );

    let db = &*state.store;
    let additional_payment_method_data_intermediate =
        payments::helpers::update_additional_payment_data_with_connector_response_pm_data(
            payment_data
                .get_payment_attempt()
                .payment_method_data
                .clone(),
            router_data
                .connector_response
                .clone()
                .and_then(|connector_response| connector_response.additional_payment_method_data),
        )?;
    let key_manager_state = &state.into();

    // If the additional PM data is sensitive, encrypt it and populate encrypted_payment_method_data; otherwise populate additional_payment_method_data
    let (additional_payment_method_data, encrypted_payment_method_data) =
        payments::helpers::get_payment_method_data_and_encrypted_payment_method_data(
            payment_data.get_payment_attempt(),
            key_manager_state,
            key_store,
            additional_payment_method_data_intermediate,
        )
        .await?;

    let debit_routing_savings = payment_data.get_payment_method_data().and_then(|data| {
        payments::helpers::get_debit_routing_savings_amount(
            data,
            payment_data.get_payment_attempt(),
        )
    });

    match router_data.response {
        Ok(types::PaymentsResponseData::TransactionResponse {
            resource_id,
            connector_metadata,
            redirection_data,
            charges,
            ..
        }) => {
            let encoded_data = payment_data.get_payment_attempt().encoded_data.clone();

            let authentication_data = (*redirection_data)
                .as_ref()
                .map(Encode::encode_to_value)
                .transpose()
                .change_context(errors::ApiErrorResponse::InternalServerError)
                .attach_printable("Could not parse the connector response")?;

            let payment_attempt_update = storage::PaymentAttemptUpdate::ResponseUpdate {
                status: router_data.status,
                connector: None,
                connector_transaction_id: match resource_id {
                    types::ResponseId::NoResponseId => None,
                    types::ResponseId::ConnectorTransactionId(id)
                    | types::ResponseId::EncodedData(id) => Some(id),
                },
                connector_response_reference_id: payment_data
                    .get_payment_attempt()
                    .connector_response_reference_id
                    .clone(),
                authentication_type: None,
                payment_method_id: payment_data.get_payment_attempt().payment_method_id.clone(),
                mandate_id: payment_data
                    .get_mandate_id()
                    .and_then(|mandate| mandate.mandate_id.clone()),
                connector_metadata,
                payment_token: None,
                error_code: None,
                error_message: None,
                error_reason: None,
                amount_capturable: if router_data.status.is_terminal_status() {
                    Some(MinorUnit::new(0))
                } else {
                    None
                },
                updated_by: storage_scheme.to_string(),
                authentication_data,
                encoded_data,
                unified_code: None,
                unified_message: None,
                standardised_code: None,
                description: None,
                user_guidance_message: None,
                capture_before: None,
                extended_authorization_applied: None,
                extended_authorization_last_applied_at: None,
                payment_method_data: additional_payment_method_data,
                encrypted_payment_method_data,
                connector_mandate_detail: Box::new(None),
                charges,
                setup_future_usage_applied: None,
                debit_routing_savings,
                network_transaction_id: payment_data
                    .get_payment_attempt()
                    .network_transaction_id
                    .clone(),
                network_transaction_link_id: payment_data
                    .get_payment_attempt()
                    .network_transaction_link_id
                    .clone(),
                is_overcapture_enabled: None,
                authorized_amount: router_data.authorized_amount,
                tokenization: None,
                issuer_error_code: None,
                issuer_error_message: None,
                network_details: None,
                network_error_message: None,
                advice_message: None,
                recommended_action: None,
                card_network: payment_data.get_payment_attempt().extract_card_network(),
                sender_payment_instrument_id: payment_data
                    .get_payment_attempt()
                    .sender_payment_instrument_id
                    .clone(),
            };

            #[cfg(feature = "v1")]
            db.update_payment_attempt_with_attempt_id(
                payment_data.get_payment_attempt().clone(),
                payment_attempt_update,
                storage_scheme,
                key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

            #[cfg(feature = "v2")]
            db.update_payment_attempt_with_attempt_id(
                key_manager_state,
                key_store,
                payment_data.get_payment_attempt().clone(),
                payment_attempt_update,
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
        }
        Ok(_) => {
            logger::error!("unexpected response: this response was not expected in Retry flow");
            return Ok(());
        }
        Err(ref error_response) => {
            let card_network = payment_data.get_payment_attempt().extract_card_network();
            let auth_update = if Some(router_data.auth_type)
                != payment_data.get_payment_attempt().authentication_type
            {
                Some(router_data.auth_type)
            } else {
                None
            };

            // For MIT transactions, lookup recommended action and description from merchant_advice_codes config
            let merchant_advice = payments_helpers::get_merchant_advice_code_config(
                &state.conf.merchant_advice_codes,
                payment_data.get_payment_intent().off_session,
                card_network.clone(),
                error_response.network_advice_code.clone(),
            );

            let payment_attempt_update = storage::PaymentAttemptUpdate::ErrorUpdate {
                connector: None,
                error_code: Some(Some(error_response.code.clone())),
                error_message: Some(Some(error_response.message.clone())),
                status: storage_enums::AttemptStatus::Failure,
                error_reason: Some(error_response.reason.clone()),
                amount_capturable: Some(MinorUnit::new(0)),
                updated_by: storage_scheme.to_string(),
                unified_code: initial_gsm.clone().map(|gsm| gsm.unified_code),
                unified_message: initial_gsm.clone().map(|gsm| gsm.unified_message),
                standardised_code: Some(initial_gsm.as_ref().and_then(|gsm| gsm.standardised_code)),
                description: initial_gsm.clone().map(|gsm| gsm.description),
                user_guidance_message: initial_gsm.clone().map(|gsm| gsm.user_guidance_message),
                connector_transaction_id: error_response.connector_transaction_id.clone(),
                connector_response_reference_id: error_response
                    .connector_response_reference_id
                    .clone(),
                payment_method_data: additional_payment_method_data,
                encrypted_payment_method_data,
                authentication_type: auth_update,
                issuer_error_code: Some(error_response.network_decline_code.clone()),
                issuer_error_message: Some(error_response.network_error_message.clone()),
                network_details: Some(Some(ForeignFrom::foreign_from(error_response))),
                network_error_message: Some(error_response.network_error_message.clone()),
                advice_message: Some(merchant_advice.map(|m| m.description.clone())),
                recommended_action: Some(merchant_advice.map(|m| m.recommended_action)),
                card_network: payment_data.get_payment_attempt().extract_card_network(),
            };

            #[cfg(feature = "v1")]
            db.update_payment_attempt_with_attempt_id(
                payment_data.get_payment_attempt().clone(),
                payment_attempt_update,
                storage_scheme,
                key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

            #[cfg(feature = "v2")]
            db.update_payment_attempt_with_attempt_id(
                key_manager_state,
                key_store,
                payment_data.get_payment_attempt().clone(),
                payment_attempt_update,
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
        }
    }

    #[cfg(feature = "v1")]
    let payment_attempt = db
        .insert_payment_attempt(new_payment_attempt, storage_scheme, key_store)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error inserting payment attempt")?;

    #[cfg(feature = "v2")]
    let payment_attempt = db
        .insert_payment_attempt(key_store, new_payment_attempt, storage_scheme)
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Error inserting payment attempt")?;

    // update payment_attempt, connector_response and payment_intent in payment_data
    payment_data.set_payment_attempt(payment_attempt);

    let payment_intent = db
        .update_payment_intent(
            payment_data.get_payment_intent().clone(),
            storage::PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate {
                active_attempt_id: payment_data.get_payment_attempt().get_id().to_owned(),
                attempt_count: new_attempt_count,
                updated_by: storage_scheme.to_string(),
            },
            key_store,
            storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

    payment_data.set_payment_intent(payment_intent);

    Ok(())
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub fn make_new_auto_retry_payment_attempt(
    connector: String,
    old_payment_attempt: storage::PaymentAttempt,
    new_attempt_count: i16,
    is_step_up: bool,
    setup_future_usage_intent: Option<storage_enums::FutureUsage>,
) -> storage::PaymentAttempt {
    let created_at @ modified_at @ last_synced = common_utils::date_time::now();
    storage::PaymentAttempt {
        connector: Some(connector),
        attempt_id: old_payment_attempt
            .payment_id
            .get_attempt_id(new_attempt_count),
        payment_id: old_payment_attempt.payment_id,
        merchant_id: old_payment_attempt.merchant_id,
        status: old_payment_attempt.status,
        currency: old_payment_attempt.currency,
        save_to_locker: old_payment_attempt.save_to_locker,
        offer_amount: old_payment_attempt.offer_amount,
        payment_method_id: old_payment_attempt.payment_method_id,
        payment_method: old_payment_attempt.payment_method,
        payment_method_type: old_payment_attempt.payment_method_type,
        capture_method: old_payment_attempt.capture_method,
        capture_on: old_payment_attempt.capture_on,
        confirm: old_payment_attempt.confirm,
        authentication_type: if is_step_up {
            Some(storage_enums::AuthenticationType::ThreeDs)
        } else {
            old_payment_attempt.authentication_type
        },
        amount_to_capture: old_payment_attempt.amount_to_capture,
        mandate_id: old_payment_attempt.mandate_id,
        browser_info: old_payment_attempt.browser_info,
        payment_token: old_payment_attempt.payment_token,
        client_source: old_payment_attempt.client_source,
        client_version: old_payment_attempt.client_version,
        created_at,
        modified_at,
        last_synced: Some(last_synced),
        profile_id: old_payment_attempt.profile_id,
        organization_id: old_payment_attempt.organization_id,
        net_amount: old_payment_attempt.net_amount,
        error_message: Default::default(),
        cancellation_reason: Default::default(),
        error_code: Default::default(),
        connector_metadata: Default::default(),
        payment_experience: Default::default(),
        payment_method_data: Default::default(),
        business_sub_label: Default::default(),
        straight_through_algorithm: Default::default(),
        preprocessing_step_id: Default::default(),
        mandate_details: Default::default(),
        error_reason: Default::default(),
        connector_response_reference_id: Default::default(),
        multiple_capture_count: Default::default(),
        amount_capturable: Default::default(),
        updated_by: Default::default(),
        authentication_data: Default::default(),
        encoded_data: Default::default(),
        merchant_connector_id: Default::default(),
        unified_code: Default::default(),
        unified_message: Default::default(),
        external_three_ds_authentication_attempted: Default::default(),
        external_threeds_authentication_type: Default::default(),
        authentication_connector: Default::default(),
        authentication_id: Default::default(),
        mandate_data: Default::default(),
        payment_method_billing_address_id: Default::default(),
        fingerprint_id: Default::default(),
        customer_acceptance: Default::default(),
        connector_mandate_detail: Default::default(),
        request_extended_authorization: Default::default(),
        extended_authorization_applied: Default::default(),
        extended_authorization_last_applied_at: Default::default(),
        capture_before: Default::default(),
        card_discovery: old_payment_attempt.card_discovery,
        processor_merchant_id: old_payment_attempt.processor_merchant_id,
        created_by: old_payment_attempt.created_by,
        setup_future_usage_applied: setup_future_usage_intent, // setup future usage is picked from intent for new payment attempt
        routing_approach: old_payment_attempt.routing_approach,
        connector_request_reference_id: Default::default(),
        network_transaction_id: old_payment_attempt.network_transaction_id,
        network_transaction_link_id: old_payment_attempt.network_transaction_link_id,
        network_details: Default::default(),
        is_stored_credential: old_payment_attempt.is_stored_credential,
        authorized_amount: old_payment_attempt.authorized_amount,
        tokenization: Default::default(),
        encrypted_payment_method_data: Default::default(),
        connector_transaction_id: Default::default(),
        charge_id: Default::default(),
        charges: Default::default(),
        issuer_error_code: Default::default(),
        issuer_error_message: Default::default(),
        debit_routing_savings: Default::default(),
        is_overcapture_enabled: Default::default(),
        error_details: Default::default(),
        retry_type: Some(storage_enums::RetryType::AutoRetry),
        installment_data: Default::default(),
        external_surcharge_details: Default::default(),
        sender_payment_instrument_id: Default::default(),
    }
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub fn make_new_payment_attempt(
    _connector: String,
    _old_payment_attempt: storage::PaymentAttempt,
    _new_attempt_count: i16,
    _is_step_up: bool,
) -> storage::PaymentAttempt {
    todo!()
}

#[cfg(feature = "v1")]
pub async fn config_should_call_gsm(
    state: &app::SessionState,
    dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndProfileId,
    profile: &domain::Profile,
    customer_id: Option<&common_utils::id_type::CustomerId>,
) -> bool {
    let merchant_config_gsm = dimensions
        .get_should_call_gsm(
            state.store.as_ref(),
            state.superposition_service.as_ref(),
            customer_id,
        )
        .await;
    let profile_config_gsm = profile.is_auto_retries_enabled;
    merchant_config_gsm || profile_config_gsm
}

pub trait GsmValidation<F: Send + Clone + Sync, FData: Send + Sync, Resp> {
    // TODO : move this function to appropriate place later.
    fn should_call_gsm(&self) -> bool;
}

impl<F: Send + Clone + Sync, FData: Send + Sync>
    GsmValidation<F, FData, types::PaymentsResponseData>
    for types::RouterData<F, FData, types::PaymentsResponseData>
{
    #[inline(always)]
    fn should_call_gsm(&self) -> bool {
        if self.response.is_err() {
            true
        } else {
            match self.status {
                storage_enums::AttemptStatus::Started
                | storage_enums::AttemptStatus::AuthenticationPending
                | storage_enums::AttemptStatus::AuthenticationSuccessful
                | storage_enums::AttemptStatus::Authorized
                | storage_enums::AttemptStatus::Charged
                | storage_enums::AttemptStatus::Authorizing
                | storage_enums::AttemptStatus::CodInitiated
                | storage_enums::AttemptStatus::Voided
                | storage_enums::AttemptStatus::VoidedPostCharge
                | storage_enums::AttemptStatus::VoidInitiated
                | storage_enums::AttemptStatus::CaptureInitiated
                | storage_enums::AttemptStatus::RouterDeclined
                | storage_enums::AttemptStatus::VoidFailed
                | storage_enums::AttemptStatus::AutoRefunded
                | storage_enums::AttemptStatus::CaptureFailed
                | storage_enums::AttemptStatus::PartialCharged
                | storage_enums::AttemptStatus::PartialChargedAndChargeable
                | storage_enums::AttemptStatus::Pending
                | storage_enums::AttemptStatus::PaymentMethodAwaited
                | storage_enums::AttemptStatus::ConfirmationAwaited
                | storage_enums::AttemptStatus::Unresolved
                | storage_enums::AttemptStatus::DeviceDataCollectionPending
                | storage_enums::AttemptStatus::IntegrityFailure
                | storage_enums::AttemptStatus::Expired
                | storage_enums::AttemptStatus::PartiallyAuthorized
                | storage_enums::AttemptStatus::CaptureReview => false,

                storage_enums::AttemptStatus::AuthenticationFailed
                | storage_enums::AttemptStatus::AuthorizationFailed
                | storage_enums::AttemptStatus::Failure => true,
            }
        }
    }
}
