use std::str::FromStr;

use async_trait::async_trait;
use common_enums::connector_enums;
use common_utils::{errors, id_type, ucs_types};
use error_stack::ResultExt;
use external_services::grpc_client;
use hyperswitch_connectors::constants as connector_consts;
use hyperswitch_domain_models::{
    router_data,
    router_data_v2::{flow_common_types, PaymentFlowData},
    router_flow_types, router_request_types, router_response_types,
};
use hyperswitch_interfaces::{
    api as api_interface,
    api::{gateway, ConnectorSpecifications},
    consts as interface_consts, errors as interface_errors,
    unified_connector_service::transformers as ucs_transformers,
};
use hyperswitch_masking::{self, ExposeInterface};
use unified_connector_service_client::payments as payments_grpc;

use super::{ConstructFlowSpecificData, Feature};
use crate::{
    core::{
        errors::{ApiErrorResponse, ConnectorErrorExt, RouterResult},
        payments::{
            self, access_token, gateway as payments_gateway, gateway::context as gateway_context,
            helpers, transformers, PaymentData,
        },
        unified_connector_service as ucs_core,
    },
    logger,
    routes::SessionState,
    services,
    types::{self, api, domain, transformers::ForeignTryFrom},
};

#[async_trait]
impl
    ConstructFlowSpecificData<
        api::CompleteAuthorize,
        types::CompleteAuthorizeData,
        types::PaymentsResponseData,
    > for PaymentData<api::CompleteAuthorize>
{
    #[cfg(feature = "v1")]
    async fn construct_router_data<'a>(
        &self,
        state: &SessionState,
        connector_id: &str,
        processor: &domain::Processor,
        merchant_connector_account: &helpers::MerchantConnectorAccountType,
        merchant_recipient_data: Option<types::MerchantRecipientData>,
        header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
        _payment_method: Option<common_enums::PaymentMethod>,
        _payment_method_type: Option<common_enums::PaymentMethodType>,
    ) -> RouterResult<
        types::RouterData<
            api::CompleteAuthorize,
            types::CompleteAuthorizeData,
            types::PaymentsResponseData,
        >,
    > {
        Box::pin(transformers::construct_payment_router_data::<
            api::CompleteAuthorize,
            types::CompleteAuthorizeData,
        >(
            state,
            self.clone(),
            connector_id,
            processor,
            merchant_connector_account,
            merchant_recipient_data,
            header_payload,
            None,
            None,
        ))
        .await
    }

    #[cfg(feature = "v2")]
    async fn construct_router_data<'a>(
        &self,
        state: &SessionState,
        connector_id: &str,
        processor: &domain::Processor,
        customer: &Option<domain::Customer>,
        merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
        merchant_recipient_data: Option<types::MerchantRecipientData>,
        header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
    ) -> RouterResult<
        types::RouterData<
            api::CompleteAuthorize,
            types::CompleteAuthorizeData,
            types::PaymentsResponseData,
        >,
    > {
        todo!()
    }
}

#[async_trait]
impl Feature<api::CompleteAuthorize, types::CompleteAuthorizeData>
    for types::RouterData<
        api::CompleteAuthorize,
        types::CompleteAuthorizeData,
        types::PaymentsResponseData,
    >
{
    async fn decide_flows<'a>(
        mut self,
        state: &SessionState,
        connector: &api::ConnectorData,
        call_connector_action: payments::CallConnectorAction,
        connector_request: Option<services::Request>,
        business_profile: &domain::Profile,
        header_payload: hyperswitch_domain_models::payments::HeaderPayload,
        _return_raw_connector_response: Option<bool>,
        gateway_context: gateway_context::RouterGatewayContext,
    ) -> RouterResult<Self> {
        let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
            api::CompleteAuthorize,
            types::CompleteAuthorizeData,
            types::PaymentsResponseData,
        > = connector.connector.get_connector_integration();

        let mut complete_authorize_router_data = gateway::execute_payment_gateway(
            state,
            connector_integration,
            &self,
            call_connector_action.clone(),
            connector_request,
            None,
            gateway_context.clone(),
        )
        .await
        .to_payment_failed_response()?;
        match complete_authorize_router_data.response.clone() {
            Err(_) => Ok(complete_authorize_router_data),
            Ok(complete_authorize_response) => {
                // Check if the Capture API should be called based on the connector and other parameters
                if super::should_initiate_capture_flow(
                    &connector.connector_name,
                    self.request.customer_acceptance,
                    self.request.capture_method,
                    self.request.setup_future_usage,
                    complete_authorize_router_data.status,
                ) {
                    complete_authorize_router_data = Box::pin(process_capture_flow(
                        complete_authorize_router_data,
                        complete_authorize_response,
                        state,
                        connector,
                        call_connector_action.clone(),
                        business_profile,
                        header_payload,
                        gateway_context,
                    ))
                    .await?;
                }
                Ok(complete_authorize_router_data)
            }
        }
    }

    async fn balance_check_flow<'a>(
        &self,
        state: &SessionState,
        connector: &api::ConnectorData,
        _gateway_context: &gateway_context::RouterGatewayContext,
    ) -> RouterResult<types::BalanceCheckResult> {
        if connector.connector.is_balance_check_flow_required(
            api_interface::CurrentFlowInfo::CompleteAuthorize {
                auth_type: self.auth_type,
                payment_method: Some(self.payment_method),
                request_data: Box::new(self.request.clone()),
            },
        ) {
            logger::info!(
                "Balance check flow is required for connector: {}",
                connector.connector_name
            );
            let balance_check_request_data =
                router_request_types::GiftCardBalanceCheckRequestData::try_from(
                    self.request.to_owned(),
                )?;
            let balance_check_response_data: Result<
                router_response_types::GiftCardBalanceCheckResponseData,
                types::ErrorResponse,
            > = Err(types::ErrorResponse::default());
            let balance_check_router_data = helpers::router_data_type_conversion::<
                _,
                router_flow_types::GiftCardBalanceCheck,
                _,
                _,
                _,
                _,
            >(
                self.clone(),
                balance_check_request_data,
                balance_check_response_data,
            );

            let connector_integration: services::connector_integration_interface::BoxedConnectorIntegrationInterface<
                router_flow_types::GiftCardBalanceCheck,
                flow_common_types::GiftCardBalanceCheckFlowData,
                router_request_types::GiftCardBalanceCheckRequestData,
                router_response_types::GiftCardBalanceCheckResponseData,
            > = connector.connector.get_connector_integration();

            let response_router_data = services::execute_connector_processing_step(
                state,
                connector_integration,
                &balance_check_router_data,
                payments::CallConnectorAction::Trigger,
                None,
                None,
            )
            .await
            .to_payment_failed_response()?;

            let balance_check_result = match &response_router_data.response {
                Ok(router_response_types::GiftCardBalanceCheckResponseData {
                    balance,
                    currency,
                }) => {
                    logger::info!(
                        "Requested amount and currency: {}, {}",
                        self.request.minor_amount,
                        self.request.currency
                    );
                    logger::info!(
                        "Balance amount and currency recieved from connector : {}, {}",
                        balance,
                        currency
                    );
                    if *balance >= self.request.minor_amount {
                        Ok(Some(router_data::PaymentMethodBalance {
                            amount: *balance,
                            currency: *currency,
                        }))
                    } else {
                        // If balance is insufficient, return a connector error response
                        // At this point, connector would have returned a success response with balance details
                        Err(router_data::ErrorResponse {
                            code: interface_consts::NO_ERROR_CODE.to_string(),
                            message: interface_consts::NO_ERROR_MESSAGE.to_string(),
                            reason: Some(connector_consts::LOW_BALANCE_ERROR_MESSAGE.to_string()),
                            status_code: response_router_data
                                .connector_http_status_code
                                .unwrap_or(200),
                            attempt_status: Some(common_enums::AttemptStatus::Failure),
                            connector_transaction_id: None,
                            connector_response_reference_id: None,
                            network_advice_code: None,
                            network_decline_code: None,
                            network_error_message: None,
                            connector_metadata: None,
                        })
                    }
                }
                Err(err) => Err(err.clone()),
            };
            Ok(types::BalanceCheckResult {
                // Continue with the payment only if ok response is recieved from balance check
                should_continue_payment: balance_check_result.is_ok(),
                balance_check_result,
            })
        } else {
            Ok(types::BalanceCheckResult {
                balance_check_result: Ok(None),
                should_continue_payment: true,
            })
        }
    }

    async fn add_access_token<'a>(
        &self,
        state: &SessionState,
        connector: &api::ConnectorData,
        _processor: &domain::Processor,
        creds_identifier: Option<&str>,
        gateway_context: &gateway_context::RouterGatewayContext,
    ) -> RouterResult<types::AddAccessTokenResult> {
        Box::pin(access_token::add_access_token(
            state,
            connector,
            self,
            creds_identifier,
            gateway_context,
            None,
        ))
        .await
    }

    async fn add_payment_method_token<'a>(
        &mut self,
        state: &SessionState,
        connector: &api::ConnectorData,
        _tokenization_action: &payments::TokenizationAction,
        should_continue_payment: bool,
        gateway_context: &gateway_context::RouterGatewayContext,
    ) -> RouterResult<types::PaymentMethodTokenResult> {
        // TODO: remove this and handle it in core
        if matches!(connector.connector_name, types::Connector::Payme) {
            let request = self.request.clone();
            payments::tokenization::add_payment_method_token(
                state,
                connector,
                &payments::TokenizationAction::TokenizeInConnector,
                self,
                types::PaymentMethodTokenizationData::try_from(request)?,
                should_continue_payment,
                gateway_context,
            )
            .await
        } else {
            Ok(types::PaymentMethodTokenResult {
                payment_method_token_result: Ok(None),
                is_payment_method_tokenization_performed: false,
                connector_response: None,
            })
        }
    }

    async fn build_flow_specific_connector_request(
        &mut self,
        state: &SessionState,
        connector: &api::ConnectorData,
        call_connector_action: payments::CallConnectorAction,
    ) -> RouterResult<(Option<services::Request>, bool)> {
        let request = match call_connector_action {
            payments::CallConnectorAction::Trigger => {
                let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
                    api::CompleteAuthorize,
                    types::CompleteAuthorizeData,
                    types::PaymentsResponseData,
                > = connector.connector.get_connector_integration();

                connector_integration
                    .build_request(self, &state.conf.connectors)
                    .to_payment_failed_response()?
            }
            _ => None,
        };

        Ok((request, true))
    }

    async fn authentication_step<'a>(
        self,
        state: &SessionState,
        connector: &api::ConnectorData,
        gateway_context: &gateway_context::RouterGatewayContext,
    ) -> RouterResult<(Self, bool)>
    where
        Self: Sized,
    {
        if connector.connector.is_authentication_flow_required(
            api_interface::CurrentFlowInfo::CompleteAuthorize {
                auth_type: self.auth_type,
                request_data: Box::new(self.request.clone()),
                payment_method: Some(self.payment_method),
            },
        ) {
            let router_data = self;
            logger::info!(
                "Authentication flow is required for connector: {}",
                connector.connector_name
            );
            let mut complete_authorize_request_data = router_data.request.clone();
            let authenticate_request_data =
                types::PaymentsAuthenticateData::try_from(router_data.request.to_owned())?;
            let authenticate_response_data: Result<
                types::PaymentsResponseData,
                types::ErrorResponse,
            > = Err(types::ErrorResponse::default());
            let authenticate_router_data =
                helpers::router_data_type_conversion::<_, api::Authenticate, _, _, _, _>(
                    router_data.clone(),
                    authenticate_request_data,
                    authenticate_response_data,
                );

            // Call UCS for Authenticate flow and store authentication result for next step
            let authenticate_router_data = Box::pin(payments_gateway::handle_gateway_call::<
                _,
                _,
                _,
                PaymentFlowData,
                _,
            >(
                state,
                authenticate_router_data,
                connector,
                gateway_context,
                payments::CallConnectorAction::Trigger,
                None,
                None,
            ))
            .await?;

            // Convert back to CompleteAuthorize router data while preserving authentication response data
            let authenticate_response = authenticate_router_data.response.clone();
            if let Ok(types::PaymentsResponseData::TransactionResponse {
                connector_metadata,
                authentication_data,
                ..
            }) = &authenticate_router_data.response
            {
                connector_metadata.clone_into(&mut complete_authorize_request_data.connector_meta);
                complete_authorize_request_data.authentication_data =
                    authentication_data.clone().map(|data| *data);
            };
            let complete_authorize_router_data =
                helpers::router_data_type_conversion::<_, api::CompleteAuthorize, _, _, _, _>(
                    authenticate_router_data,
                    complete_authorize_request_data,
                    authenticate_response,
                );
            // check if redirection is not present in the response and attempt status is not AuthenticationFailed
            // if condition does not satisfy, then we don't need to proceed further
            let should_continue = (matches!(
                complete_authorize_router_data.response,
                Ok(types::PaymentsResponseData::TransactionResponse {
                    ref redirection_data,
                    ..
                }) if redirection_data.is_none()
            ) && complete_authorize_router_data.status
                != common_enums::AttemptStatus::AuthenticationFailed);
            Ok((complete_authorize_router_data, should_continue))
        } else {
            Ok((self, true))
        }
    }

    async fn post_authentication_step<'a>(
        self,
        state: &SessionState,
        connector: &api::ConnectorData,
        gateway_context: &gateway_context::RouterGatewayContext,
    ) -> RouterResult<(Self, bool)>
    where
        Self: Sized,
    {
        if connector.connector.is_post_authentication_flow_required(
            api_interface::CurrentFlowInfo::CompleteAuthorize {
                auth_type: self.auth_type,
                request_data: Box::new(self.request.clone()),
                payment_method: Some(self.payment_method),
            },
        ) {
            let router_data = self;
            logger::info!(
                "Post-authentication flow is required for connector: {}",
                connector.connector_name
            );
            // Convert CompleteAuthorize to PostAuthenticate for UCS call
            let mut complete_authorize_request_data = router_data.request.clone();

            let post_authenticate_request_data =
                types::PaymentsPostAuthenticateData::try_from(router_data.request.to_owned())?;

            let post_authenticate_response_data: Result<
                types::PaymentsResponseData,
                types::ErrorResponse,
            > = Err(types::ErrorResponse::default());
            let post_authenticate_router_data =
                helpers::router_data_type_conversion::<_, api::PostAuthenticate, _, _, _, _>(
                    router_data.clone(),
                    post_authenticate_request_data,
                    post_authenticate_response_data,
                );

            let post_authenticate_router_data = Box::pin(payments_gateway::handle_gateway_call::<
                _,
                _,
                _,
                PaymentFlowData,
                _,
            >(
                state,
                post_authenticate_router_data,
                connector,
                gateway_context,
                payments::CallConnectorAction::Trigger,
                None,
                None,
            ))
            .await?;
            // Convert back to CompleteAuthorize router data while preserving preprocessing response data
            let post_authenticate_response = post_authenticate_router_data.response.clone();
            if let Ok(types::PaymentsResponseData::TransactionResponse {
                connector_metadata,
                authentication_data,
                ..
            }) = &post_authenticate_router_data.response
            {
                connector_metadata.clone_into(&mut complete_authorize_request_data.connector_meta);
                complete_authorize_request_data.authentication_data =
                    authentication_data.clone().map(|data| *data);
            };
            let complete_authorize_router_data =
                helpers::router_data_type_conversion::<_, api::CompleteAuthorize, _, _, _, _>(
                    post_authenticate_router_data,
                    complete_authorize_request_data,
                    post_authenticate_response,
                );
            // check if redirection is not present in the response and attempt status is not AuthenticationFailed
            // if condition does not satisfy, then we don't need to proceed further
            let should_continue = matches!(
                complete_authorize_router_data.response,
                Ok(types::PaymentsResponseData::TransactionResponse {
                    ref redirection_data,
                    ..
                }) if redirection_data.is_none()
            ) && !matches!(
                complete_authorize_router_data.status,
                common_enums::AttemptStatus::AuthenticationFailed
                    | common_enums::AttemptStatus::Failure
            );
            Ok((complete_authorize_router_data, should_continue))
        } else {
            Ok((self, true))
        }
    }

    async fn push_notification_step<'a>(
        self,
        state: &SessionState,
        connector: &api::ConnectorData,
        gateway_context: &gateway_context::RouterGatewayContext,
    ) -> RouterResult<(Self, bool)>
    where
        Self: Sized,
    {
        if connector.connector.is_push_notification_flow_required(
            api_interface::CurrentFlowInfo::CompleteAuthorize {
                auth_type: self.auth_type,
                request_data: Box::new(self.request.clone()),
                payment_method: Some(self.payment_method),
            },
        ) {
            logger::info!(
                "Payment trigger flow is required for connector: {} for Complete Authorize flow",
                connector.connector_name
            );
            let complete_authorize_request_data = self.request.clone();
            let push_notification_request_data =
                types::PushNotificationRequestData::try_from(self.request.to_owned())?;
            let push_notification_response_data: Result<
                types::PaymentsResponseData,
                types::ErrorResponse,
            > = Err(types::ErrorResponse::default());
            let push_notification_router_data =
                helpers::router_data_type_conversion::<_, api::PushNotification, _, _, _, _>(
                    self.clone(),
                    push_notification_request_data,
                    push_notification_response_data,
                );
            let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
                api::PushNotification,
                types::PushNotificationRequestData,
                types::PaymentsResponseData,
            > = connector.connector.get_connector_integration();
            let push_notification_router_data = gateway::execute_payment_gateway(
                state,
                connector_integration,
                &push_notification_router_data,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                gateway_context.clone(),
            )
            .await
            .to_payment_failed_response()?;
            let push_notification_response = push_notification_router_data.response.clone();
            let complete_authorize_router_data =
                helpers::router_data_type_conversion::<_, api::CompleteAuthorize, _, _, _, _>(
                    push_notification_router_data,
                    complete_authorize_request_data,
                    push_notification_response,
                );
            let should_continue_payment = complete_authorize_router_data.response.is_ok();
            Ok((complete_authorize_router_data, should_continue_payment))
        } else {
            Ok((self, true))
        }
    }

    async fn generate_qr_step<'a>(
        self,
        state: &SessionState,
        connector: &api::ConnectorData,
        gateway_context: &gateway_context::RouterGatewayContext,
    ) -> RouterResult<(Self, bool)>
    where
        Self: Sized,
    {
        if connector.connector.is_generate_qr_flow_required(
            api_interface::CurrentFlowInfo::CompleteAuthorize {
                auth_type: self.auth_type,
                request_data: Box::new(self.request.clone()),
                payment_method: Some(self.payment_method),
            },
        ) {
            logger::info!(
                "Generate Qr flow is required for connector: {} for Complete Authorize flow",
                connector.connector_name
            );
            let complete_authorize_request_data = self.request.clone();
            let generate_qr_request_data =
                types::GenerateQrRequestData::try_from(self.request.to_owned())?;
            let generate_qr_response_data: Result<
                types::PaymentsResponseData,
                types::ErrorResponse,
            > = Err(types::ErrorResponse::default());
            let generate_qr_router_data =
                helpers::router_data_type_conversion::<_, api::GenerateQr, _, _, _, _>(
                    self.clone(),
                    generate_qr_request_data,
                    generate_qr_response_data,
                );
            let connector_integration: services::BoxedPaymentConnectorIntegrationInterface<
                api::GenerateQr,
                types::GenerateQrRequestData,
                types::PaymentsResponseData,
            > = connector.connector.get_connector_integration();
            let generate_qr_router_data = gateway::execute_payment_gateway(
                state,
                connector_integration,
                &generate_qr_router_data,
                payments::CallConnectorAction::Trigger,
                None,
                None,
                gateway_context.clone(),
            )
            .await
            .to_payment_failed_response()?;
            let generate_qr_response = generate_qr_router_data.response.clone();
            let complete_authorize_router_data =
                helpers::router_data_type_conversion::<_, api::CompleteAuthorize, _, _, _, _>(
                    generate_qr_router_data,
                    complete_authorize_request_data,
                    generate_qr_response,
                );
            let should_continue_payment = complete_authorize_router_data.response.is_ok();
            Ok((complete_authorize_router_data, should_continue_payment))
        } else {
            Ok((self, true))
        }
    }
}

fn transform_redirection_response_for_authenticate_flow(
    connector: connector_enums::Connector,
    response_data: router_response_types::RedirectForm,
) -> errors::CustomResult<
    router_response_types::RedirectForm,
    ucs_transformers::UnifiedConnectorServiceError,
> {
    match (connector, &response_data) {
        (
            connector_enums::Connector::Cybersource | connector_enums::Connector::Barclaycard,
            router_response_types::RedirectForm::Form {
                endpoint,
                method: _,
                ref form_fields,
            },
        ) => {
            let access_token = form_fields.get("access_token").cloned().ok_or(
                ucs_transformers::UnifiedConnectorServiceError::MissingRequiredField {
                    field_name: "access_token",
                },
            )?;
            let step_up_url = form_fields.get("step_up_url").unwrap_or(endpoint).clone();
            match connector {
                connector_enums::Connector::Barclaycard => Ok(
                    router_response_types::RedirectForm::BarclaycardConsumerAuth {
                        access_token,
                        step_up_url,
                    },
                ),
                connector_enums::Connector::Cybersource => Ok(
                    router_response_types::RedirectForm::CybersourceConsumerAuth {
                        access_token,
                        step_up_url,
                    },
                ),
                _ => Ok(response_data),
            }
        }
        _ => Ok(response_data),
    }
}
fn transform_response_for_authenticate_flow(
    connector: connector_enums::Connector,
    response_data: router_response_types::PaymentsResponseData,
) -> errors::CustomResult<
    router_response_types::PaymentsResponseData,
    ucs_transformers::UnifiedConnectorServiceError,
> {
    match (connector, response_data.clone()) {
        (
            connector_enums::Connector::Cybersource | connector_enums::Connector::Barclaycard,
            router_response_types::PaymentsResponseData::TransactionResponse {
                resource_id,
                redirection_data,
                mandate_reference,
                connector_metadata,
                network_txn_id,
                network_txn_link_id,
                connector_response_reference_id,
                incremental_authorization_allowed,
                authentication_data,
                charges,
            },
        ) => {
            let redirection_data = Box::new(
                (*redirection_data)
                    .clone()
                    .map(|redirection_data| {
                        transform_redirection_response_for_authenticate_flow(
                            connector,
                            redirection_data,
                        )
                    })
                    .transpose()?,
            );
            Ok(
                router_response_types::PaymentsResponseData::TransactionResponse {
                    resource_id,
                    redirection_data,
                    mandate_reference,
                    connector_metadata,
                    network_txn_id,
                    network_txn_link_id,
                    connector_response_reference_id,
                    incremental_authorization_allowed,
                    authentication_data,
                    charges,
                },
            )
        }
        _ => Ok(response_data),
    }
}

#[allow(dead_code, clippy::too_many_arguments)]
pub async fn call_unified_connector_service_authenticate(
    router_data: &types::RouterData<
        api::Authenticate,
        types::PaymentsAuthenticateData,
        types::PaymentsResponseData,
    >,
    state: &SessionState,
    header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    lineage_ids: grpc_client::LineageIds,
    #[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType,
    #[cfg(feature = "v2")] merchant_connector_account: domain::MerchantConnectorAccountTypeDetails,
    processor: &domain::Processor,
    connector: connector_enums::Connector,
    unified_connector_service_execution_mode: common_enums::ExecutionMode,
) -> errors::CustomResult<
    types::RouterData<
        api::Authenticate,
        types::PaymentsAuthenticateData,
        types::PaymentsResponseData,
    >,
    interface_errors::ConnectorError,
> {
    let client = state
        .grpc_client
        .unified_connector_service_client
        .clone()
        .ok_or(interface_errors::ConnectorError::RequestEncodingFailed)
        .attach_printable("Failed to fetch Unified Connector Service client")?;

    let payment_authenticate_request =
        payments_grpc::PaymentMethodAuthenticationServiceAuthenticateRequest::foreign_try_from(
            router_data,
        )
        .change_context(interface_errors::ConnectorError::RequestEncodingFailed)
        .attach_printable("Failed to construct Payment Authenticate Request")?;

    let connector_auth_metadata = ucs_core::build_unified_connector_service_auth_metadata(
        merchant_connector_account,
        processor.get_account().get_id(),
        router_data.connector.clone(),
    )
    .change_context(interface_errors::ConnectorError::RequestEncodingFailed)
    .attach_printable("Failed to construct request metadata")?;
    let merchant_reference_id = ucs_core::parse_merchant_reference_id(
        header_payload
            .x_reference_id
            .as_deref()
            .unwrap_or(router_data.payment_id.as_str()),
    )
    .map(ucs_types::UcsReferenceId::Payment);
    let resource_id = id_type::PaymentResourceId::from_str(router_data.attempt_id.as_str())
        .inspect_err(
            |err| logger::warn!(error=?err, "Invalid Payment AttemptId for UCS resource id"),
        )
        .ok()
        .map(ucs_types::UcsResourceId::PaymentAttempt);
    let headers_builder = state
        .get_grpc_headers_ucs(unified_connector_service_execution_mode)
        .external_vault_proxy_metadata(None)
        .merchant_reference_id(merchant_reference_id)
        .resource_id(resource_id)
        .lineage_ids(lineage_ids);
    let (router_data, _) = Box::pin(ucs_core::ucs_logging_wrapper_granular(
        router_data.clone(),
        state,
        payment_authenticate_request,
        headers_builder,
        unified_connector_service_execution_mode,
        |mut router_data, payment_authenticate_request, grpc_headers| async move {
            let response = Box::pin(client.payment_authenticate(
                payment_authenticate_request,
                connector_auth_metadata,
                grpc_headers,
            ))
            .await
            .attach_printable("Failed to authenticate payment")?;

            let payment_authenticate_response = response.into_inner();

            let (router_data_response, status_code) =
                ucs_core::handle_unified_connector_service_response_for_payment_authenticate(
                    payment_authenticate_response.clone(),
                    router_data.status,
                )
                .attach_printable("Failed to deserialize UCS response")?;

            let router_data_response = match router_data_response {
                Ok((response, status)) => {
                    router_data.status = status;
                    Ok(transform_response_for_authenticate_flow(
                        connector, response,
                    )?)
                }
                Err(err) => {
                    if let Some(attempt_status) = err.attempt_status {
                        router_data.status = attempt_status;
                    }
                    Err(err)
                }
            };

            router_data.response = router_data_response;
            router_data.raw_connector_response = payment_authenticate_response
                .raw_connector_response
                .clone()
                .map(|raw_connector_response| raw_connector_response.expose().into());
            router_data.connector_http_status_code = Some(status_code);

            Ok((router_data, (), payment_authenticate_response))
        },
    ))
    .await
    .change_context(interface_errors::ConnectorError::ResponseHandlingFailed)?;

    Ok(router_data)
}

#[allow(dead_code, clippy::too_many_arguments)]
pub async fn call_unified_connector_service_post_authenticate(
    router_data: &types::RouterData<
        api::PostAuthenticate,
        types::PaymentsPostAuthenticateData,
        types::PaymentsResponseData,
    >,
    state: &SessionState,
    header_payload: &hyperswitch_domain_models::payments::HeaderPayload,
    lineage_ids: grpc_client::LineageIds,
    #[cfg(feature = "v1")] merchant_connector_account: helpers::MerchantConnectorAccountType,
    #[cfg(feature = "v2")] merchant_connector_account: domain::MerchantConnectorAccountTypeDetails,
    processor: &domain::Processor,
    unified_connector_service_execution_mode: common_enums::ExecutionMode,
) -> errors::CustomResult<
    types::RouterData<
        api::PostAuthenticate,
        types::PaymentsPostAuthenticateData,
        types::PaymentsResponseData,
    >,
    interface_errors::ConnectorError,
> {
    let client = state
        .grpc_client
        .unified_connector_service_client
        .clone()
        .ok_or(interface_errors::ConnectorError::RequestEncodingFailed)
        .attach_printable("Failed to fetch Unified Connector Service client")?;

    let payment_post_authenticate_request =
        payments_grpc::PaymentMethodAuthenticationServicePostAuthenticateRequest::foreign_try_from(
            router_data,
        )
        .change_context(interface_errors::ConnectorError::RequestEncodingFailed)
        .attach_printable("Failed to construct Payment Post Authenticate Request")?;

    let connector_auth_metadata = ucs_core::build_unified_connector_service_auth_metadata(
        merchant_connector_account,
        processor.get_account().get_id(),
        router_data.connector.clone(),
    )
    .change_context(interface_errors::ConnectorError::RequestEncodingFailed)
    .attach_printable("Failed to construct request metadata")?;
    let merchant_reference_id = ucs_core::parse_merchant_reference_id(
        header_payload
            .x_reference_id
            .as_deref()
            .unwrap_or(router_data.payment_id.as_str()),
    )
    .map(ucs_types::UcsReferenceId::Payment);
    let resource_id = id_type::PaymentResourceId::from_str(router_data.attempt_id.as_str())
        .inspect_err(
            |err| logger::warn!(error=?err, "Invalid Payment AttemptId for UCS resource id"),
        )
        .ok()
        .map(ucs_types::UcsResourceId::PaymentAttempt);
    let headers_builder = state
        .get_grpc_headers_ucs(unified_connector_service_execution_mode)
        .external_vault_proxy_metadata(None)
        .merchant_reference_id(merchant_reference_id)
        .resource_id(resource_id)
        .lineage_ids(lineage_ids);
    let (router_data, _) = Box::pin(ucs_core::ucs_logging_wrapper_granular(
        router_data.clone(),
        state,
        payment_post_authenticate_request,
        headers_builder,
        unified_connector_service_execution_mode,
        |mut router_data, payment_post_authenticate_request, grpc_headers| async move {
            let response = Box::pin(client.payment_post_authenticate(
                payment_post_authenticate_request,
                connector_auth_metadata,
                grpc_headers,
            ))
            .await
            .attach_printable("Failed to post authenticate payment")?;

            let payment_post_authenticate_response = response.into_inner();

            let (router_data_response, status_code) =
                ucs_core::handle_unified_connector_service_response_for_payment_post_authenticate(
                    payment_post_authenticate_response.clone(),
                    router_data.status,
                )
                .attach_printable("Failed to deserialize UCS response")?;

            let router_data_response = match router_data_response {
                Ok((response, status)) => {
                    router_data.status = status;
                    Ok(response)
                }
                Err(err) => {
                    if let Some(attempt_status) = err.attempt_status {
                        router_data.status = attempt_status;
                    }
                    Err(err)
                }
            };
            router_data.response = router_data_response;
            router_data.raw_connector_response = payment_post_authenticate_response
                .raw_connector_response
                .clone()
                .map(|raw_connector_response| raw_connector_response.expose().into());
            router_data.connector_http_status_code = Some(status_code);

            Ok((router_data, (), payment_post_authenticate_response))
        },
    ))
    .await
    .change_context(interface_errors::ConnectorError::ResponseHandlingFailed)?;

    Ok(router_data)
}

impl<F>
    ForeignTryFrom<types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>>
    for types::PaymentsCaptureData
{
    type Error = error_stack::Report<ApiErrorResponse>;

    fn foreign_try_from(
        item: types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let response = item
            .response
            .map_err(|err| ApiErrorResponse::ExternalConnectorError {
                code: err.code,
                message: err.message,
                connector: item.connector.clone().to_string(),
                status_code: err.status_code,
                reason: err.reason,
            })?;

        Ok(Self {
            amount_to_capture: item.request.amount,
            currency: item.request.currency,
            connector_transaction_id: types::PaymentsResponseData::get_connector_transaction_id(
                &response,
            )?,
            payment_amount: item.request.amount,
            multiple_capture_data: None,
            connector_meta: types::PaymentsResponseData::get_connector_metadata(&response)
                .map(|secret| secret.expose()),
            browser_info: None,
            metadata: None,
            capture_method: item.request.capture_method,
            minor_payment_amount: item.request.minor_amount,
            minor_amount_to_capture: item.request.minor_amount,
            order_tax_amount: None,
            integrity_object: None,
            split_payments: None,
            webhook_url: None,
            merchant_order_reference_id: item.request.merchant_order_reference_id,
        })
    }
}

#[allow(clippy::too_many_arguments)]
async fn process_capture_flow(
    mut router_data: types::RouterData<
        api::CompleteAuthorize,
        types::CompleteAuthorizeData,
        types::PaymentsResponseData,
    >,
    complete_authorize_response: types::PaymentsResponseData,
    state: &SessionState,
    connector: &api::ConnectorData,
    call_connector_action: payments::CallConnectorAction,
    business_profile: &domain::Profile,
    header_payload: hyperswitch_domain_models::payments::HeaderPayload,
    gateway_context: gateway_context::RouterGatewayContext,
) -> RouterResult<
    types::RouterData<
        api::CompleteAuthorize,
        types::CompleteAuthorizeData,
        types::PaymentsResponseData,
    >,
> {
    // Convert RouterData into Capture RouterData
    let capture_router_data = helpers::router_data_type_conversion(
        router_data.clone(),
        types::PaymentsCaptureData::foreign_try_from(router_data.clone())?,
        Err(types::ErrorResponse::default()),
    );

    // Call capture request
    let post_capture_router_data = super::call_capture_request(
        capture_router_data,
        state,
        connector,
        call_connector_action,
        business_profile,
        header_payload,
        gateway_context,
    )
    .await;

    // Process capture response
    let (updated_status, updated_response) =
        super::handle_post_capture_response(complete_authorize_response, post_capture_router_data)?;

    router_data.status = updated_status;
    router_data.response = Ok(updated_response);
    Ok(router_data)
}
