use std::marker::PhantomData;

use api_models::{
    customers::CustomerDocumentDetails, enums::FrmSuggestion, mandates::RecurringDetails,
    payments::RequestSurchargeDetails,
};
use async_trait::async_trait;
use common_utils::{
    ext_traits::{AsyncExt, Encode, ValueExt},
    pii::Email,
    types::MinorUnit,
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    mandates::{self, MandateTransactionType},
    payments::payment_intent::{CustomerData, PaymentIntentUpdateFields},
};
use hyperswitch_interfaces::api::ConnectorSpecifications;
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};

use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
    core::{
        configs::dimension_state,
        errors::{self, CustomResult, RouterResult, StorageErrorExt},
        mandate::helpers as m_helpers,
        payments::{
            self, client_session::ClientSessionManager, helpers, operations, CustomerDetails,
            PaymentAddress, PaymentData, PaymentDataUpdateRequestFields,
        },
        utils::{self as core_utils},
    },
    events::audit_events::{AuditEvent, AuditEventType},
    routes::{app::ReqState, SessionState},
    services,
    types::{
        self,
        api::{self, ConnectorCallType, PaymentIdTypeExt},
        domain,
        storage::{self, enums as storage_enums, payment_attempt::PaymentAttemptExt},
        transformers::ForeignTryFrom,
    },
    utils::OptionExt,
};

#[derive(Debug, Clone, Copy, PaymentOperation)]
#[operation(operations = "all", flow = "update_post_confirm")]
pub struct PaymentUpdate;

type PaymentUpdateOperation<'a, F> = BoxedOperation<'a, F, api::PaymentsRequest, PaymentData<F>>;

#[async_trait]
impl<F: Send + Clone + Sync> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for PaymentUpdate {
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_id: &api::PaymentIdType,
        request: &api::PaymentsRequest,
        platform: &domain::Platform,
        auth_flow: services::AuthFlow,
        _flow_kind: operations::PaymentFlowKind,
        _header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
        _payment_method_fetch_data: operations::PaymentMethodFetchData,
        _dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantId,
        _payment_pre_fetched_info: Option<operations::PaymentPreFetchedInformation>,
    ) -> RouterResult<operations::GetTrackerResponse<'a, F, api::PaymentsRequest, PaymentData<F>>>
    {
        let (mut payment_intent, mut payment_attempt, currency): (_, _, storage_enums::Currency);

        let payment_id = payment_id
            .get_payment_intent_id()
            .change_context(errors::ApiErrorResponse::PaymentNotFound)?;
        let processor_merchant_id = platform.get_processor().get_account().get_id();
        let storage_scheme = platform.get_processor().get_account().storage_scheme;

        let db = &*state.store;
        helpers::allow_payment_update_enabled_for_client_auth(
            processor_merchant_id,
            state,
            auth_flow,
        )
        .await?;
        payment_intent = db
            .find_payment_intent_by_payment_id_processor_merchant_id(
                &payment_id,
                processor_merchant_id,
                platform.get_processor().get_key_store(),
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        // TODO (#7195): Add platform merchant account validation once publishable key auth is solved

        if let Some(order_details) = &request.order_details {
            helpers::validate_order_details_amount(
                order_details.to_owned(),
                payment_intent.amount,
                false,
            )?;
        }

        payment_intent.setup_future_usage = request
            .setup_future_usage
            .or(payment_intent.setup_future_usage);

        helpers::validate_customer_access(&payment_intent, auth_flow, request)?;

        helpers::validate_card_data(
            request
                .payment_method_data
                .as_ref()
                .and_then(|pmd| pmd.payment_method_data.clone()),
        )?;

        helpers::validate_payment_status_against_allowed_statuses(
            payment_intent.status,
            &[
                storage_enums::IntentStatus::RequiresPaymentMethod,
                storage_enums::IntentStatus::RequiresConfirmation,
                storage_enums::IntentStatus::RequiresCustomerAction,
            ],
            "update",
        )?;
        helpers::authenticate_client_secret(request.client_secret.as_ref(), &payment_intent)?;

        payment_intent.order_details = request
            .get_order_details_as_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to convert order details to value")?
            .or(payment_intent.order_details);

        payment_attempt = db
            .find_payment_attempt_by_payment_id_processor_merchant_id_attempt_id(
                &payment_intent.payment_id,
                processor_merchant_id,
                payment_intent.active_attempt.get_id().as_str(),
                storage_scheme,
                platform.get_processor().get_key_store(),
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        let customer_acceptance = request.customer_acceptance.clone();
        let recurring_details = request.recurring_details.clone();
        let mandate_type = m_helpers::get_mandate_type(
            request.mandate_data.clone(),
            request.off_session,
            payment_intent.setup_future_usage,
            request.customer_acceptance.clone(),
            request.payment_token.clone(),
            payment_attempt.payment_method.or(request.payment_method),
        )
        .change_context(errors::ApiErrorResponse::MandateValidationFailed {
            reason: "Expected one out of recurring_details and mandate_data but got both".into(),
        })?;

        let m_helpers::MandateGenericData {
            token,
            payment_method,
            payment_method_type,
            mandate_data,
            recurring_mandate_payment_data,
            mandate_connector,
            payment_method_info,
        } = Box::pin(helpers::get_token_pm_type_mandate_details(
            state,
            request,
            mandate_type.to_owned(),
            platform,
            None,
            payment_intent.customer_id.as_ref(),
            None,
        ))
        .await?;
        helpers::validate_amount_to_capture_and_capture_method(Some(&payment_attempt), request)?;

        helpers::validate_request_amount_and_amount_to_capture(
            request.amount,
            request.amount_to_capture,
            request
                .surcharge_details
                .or(payment_attempt.get_surcharge_details()),
        )
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "amount_to_capture".to_string(),
            expected_format: "amount_to_capture lesser than or equal to amount".to_string(),
        })?;

        currency = request
            .currency
            .or(payment_attempt.currency)
            .get_required_value("currency")?;

        payment_attempt.payment_method = payment_method.or(payment_attempt.payment_method);
        payment_attempt.payment_method_type =
            payment_method_type.or(payment_attempt.payment_method_type);
        let customer_details =
            helpers::get_customer_details_from_request_or_pm_table(request, None, None)?;

        payment_intent.customer_details = helpers::merge_request_and_intent_customer_data(
            state,
            payment_intent.customer_details,
            &customer_details,
            platform.get_processor(),
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Unable to store customer data in payment intent")?;

        let amount = request
            .amount
            .unwrap_or_else(|| payment_attempt.net_amount.get_order_amount().into());

        if request.confirm.unwrap_or(false) {
            helpers::validate_customer_id_mandatory_cases(
                request.setup_future_usage.is_some(),
                payment_intent
                    .customer_id
                    .as_ref()
                    .or(customer_details.customer_id.as_ref()),
            )?;
        }

        let shipping_address = helpers::create_or_update_address_for_payment_by_request(
            state,
            request.shipping.as_ref(),
            payment_intent.shipping_address_id.as_deref(),
            processor_merchant_id,
            payment_intent
                .customer_id
                .as_ref()
                .or(customer_details.customer_id.as_ref()),
            platform.get_processor().get_key_store(),
            &payment_intent.payment_id,
            platform.get_processor().get_account().storage_scheme,
        )
        .await?;
        let billing_address = helpers::create_or_update_address_for_payment_by_request(
            state,
            request.billing.as_ref(),
            payment_intent.billing_address_id.as_deref(),
            processor_merchant_id,
            payment_intent
                .customer_id
                .as_ref()
                .or(customer_details.customer_id.as_ref()),
            platform.get_processor().get_key_store(),
            &payment_intent.payment_id,
            platform.get_processor().get_account().storage_scheme,
        )
        .await?;

        let payment_method_billing = helpers::create_or_update_address_for_payment_by_request(
            state,
            request
                .payment_method_data
                .as_ref()
                .and_then(|pmd| pmd.billing.as_ref()),
            payment_attempt.payment_method_billing_address_id.as_deref(),
            processor_merchant_id,
            payment_intent
                .customer_id
                .as_ref()
                .or(customer_details.customer_id.as_ref()),
            platform.get_processor().get_key_store(),
            &payment_intent.payment_id,
            platform.get_processor().get_account().storage_scheme,
        )
        .await?;

        payment_intent.shipping_address_id = shipping_address.clone().map(|x| x.address_id);
        payment_intent.billing_address_id = billing_address.clone().map(|x| x.address_id);
        payment_attempt.payment_method_billing_address_id = payment_method_billing
            .as_ref()
            .map(|payment_method_billing| payment_method_billing.address_id.clone());

        payment_intent.allowed_payment_method_types = request
            .get_allowed_payment_method_types_as_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error converting allowed_payment_types to Value")?
            .or(payment_intent.allowed_payment_method_types);

        payment_intent.connector_metadata = request
            .get_connector_metadata_as_value()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error converting connector_metadata to Value")?
            .or(payment_intent.connector_metadata);

        payment_intent.frm_metadata = request.frm_metadata.clone().or(payment_intent.frm_metadata);
        payment_intent.psd2_sca_exemption_type = request
            .psd2_sca_exemption_type
            .or(payment_intent.psd2_sca_exemption_type);
        Self::populate_payment_intent_with_request(&mut payment_intent, request);

        let token = token.or_else(|| payment_attempt.payment_token.clone());

        if request.confirm.unwrap_or(false) {
            helpers::validate_pm_or_token_given(
                &request.payment_method,
                &request
                    .payment_method_data
                    .as_ref()
                    .and_then(|pmd| pmd.payment_method_data.clone()),
                &request.payment_method_type,
                &mandate_type,
                &token,
                &request.ctp_service_details,
            )?;
        }

        let token_data = if let Some(token) = token.clone() {
            Some(helpers::retrieve_payment_token_data(state, token, payment_method).await?)
        } else {
            None
        };

        let mandate_id = request
            .mandate_id
            .as_ref()
            .or_else(|| {
                request
                    .recurring_details
                    .as_ref()
                    .and_then(|recurring_details| match recurring_details {
                        RecurringDetails::MandateId(id) => Some(id),
                        _ => None,
                    })
            })
            .async_and_then(|mandate_id| async {
                let mandate = db
                    .find_mandate_by_merchant_id_mandate_id(
                        processor_merchant_id,
                        mandate_id,
                        platform.get_processor().get_account().storage_scheme,
                    )
                    .await
                    .change_context(errors::ApiErrorResponse::MandateNotFound);
                Some(mandate.and_then(|mandate_obj| {
                    match (
                        mandate_obj.network_transaction_id,
                        mandate_obj.connector_mandate_ids,
                    ) {
                        (Some(network_tx_id), _) => Ok(mandates::MandateIds {
                            mandate_id: Some(mandate_obj.mandate_id),
                            mandate_reference_id: Some(
                                mandates::MandateReferenceId::NetworkMandateId(
                                    mandates::NetworkMandateIdRef {
                                        network_transaction_id: network_tx_id,
                                        transaction_link_id: mandate_obj
                                            .network_transaction_link_id,
                                    },
                                ),
                            ),
                        }),
                        (_, Some(connector_mandate_id)) => connector_mandate_id
                            .parse_value("ConnectorMandateId")
                            .change_context(errors::ApiErrorResponse::MandateNotFound)
                            .map(|connector_id: mandates::ConnectorMandateReferenceId| {
                                mandates::MandateIds {
                                    mandate_id: Some(mandate_obj.mandate_id),
                                    mandate_reference_id: Some(
                                        mandates::MandateReferenceId::ConnectorMandateId(
                                            mandates::ConnectorMandateReferenceId::new(
                                                connector_id.get_connector_mandate_id(), // connector_mandate_id
                                                connector_id.get_payment_method_id(), // payment_method_id
                                                None, // update_history
                                                connector_id.get_mandate_metadata(), // mandate_metadata
                                                connector_id
                                                    .get_connector_mandate_request_reference_id(),
                                                None, // connector_mandate_request_reference_id
                                            ),
                                        ),
                                    ),
                                }
                            }),
                        (_, _) => Ok(mandates::MandateIds {
                            mandate_id: Some(mandate_obj.mandate_id),
                            mandate_reference_id: None,
                        }),
                    }
                }))
            })
            .await
            .transpose()?;
        let (next_operation, amount): (PaymentUpdateOperation<'a, F>, _) =
            if request.confirm.unwrap_or(false) {
                let amount = {
                    let amount = request
                        .amount
                        .map(Into::into)
                        .unwrap_or(payment_attempt.net_amount.get_order_amount());
                    payment_attempt.net_amount.set_order_amount(amount);
                    payment_intent.amount = amount;
                    let surcharge_amount = request
                        .surcharge_details
                        .as_ref()
                        .map(RequestSurchargeDetails::get_total_surcharge_amount)
                        .or(payment_attempt.get_total_surcharge_amount());
                    amount + surcharge_amount.unwrap_or_default()
                };
                (Box::new(operations::PaymentConfirm), amount.into())
            } else {
                (Box::new(self), amount)
            };

        payment_intent.request_external_three_ds_authentication = request
            .request_external_three_ds_authentication
            .or(payment_intent.request_external_three_ds_authentication);

        Self::populate_payment_attempt_with_request(&mut payment_attempt, request);

        let creds_identifier = request
            .merchant_connector_details
            .as_ref()
            .map(|mcd| mcd.creds_identifier.to_owned());
        request
            .merchant_connector_details
            .to_owned()
            .async_map(|mcd| async {
                helpers::insert_merchant_connector_creds_to_config(
                    db,
                    platform.get_processor(),
                    mcd,
                )
                .await
            })
            .await
            .transpose()?;

        // The operation merges mandate data from both request and payment_attempt
        let setup_mandate = mandate_data;
        let mandate_details_present =
            payment_attempt.mandate_details.is_some() || request.mandate_data.is_some();
        helpers::validate_mandate_data_and_future_usage(
            payment_intent.setup_future_usage,
            mandate_details_present,
        )?;
        let profile_id = payment_intent
            .profile_id
            .as_ref()
            .get_required_value("profile_id")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("'profile_id' not set in payment intent")?;

        let business_profile = db
            .find_business_profile_by_profile_id(
                platform.get_processor().get_key_store(),
                profile_id,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
                id: profile_id.get_string_repr().to_owned(),
            })?;

        let surcharge_details = request.surcharge_details.map(|request_surcharge_details| {
            payments::types::SurchargeDetails::from((&request_surcharge_details, &payment_attempt))
        });

        payment_intent.force_3ds_challenge = request
            .force_3ds_challenge
            .or(payment_intent.force_3ds_challenge);

        payment_intent.payment_channel = request
            .payment_channel
            .clone()
            .or(payment_intent.payment_channel);

        payment_intent.enable_partial_authorization = request
            .enable_partial_authorization
            .or(payment_intent.enable_partial_authorization);

        helpers::validate_overcapture_request(
            &request.enable_overcapture,
            &payment_attempt.capture_method,
        )?;
        payment_intent.enable_overcapture = request.enable_overcapture;

        let pmt_order_tax_amount = payment_intent.tax_details.clone().and_then(|tax| {
            if tax.payment_method_type.clone().map(|a| a.pmt) == payment_attempt.payment_method_type
            {
                tax.payment_method_type.map(|a| a.order_tax_amount)
            } else {
                None
            }
        });

        let order_tax_amount = pmt_order_tax_amount.or_else(|| {
            payment_intent
                .tax_details
                .clone()
                .and_then(|tax| tax.default.map(|a| a.order_tax_amount))
        });

        payment_intent.shipping_cost = request.shipping_cost.or(payment_intent.shipping_cost);
        payment_intent.installment_options = request
            .installment_options
            .clone()
            .or(payment_intent.installment_options);
        payment_attempt
            .net_amount
            .set_order_tax_amount(request.order_tax_amount.or(order_tax_amount));

        let payment_data = PaymentData {
            flow: PhantomData,
            payment_intent,
            payment_attempt,
            currency,
            amount,
            mandate_id,
            mandate_connector,
            token,
            token_data,
            setup_mandate,
            customer_acceptance,
            address: PaymentAddress::new(
                shipping_address.as_ref().map(From::from),
                billing_address.as_ref().map(From::from),
                payment_method_billing.as_ref().map(From::from),
                business_profile.use_billing_as_payment_method_billing,
            ),
            confirm: request.confirm,
            payment_method_data: request
                .payment_method_data
                .as_ref()
                .and_then(|pmd| pmd.payment_method_data.clone().map(Into::into)),
            payment_method_token: None,
            payment_method_info,
            force_sync: None,
            all_keys_required: None,
            refunds: vec![],
            disputes: vec![],
            attempts: None,
            sessions_token: vec![],
            card_cvc: request.card_cvc.clone(),
            creds_identifier,
            pm_token: None,
            connector_customer_id: None,
            recurring_mandate_payment_data,
            multiple_capture_data: None,
            redirect_response: None,
            surcharge_details,
            frm_message: None,
            payment_link_data: None,
            incremental_authorization_details: None,
            authorizations: vec![],
            authentication: None,
            recurring_details,
            poll_config: None,
            tax_data: None,
            session_id: None,
            service_details: None,
            card_testing_guard_data: None,
            vault_operation: None,
            threeds_method_comp_ind: None,
            whole_connector_response: None,
            is_manual_retry_enabled: None,
            is_l2_l3_enabled: business_profile.is_l2_l3_enabled,
            external_authentication_data: None,
            client_session_id: None,
            vault_session_details: None,
            external_vault_pmd: None,
            update_request_fields: Some(Self::extract_update_request_fields(request)),
        };

        let get_trackers_response = operations::GetTrackerResponse {
            operation: next_operation,
            customer_details: Some(customer_details),
            payment_data,
            business_profile,
            mandate_type,
        };

        Ok(get_trackers_response)
    }
}

#[async_trait]
impl<F: Clone + Send + Sync> Domain<F, api::PaymentsRequest, PaymentData<F>> for PaymentUpdate {
    #[instrument(skip_all)]
    async fn get_or_create_customer_details<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentData<F>,
        request: Option<CustomerDetails>,
        provider: &domain::Provider,
        initiator: Option<&domain::Initiator>,
        dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndProfileId,
        _mandate_type: Option<MandateTransactionType>,
    ) -> CustomResult<(PaymentUpdateOperation<'a, F>, Option<domain::Customer>), errors::StorageError>
    {
        let request_has_customer_document_details = request
            .as_ref()
            .and_then(|request| request.document_details.as_ref())
            .is_some();

        let (operation, customer): (PaymentUpdateOperation<'a, F>, Option<domain::Customer>) =
            match provider.get_account().merchant_account_type {
                common_enums::MerchantAccountType::Standard => {
                    helpers::create_customer_if_not_exist(
                        state,
                        Box::new(self),
                        payment_data,
                        request,
                        provider,
                        initiator,
                        dimensions,
                    )
                    .await
                }
                common_enums::MerchantAccountType::Platform => {
                    let customer = helpers::get_customer_if_exists(
                        state,
                        request.as_ref().and_then(|r| r.customer_id.as_ref()),
                        payment_data.payment_intent.customer_id.as_ref(),
                        provider,
                    )
                    .await?
                    .map(|cust| {
                        payment_data
                            .payment_intent
                            .customer_id
                            .as_ref()
                            .is_some_and(|existing_id| existing_id != cust.get_id())
                            .then_some(errors::StorageError::ValueNotFound(
                                "Customer id mismatch between payment intent and request"
                                    .to_string(),
                            ))
                            .map_or(Ok(()), Err)?;
                        Ok(cust)
                    })
                    .transpose()
                    .map_err(|e: errors::StorageError| report!(e))?;

                    let operation: PaymentUpdateOperation<'a, F> = Box::new(self);
                    Ok((operation, customer))
                }
                common_enums::MerchantAccountType::Connected => {
                    Err(errors::StorageError::ValueNotFound(
                        "Connected merchant cannot be a provider".to_string(),
                    )
                    .into())
                }
            }?;

        let intent_customer_document_details = payment_data
            .payment_intent
            .get_customer_document_details()
            .change_context(errors::StorageError::DeserializationFailed)
            .attach_printable("Failed to extract customer document details from payment_intent")?;

        if intent_customer_document_details.is_none() && !request_has_customer_document_details {
            if let Some(customer_data) = customer
                .clone()
                .map(CustomerData::foreign_try_from)
                .transpose()
                .map_err(|_| report!(errors::StorageError::DeserializationFailed))?
                .filter(|customer_data| customer_data.customer_document_details.is_some())
            {
                let key_manager_state = state.into();
                payment_data.payment_intent.customer_details = Some(
                    core_utils::create_encrypted_data(
                        &key_manager_state,
                        provider.get_key_store(),
                        customer_data,
                        common_utils::type_name!(diesel_models::PaymentIntent),
                    )
                    .await
                    .change_context(errors::StorageError::EncryptionError)
                    .attach_printable("Unable to encrypt customer details")?,
                );
            }
        }

        Ok((operation, customer))
    }

    async fn payments_dynamic_tax_calculation<'a>(
        &'a self,
        state: &SessionState,
        payment_data: &mut PaymentData<F>,
        _connector_call_type: &ConnectorCallType,
        business_profile: &domain::Profile,
        processor: &domain::Processor,
    ) -> CustomResult<(), errors::ApiErrorResponse> {
        let is_tax_connector_enabled = business_profile.get_is_tax_connector_enabled();
        let skip_external_tax_calculation = payment_data
            .payment_intent
            .skip_external_tax_calculation
            .unwrap_or(false);
        if is_tax_connector_enabled && !skip_external_tax_calculation {
            let db = state.store.as_ref();

            let merchant_connector_id = business_profile
                .tax_connector_id
                .as_ref()
                .get_required_value("business_profile.tax_connector_id")?;

            #[cfg(feature = "v1")]
            let mca = db
                .find_by_merchant_connector_account_merchant_id_merchant_connector_id(
                    &business_profile.merchant_id,
                    merchant_connector_id,
                    processor.get_key_store(),
                )
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: merchant_connector_id.get_string_repr().to_string(),
                    },
                )?;

            #[cfg(feature = "v2")]
            let mca = db
                .find_merchant_connector_account_by_id(merchant_connector_id, key_store)
                .await
                .to_not_found_response(
                    errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
                        id: merchant_connector_id.get_string_repr().to_string(),
                    },
                )?;

            let connector_data =
                api::TaxCalculateConnectorData::get_connector_by_name(&mca.connector_name)?;

            let router_data = core_utils::construct_payments_dynamic_tax_calculation_router_data(
                state,
                processor,
                payment_data,
                &mca,
            )
            .await?;
            let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
                api::CalculateTax,
                types::PaymentsTaxCalculationData,
                types::TaxCalculationResponseData,
            > = connector_data.connector.get_connector_integration();

            let response = services::execute_connector_processing_step(
                state,
                connector_integration,
                &router_data,
                payments::CallConnectorAction::Trigger,
                None,
                None,
            )
            .await
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Tax connector Response Failed")?;

            let tax_response = response.response.map_err(|err| {
                errors::ApiErrorResponse::ExternalConnectorError {
                    code: err.code,
                    message: err.message,
                    connector: connector_data.connector_name.clone().to_string(),
                    status_code: err.status_code,
                    reason: err.reason,
                }
            })?;

            payment_data.payment_intent.tax_details = Some(diesel_models::TaxDetails {
                default: Some(diesel_models::DefaultTax {
                    order_tax_amount: tax_response.order_tax_amount,
                }),
                payment_method_type: None,
            });

            Ok(())
        } else {
            Ok(())
        }
    }

    #[instrument(skip_all)]
    async fn make_pm_data<'a>(
        &'a self,
        state: &'a SessionState,
        payment_data: &mut PaymentData<F>,
        storage_scheme: storage_enums::MerchantStorageScheme,
        platform: &domain::Platform,
        business_profile: &domain::Profile,
        should_retry_with_pan: bool,
    ) -> RouterResult<(
        PaymentUpdateOperation<'a, F>,
        Option<domain::PaymentMethodData>,
        Option<String>,
    )> {
        Box::pin(helpers::make_pm_data(
            Box::new(self),
            state,
            payment_data,
            platform,
            storage_scheme,
            business_profile,
            should_retry_with_pan,
        ))
        .await
    }

    #[instrument(skip_all)]
    async fn add_task_to_process_tracker<'a>(
        &'a self,
        _state: &'a SessionState,
        _payment_attempt: &storage::PaymentAttempt,
        _requeue: bool,
        _schedule_time: Option<time::PrimitiveDateTime>,
    ) -> CustomResult<(), errors::ApiErrorResponse> {
        Ok(())
    }

    async fn get_connector<'a>(
        &'a self,
        _processor: &domain::Processor,
        state: &SessionState,
        request: &api::PaymentsRequest,
        _payment_intent: &storage::PaymentIntent,
    ) -> CustomResult<api::ConnectorChoice, errors::ApiErrorResponse> {
        helpers::get_connector_default(state, request.routing.clone()).await
    }

    #[instrument(skip_all)]
    async fn guard_payment_against_blocklist<'a>(
        &'a self,
        _state: &SessionState,
        _processor: &domain::Processor,
        _dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantId,
        _payment_data: &mut PaymentData<F>,
        _business_profile: &domain::Profile,
    ) -> CustomResult<bool, errors::ApiErrorResponse> {
        Ok(false)
    }
}

#[async_trait]
impl<F: Clone + Sync> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for PaymentUpdate {
    #[cfg(feature = "v2")]
    #[instrument(skip_all)]
    async fn update_trackers<'b>(
        &'b self,
        _state: &'b SessionState,
        _req_state: ReqState,
        _processor: &domain::Processor,
        mut _payment_data: PaymentData<F>,
        _customer: Option<domain::Customer>,
        _frm_suggestion: Option<FrmSuggestion>,
        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
        _dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantId,
    ) -> RouterResult<(PaymentUpdateOperation<'b, F>, PaymentData<F>)>
    where
        F: 'b + Send,
    {
        todo!()
    }

    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    async fn update_trackers<'b>(
        &'b self,
        state: &'b SessionState,
        req_state: ReqState,
        processor: &domain::Processor,
        mut payment_data: PaymentData<F>,
        _frm_suggestion: Option<FrmSuggestion>,
        _header_payload: hyperswitch_domain_models::payments::HeaderPayload,
        dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantId,
    ) -> RouterResult<(PaymentUpdateOperation<'b, F>, PaymentData<F>)>
    where
        F: 'b + Send,
    {
        let will_call_connector = payment_data
            .payment_attempt
            .connector
            .as_deref()
            .map(|connector_name| {
                api::connector_mapping::ConnectorData::convert_connector(connector_name)
                    .change_context(errors::ApiErrorResponse::InternalServerError)
                    .attach_printable("Invalid connector name stored in payment_attempt")
            })
            .transpose()?
            .is_some_and(|connector| {
                connector.should_call_connector_for_update_post_confirm(
                    payment_data.payment_attempt.payment_method_type,
                    payment_data.payment_intent.status,
                )
            });
        if !will_call_connector {
            payment_data = Box::pin(Self::persist_payment_update_changes(
                state,
                processor,
                dimensions,
                payment_data,
            ))
            .await?;
        }

        let processor_merchant_id = processor.get_account().get_id().clone();
        let payment_id = payment_data.payment_intent.payment_id.clone();

        // Check if client session validation is enabled
        let session_validation_enabled = dimensions
            .get_client_session_validation_enabled(
                state.store.as_ref(),
                state.superposition_service.as_ref(),
                None,
            )
            .await;

        // Recreate session only if validation is enabled and connector is not being called
        // (UpdatePostConfirm flows skip session recreation since the session may have expired)
        if session_validation_enabled && !will_call_connector {
            let (new_client_session_id, invalidation_report) =
                ClientSessionManager::recreate_session(
                    state,
                    &processor_merchant_id,
                    &payment_id,
                    payment_data.payment_intent.session_expiry,
                )
                .await
                .attach_printable("Failed to refresh client session during payment update")?;

            services::logger::debug!(
                payment_id = %payment_id.get_string_repr(),
                session_existed = invalidation_report.session_existed,
                "Client session recreated during update"
            );

            // Update payment_data with new session_id
            payment_data.client_session_id = Some(new_client_session_id);
        }

        let amount = payment_data.amount;
        req_state
            .event_context
            .event(AuditEvent::new(AuditEventType::PaymentUpdate { amount }))
            .with(payment_data.to_event())
            .emit();

        Ok((
            payments::is_confirm(self, payment_data.confirm),
            payment_data,
        ))
    }
}

impl ForeignTryFrom<domain::Customer> for CustomerData {
    type Error = errors::ApiErrorResponse;
    fn foreign_try_from(value: domain::Customer) -> Result<Self, Self::Error> {
        Ok(Self {
            name: value.name.map(|name| name.into_inner()),
            email: value.email.map(Email::from),
            phone: value.phone.map(|ph| ph.into_inner()),
            phone_country_code: value.phone_country_code,
            tax_registration_id: value
                .tax_registration_id
                .map(|tax_registration_id| tax_registration_id.into_inner()),
            customer_document_details: CustomerDocumentDetails::from(
                &value
                    .document_details
                    .map(|customer_document_details| customer_document_details.into_inner()),
            )
            .map_err(|report| {
                router_env::logger::error!(error = ?report, "Failed to convert customer document details");
                errors::ApiErrorResponse::InternalServerError
            })?,
        })
    }
}

impl<F: Send + Clone + Sync> ValidateRequest<F, api::PaymentsRequest, PaymentData<F>>
    for PaymentUpdate
{
    #[instrument(skip_all)]
    fn validate_request<'a, 'b>(
        &'b self,
        request: &api::PaymentsRequest,
        processor: &'a domain::Processor,
    ) -> RouterResult<(PaymentUpdateOperation<'b, F>, operations::ValidateResult)> {
        helpers::validate_customer_information(request)?;

        if let Some(amount) = request.amount {
            helpers::validate_max_amount(amount)?;
        }
        if let Some(session_expiry) = &request.session_expiry {
            helpers::validate_session_expiry(session_expiry.to_owned())?;
        }
        let payment_id = request
            .payment_id
            .clone()
            .ok_or(report!(errors::ApiErrorResponse::PaymentNotFound))?;

        let request_merchant_id = request.merchant_id.as_ref();
        helpers::validate_merchant_id(processor.get_account().get_id(), request_merchant_id)
            .change_context(errors::ApiErrorResponse::InvalidDataFormat {
                field_name: "merchant_id".to_string(),
                expected_format: "merchant_id from merchant account".to_string(),
            })?;

        helpers::validate_request_amount_and_amount_to_capture(
            request.amount,
            request.amount_to_capture,
            request.surcharge_details,
        )
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "amount_to_capture".to_string(),
            expected_format: "amount_to_capture lesser than or equal to amount".to_string(),
        })?;

        helpers::validate_payment_method_fields_present(request)?;

        let _mandate_type = helpers::validate_mandate(request, false)?;

        helpers::validate_recurring_details_and_token(
            &request.recurring_details,
            &request.payment_token,
            &request.mandate_id,
        )?;
        let _request_straight_through: Option<api::routing::StraightThroughAlgorithm> = request
            .routing
            .clone()
            .map(|val| val.parse_value("RoutingAlgorithm"))
            .transpose()
            .change_context(errors::ApiErrorResponse::InvalidRequestData {
                message: "Invalid straight through routing rules format".to_string(),
            })
            .attach_printable("Invalid straight through routing rules format")?;

        request.validate_installment_options().map_err(|err| {
            let message = format!("invalid installment options: {err}");
            err.change_context(errors::ApiErrorResponse::InvalidRequestData { message })
        })?;

        Ok((
            Box::new(self),
            operations::ValidateResult {
                merchant_id: processor.get_account().get_id().to_owned(),
                payment_id,
                storage_scheme: processor.get_account().storage_scheme,
                requeue: matches!(
                    request.retry_action,
                    Some(api_models::enums::RetryAction::Requeue)
                ),
            },
        ))
    }
}

impl PaymentUpdate {
    #[cfg(feature = "v1")]
    fn extract_update_request_fields(
        request: &api::PaymentsRequest,
    ) -> PaymentDataUpdateRequestFields {
        PaymentDataUpdateRequestFields {
            feature_metadata: request.feature_metadata.clone(),
            amount: request.amount.map(MinorUnit::from),
            connector_attempt_metadata: None,
            connector_transaction_id: String::new(),
            description: request.description.clone(),
            billing_descriptor: request.billing_descriptor.clone(),
            billing_address: request
                .billing
                .as_ref()
                .and_then(|billing| billing.address.clone()),
            metadata: request.metadata.clone(),
            merchant_order_reference_id: request.merchant_order_reference_id.clone(),
            customer_document_details: request
                .customer
                .as_ref()
                .and_then(|customer| customer.document_details.clone()),
        }
    }

    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    pub(crate) async fn compute_payment_update_changes<F>(
        state: &SessionState,
        processor: &domain::Processor,
        dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantId,
        payment_data: PaymentData<F>,
        request: &PaymentDataUpdateRequestFields,
    ) -> RouterResult<(
        PaymentData<F>,
        storage::PaymentAttemptUpdate,
        storage::PaymentIntentUpdate,
    )>
    where
        F: Clone + Send + Sync,
    {
        let storage_scheme = processor.get_account().storage_scheme;
        let key_store = processor.get_key_store();
        let profile_id = payment_data
            .payment_intent
            .profile_id
            .as_ref()
            .get_required_value("profile_id")
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("'profile_id' not set in payment intent")?;
        let customer_id = payment_data.payment_intent.customer_id.as_ref();

        let additional_pm_data = payment_data
            .payment_method_data
            .as_ref()
            .async_map(|payment_method_data| async {
                helpers::get_additional_payment_data(
                    payment_method_data,
                    &*state.store,
                    state.superposition_service.as_ref(),
                    &dimensions.with_profile_id(profile_id.clone()),
                    customer_id,
                    payment_data.payment_method_token.as_ref(),
                )
                .await
            })
            .await
            .transpose()?
            .flatten();

        let encoded_pm_data = additional_pm_data
            .as_ref()
            .map(Encode::encode_to_value)
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to encode additional pm data")?;

        let business_sub_label = payment_data.payment_attempt.business_sub_label.clone();

        let payment_method = payment_data.payment_attempt.payment_method;
        let payment_method_type = payment_data.payment_attempt.payment_method_type;
        let payment_experience = payment_data.payment_attempt.payment_experience;
        let amount_to_capture = payment_data.payment_attempt.amount_to_capture;
        let capture_method = payment_data.payment_attempt.capture_method;
        let payment_method_billing_address_id = payment_data
            .payment_attempt
            .payment_method_billing_address_id
            .clone();

        let surcharge_amount = payment_data
            .surcharge_details
            .as_ref()
            .map(|surcharge_details| surcharge_details.surcharge_amount);
        let tax_amount = payment_data
            .surcharge_details
            .as_ref()
            .map(|surcharge_details| surcharge_details.tax_on_surcharge_amount);
        let network_transaction_id = payment_data.payment_attempt.network_transaction_id.clone();
        let network_transaction_link_id = payment_data
            .payment_attempt
            .network_transaction_link_id
            .clone();
        let attempt_update = storage::PaymentAttemptUpdate::Update {
            currency: payment_data.currency,
            status: payment_data.payment_attempt.status,
            authentication_type: None,
            payment_method,
            payment_token: payment_data.token.clone(),
            payment_method_data: encoded_pm_data,
            payment_experience,
            payment_method_type,
            business_sub_label,
            amount_to_capture,
            capture_method,
            fingerprint_id: None,
            payment_method_billing_address_id,
            updated_by: storage_scheme.to_string(),
            network_transaction_id,
            network_transaction_link_id,
            net_amount: hyperswitch_domain_models::payments::payment_attempt::NetAmount::new(
                payment_data.amount.into(),
                payment_data.payment_intent.shipping_cost,
                payment_data
                    .payment_attempt
                    .net_amount
                    .get_order_tax_amount(),
                surcharge_amount,
                tax_amount,
                None,
            ),
            shipping_cost: payment_data.payment_intent.shipping_cost,
            order_tax_amount: payment_data
                .payment_attempt
                .net_amount
                .get_order_tax_amount(),
        };

        let customer_id = payment_data.payment_intent.customer_id.clone();

        let intent_status = match (
            payment_data.payment_intent.status,
            payment_data.payment_method_data.as_ref(),
            payment_data.token.as_ref(),
        ) {
            (storage_enums::IntentStatus::RequiresPaymentMethod, Some(_), _)
            | (storage_enums::IntentStatus::RequiresPaymentMethod, _, Some(_)) => {
                storage_enums::IntentStatus::RequiresConfirmation
            }
            (status, _, _) => status,
        };

        let (shipping_address, billing_address) = (
            payment_data.payment_intent.shipping_address_id.clone(),
            payment_data.payment_intent.billing_address_id.clone(),
        );

        let customer_details = payment_data.payment_intent.customer_details.clone();

        let return_url = payment_data.payment_intent.return_url.clone();
        let setup_future_usage = payment_data.payment_intent.setup_future_usage;
        let business_label = payment_data.payment_intent.business_label.clone();
        let business_country = payment_data.payment_intent.business_country;
        let statement_descriptor_name = payment_data
            .payment_intent
            .statement_descriptor_name
            .clone();
        let statement_descriptor_suffix = payment_data
            .payment_intent
            .statement_descriptor_suffix
            .clone();
        let key_manager_state = state.into();
        let billing_details = payment_data
            .address
            .get_payment_billing()
            .async_map(|billing_details| {
                core_utils::create_encrypted_data(
                    &key_manager_state,
                    key_store,
                    billing_details,
                    common_utils::type_name!(diesel_models::payment_method::PaymentMethod),
                )
            })
            .await
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt billing details")?;

        let shipping_details = payment_data
            .address
            .get_shipping()
            .async_map(|shipping_details| {
                core_utils::create_encrypted_data(
                    &key_manager_state,
                    key_store,
                    shipping_details,
                    common_utils::type_name!(diesel_models::payment_method::PaymentMethod),
                )
            })
            .await
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt shipping details")?;

        let order_details = payment_data.payment_intent.order_details.clone();
        let connector_metadata = payment_data.payment_intent.connector_metadata.clone();
        let frm_metadata = payment_data.payment_intent.frm_metadata.clone();
        let session_expiry = payment_data.payment_intent.session_expiry;
        let tax_details = payment_data
            .payment_attempt
            .net_amount
            .get_order_tax_amount()
            .map(|order_tax_amount| diesel_models::TaxDetails {
                default: Some(diesel_models::DefaultTax { order_tax_amount }),
                payment_method_type: None,
            });

        let shipping_cost = payment_data.payment_intent.shipping_cost;

        let payment_intent_feature_metadata = payment_data
            .payment_intent
            .get_optional_feature_metadata()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to parse payment intent feature metadata")?;

        let merchant_order_reference_id =
            request.merchant_order_reference_id.clone().or(payment_data
                .payment_intent
                .merchant_order_reference_id
                .clone());

        let description = request
            .description
            .clone()
            .or(payment_data.payment_intent.description.clone());

        let metadata = request
            .metadata
            .clone()
            .or(payment_data.payment_intent.metadata.clone());

        let feature_metadata = match (
            request.feature_metadata.clone(),
            payment_intent_feature_metadata,
        ) {
            (Some(request_meta), intent_meta) => Some(request_meta.merge(intent_meta)),
            (None, intent_meta) => intent_meta,
        };

        let feature_metadata_value = feature_metadata
            .as_ref()
            .map(Encode::encode_to_value)
            .transpose()
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Failed to serialize feature metadata")?;

        let intent_update =
            storage::PaymentIntentUpdate::Update(Box::new(PaymentIntentUpdateFields {
                amount: payment_data.amount.into(),
                currency: payment_data.currency,
                setup_future_usage,
                status: intent_status,
                customer_id: customer_id.clone(),
                shipping_address_id: shipping_address,
                billing_address_id: billing_address,
                return_url,
                business_country,
                business_label,
                description,
                statement_descriptor_name,
                statement_descriptor_suffix,
                billing_descriptor: payment_data.payment_intent.billing_descriptor.clone(),
                order_details,
                metadata,
                connector_metadata,
                payment_confirm_source: None,
                updated_by: storage_scheme.to_string(),
                fingerprint_id: None,
                session_expiry,
                request_external_three_ds_authentication: payment_data
                    .payment_intent
                    .request_external_three_ds_authentication,
                frm_metadata,
                customer_details,
                merchant_order_reference_id,
                billing_details,
                shipping_details,
                is_payment_processor_token_flow: None,
                tax_details,
                force_3ds_challenge: payment_data.payment_intent.force_3ds_challenge,
                is_iframe_redirection_enabled: payment_data
                    .payment_intent
                    .is_iframe_redirection_enabled,
                is_confirm_operation: false, // this is not a confirm operation
                payment_channel: payment_data.payment_intent.payment_channel.clone(),
                feature_metadata: feature_metadata_value.map(hyperswitch_masking::Secret::new),
                tax_status: payment_data.payment_intent.tax_status,
                discount_amount: payment_data.payment_intent.discount_amount,
                order_date: payment_data.payment_intent.order_date,
                shipping_amount_tax: payment_data.payment_intent.shipping_amount_tax,
                duty_amount: payment_data.payment_intent.duty_amount,
                enable_partial_authorization: payment_data
                    .payment_intent
                    .enable_partial_authorization,
                enable_overcapture: payment_data.payment_intent.enable_overcapture,
                shipping_cost,
                installment_options: payment_data.payment_intent.installment_options.clone(),
                profile_acquirer_id: payment_data.payment_intent.profile_acquirer_id.clone(),
                external_surcharge_strategy: payment_data
                    .payment_intent
                    .external_surcharge_strategy,
                external_surcharge_applicable: payment_data
                    .payment_intent
                    .external_surcharge_applicable,
            }));

        Ok((payment_data, attempt_update, intent_update))
    }

    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    pub(crate) async fn persist_payment_update_changes<F>(
        state: &SessionState,
        processor: &domain::Processor,
        dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantId,
        payment_data: PaymentData<F>,
    ) -> RouterResult<PaymentData<F>>
    where
        F: Clone + Send + Sync,
    {
        let storage_scheme = processor.get_account().storage_scheme;
        let key_store = processor.get_key_store();

        let update_request_fields = payment_data
            .update_request_fields
            .clone()
            .ok_or(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("update_request_fields not found in payment_data")?;

        let (mut payment_data, attempt_update, intent_update) =
            Box::pin(Self::compute_payment_update_changes(
                state,
                processor,
                dimensions,
                payment_data,
                &update_request_fields,
            ))
            .await?;

        payment_data.payment_attempt = state
            .store
            .update_payment_attempt_with_attempt_id(
                payment_data.payment_attempt.clone(),
                attempt_update,
                storage_scheme,
                key_store,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        payment_data.payment_intent = state
            .store
            .update_payment_intent(
                payment_data.payment_intent.clone(),
                intent_update,
                key_store,
                storage_scheme,
            )
            .await
            .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

        Ok(payment_data)
    }

    fn populate_payment_attempt_with_request(
        payment_attempt: &mut storage::PaymentAttempt,
        request: &api::PaymentsRequest,
    ) {
        request
            .business_sub_label
            .clone()
            .map(|bsl| payment_attempt.business_sub_label.replace(bsl));
        request
            .payment_method_type
            .map(|pmt| payment_attempt.payment_method_type.replace(pmt));
        request
            .payment_experience
            .map(|experience| payment_attempt.payment_experience.replace(experience));
        payment_attempt.amount_to_capture = request
            .amount_to_capture
            .or(payment_attempt.amount_to_capture);
        request
            .capture_method
            .map(|i| payment_attempt.capture_method.replace(i));
    }
    fn populate_payment_intent_with_request(
        payment_intent: &mut storage::PaymentIntent,
        request: &api::PaymentsRequest,
    ) {
        request
            .return_url
            .clone()
            .map(|i| payment_intent.return_url.replace(i.to_string()));

        payment_intent.business_country = request.business_country;

        payment_intent
            .business_label
            .clone_from(&request.business_label);

        request
            .statement_descriptor_name
            .clone()
            .map(|i| payment_intent.statement_descriptor_name.replace(i));

        request
            .statement_descriptor_suffix
            .clone()
            .map(|i| payment_intent.statement_descriptor_suffix.replace(i));

        request
            .client_secret
            .clone()
            .map(|i| payment_intent.client_secret.replace(i));
    }
}
