use std::collections::HashMap;

use common_enums::enums;
use common_utils::{
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    ext_traits::ValueExt,
    types::StringMajorUnit,
};
use domain_types::{
    connector_flow::{
        Authorize, Capture, ClientAuthenticationToken, CreateConnectorCustomer, RSync, Refund,
        SetupMandate, Void,
    },
    connector_types::{
        BillingDescriptor, ClientAuthenticationTokenData, ClientAuthenticationTokenRequestData,
        ConnectorCustomerData, ConnectorCustomerResponse,
        ConnectorSpecificClientAuthenticationResponse, MandateReference,
        PayloadClientAuthenticationResponse as PayloadClientAuthenticationResponseDomain,
        PaymentFlowData, PaymentVoidData, PaymentsAuthorizeData, PaymentsCaptureData,
        PaymentsResponseData, RefundFlowData, RefundSyncData, RefundsData, RefundsResponseData,
        ResponseId, SetupMandateRequestData,
    },
    errors::{ConnectorError, IntegrationError, IntegrationErrorContext},
    merchant_authentication_flow_data::MerchantAuthenticationFlowData,
    payment_method_data::{BankDebitData, PaymentMethodData, PaymentMethodDataTypes},
    router_data::{
        AdditionalPaymentMethodConnectorResponse, ConnectorResponseData, ConnectorSpecificConfig,
        ErrorResponse,
    },
    router_data_v2::RouterDataV2,
};
use error_stack::ResultExt;
use hyperswitch_masking::{ExposeOptionInterface, Secret};
use serde::{Deserialize, Serialize};

use super::{requests, responses};
use crate::connectors::payload::{PayloadAmountConvertor, PayloadRouterData};
use crate::types::ResponseRouterData;

pub use super::requests::{
    PayloadCaptureRequest, PayloadCardsRequestData, PayloadCustomerRequest, PayloadPaymentsRequest,
    PayloadRefundRequest, PayloadRepeatPaymentRequest, PayloadVoidRequest,
};
pub use super::responses::{
    PayloadAuthorizeResponse, PayloadCaptureResponse, PayloadCustomerResponse,
    PayloadErrorResponse, PayloadEventDetails, PayloadPSyncResponse, PayloadPaymentsResponse,
    PayloadRSyncResponse, PayloadRefundResponse, PayloadRepeatPaymentResponse,
    PayloadSetupMandateResponse, PayloadVoidResponse, PayloadWebhookEvent, PayloadWebhooksTrigger,
};

type Error = error_stack::Report<IntegrationError>;
type ResponseError = error_stack::Report<ConnectorError>;

// Helper function to check if capture method is manual
fn is_manual_capture(capture_method: Option<enums::CaptureMethod>) -> bool {
    matches!(capture_method, Some(enums::CaptureMethod::Manual))
}

fn get_processing_account_id_from_metadata(
    metadata: Option<&serde_json::Value>,
) -> Option<Secret<String>> {
    metadata
        .and_then(|m| m.get("processing_account_id"))
        .and_then(|v| v.as_str())
        .map(|s| Secret::new(s.to_string()))
}

// Auth Struct
#[derive(Debug, Clone, Deserialize)]
pub struct PayloadAuth {
    pub api_key: Secret<String>,
    pub processing_account_id: Option<Secret<String>>,
}

#[derive(Debug, Clone)]
pub struct PayloadAuthType {
    pub auths: HashMap<enums::Currency, PayloadAuth>,
}

impl TryFrom<(&ConnectorSpecificConfig, enums::Currency)> for PayloadAuth {
    type Error = Error;
    fn try_from(value: (&ConnectorSpecificConfig, enums::Currency)) -> Result<Self, Self::Error> {
        let (auth_type, currency) = value;
        match auth_type {
            ConnectorSpecificConfig::Payload { auth_key_map, .. } => auth_key_map
                .get(&currency)
                .ok_or(IntegrationError::CurrencyNotSupported {
                    message: currency.to_string(),
                    connector: "Payload",
                    context: Default::default(),
                })?
                .to_owned()
                .parse_value::<Self>("PayloadAuth")
                .change_context(IntegrationError::FailedToObtainAuthType {
                    context: Default::default(),
                }),
            _ => Err(IntegrationError::FailedToObtainAuthType {
                context: Default::default(),
            }
            .into()),
        }
    }
}

impl TryFrom<&ConnectorSpecificConfig> for PayloadAuthType {
    type Error = Error;
    fn try_from(auth_type: &ConnectorSpecificConfig) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorSpecificConfig::Payload { auth_key_map, .. } => Ok(Self {
                auths: auth_key_map
                    .iter()
                    .map(|(currency, auth_value)| {
                        let auth = auth_value
                            .to_owned()
                            .parse_value::<PayloadAuth>("PayloadAuth")
                            .change_context(IntegrationError::FailedToObtainAuthType {
                                context: Default::default(),
                            })?;
                        Ok((*currency, auth))
                    })
                    .collect::<Result<_, Self::Error>>()?,
            }),
            _ => Err(IntegrationError::FailedToObtainAuthType {
                context: Default::default(),
            }
            .into()),
        }
    }
}

impl PayloadAuthType {
    // The api_key / processing_account_id pair is the same across currencies in
    // the Payload sandbox, so any currency-keyed entry yields the same id.
    fn primary_processing_id(&self) -> Option<Secret<String>> {
        self.auths
            .values()
            .find_map(|a| a.processing_account_id.clone())
    }
}

/// Strip the connector-internal `processing_account_id` key from the merchant
/// metadata before it is echoed back to Payload as `attrs` (mirrors hyperswitch).
fn get_filtered_metadata(metadata: Option<&serde_json::Value>) -> Option<serde_json::Value> {
    metadata.and_then(|m| match m {
        serde_json::Value::Object(map) => {
            let mut filtered = map.clone();
            filtered.remove("processing_account_id");
            if filtered.is_empty() {
                None
            } else {
                Some(serde_json::Value::Object(filtered))
            }
        }
        _ => None,
    })
}

/// Map the statement descriptor to Payload's `descriptor`, capped at 32 chars
/// (card-brand limit). Mirrors hyperswitch, including its bytes-length check /
/// chars-truncation quirk.
fn get_description_from_billing_descriptor(
    billing_descriptor: Option<&BillingDescriptor>,
) -> Option<String> {
    billing_descriptor
        .and_then(|descriptor| descriptor.statement_descriptor.as_ref())
        .map(|desc| {
            if desc.len() > 32 {
                desc.chars().take(32).collect()
            } else {
                desc.clone()
            }
        })
}

#[allow(clippy::too_many_arguments)]
fn build_payload_card_request_data<T: PaymentMethodDataTypes>(
    payment_method_data: &PaymentMethodData<T>,
    connector_config: &ConnectorSpecificConfig,
    currency: enums::Currency,
    amount: StringMajorUnit,
    resource_common_data: &PaymentFlowData,
    capture_method: Option<enums::CaptureMethod>,
    is_mandate: bool,
    metadata: Option<&serde_json::Value>,
    billing_descriptor: Option<&BillingDescriptor>,
) -> Result<PayloadCardsRequestData<T>, Error> {
    if let PaymentMethodData::Card(req_card) = payment_method_data {
        let payload_auth = PayloadAuth::try_from((connector_config, currency))?;

        let card = requests::PayloadCard {
            card: requests::PayloadCardData {
                card_number: req_card.card_number.clone(),
                expiry: req_card
                    .get_card_expiry_month_year_2_digit_with_delimiter("/".to_string())?,
                card_code: req_card.card_cvc.clone(),
            },
        };

        let billing_addr = resource_common_data.get_billing_address()?;

        let billing_address = Some(requests::BillingAddress {
            city: resource_common_data.get_billing_city()?,
            country_code: resource_common_data.get_billing_country()?,
            postal_code: billing_addr.zip.clone().ok_or(
                IntegrationError::MissingRequiredField {
                    field_name: "billing.address.zip",
                    context: Default::default(),
                },
            )?,
            state_province: billing_addr.state.clone().ok_or(
                IntegrationError::MissingRequiredField {
                    field_name: "billing.address.state",
                    context: Default::default(),
                },
            )?,
            street_address: resource_common_data.get_billing_line1()?,
        });

        let status = if is_manual_capture(capture_method) {
            Some(responses::PayloadPaymentStatus::Authorized)
        } else {
            None
        };

        Ok(PayloadCardsRequestData {
            amount,
            payment_method: requests::PayloadPaymentMethod {
                method: requests::PayloadPaymentMethods::Card(card),
                billing_address,
                keep_active: is_mandate,
            },
            transaction_types: requests::TransactionTypes::Payment,
            status,
            processing_id: get_processing_account_id_from_metadata(metadata)
                .or(payload_auth.processing_account_id),
            customer_id: resource_common_data.connector_customer.clone(),
            description: resource_common_data.description.clone(),
            descriptor: get_description_from_billing_descriptor(billing_descriptor),
            attrs: get_filtered_metadata(metadata),
        })
    } else {
        Err(IntegrationError::NotSupported {
            message: "Payment method".to_string(),
            connector: "Payload",
            context: Default::default(),
        }
        .into())
    }
}

#[allow(clippy::too_many_arguments)]
fn build_payload_bank_account_request_data<T: PaymentMethodDataTypes>(
    bank_debit_data: &BankDebitData,
    connector_config: &ConnectorSpecificConfig,
    currency: enums::Currency,
    amount: StringMajorUnit,
    capture_method: Option<enums::CaptureMethod>,
    resource_common_data: &PaymentFlowData,
    metadata: Option<&serde_json::Value>,
    billing_descriptor: Option<&BillingDescriptor>,
) -> Result<PayloadCardsRequestData<T>, Error> {
    match bank_debit_data {
        BankDebitData::AchBankDebit {
            account_number,
            routing_number,
            bank_account_holder_name,
            card_holder_name,
            bank_type,
            bank_holder_type,
            ..
        } => {
            let payload_auth = PayloadAuth::try_from((connector_config, currency))?;

            let account_holder = bank_account_holder_name
                .clone()
                .or_else(|| card_holder_name.clone())
                .or_else(|| resource_common_data.get_billing_full_name().ok())
                .ok_or(IntegrationError::MissingRequiredField {
                    field_name: "bank_account_holder_name",
                    context: Default::default(),
                })?;

            let account_type = match bank_type {
                Some(enums::BankType::Savings) => requests::PayloadBankAccountType::Savings,
                Some(enums::BankType::Checking) | None => {
                    requests::PayloadBankAccountType::Checking
                }
                Some(enums::BankType::Transmission)
                | Some(enums::BankType::Current)
                | Some(enums::BankType::Bond)
                | Some(enums::BankType::SubscriptionShare)
                | Some(enums::BankType::Salary)
                | Some(enums::BankType::Payment) => {
                    Err(error_stack::report!(IntegrationError::NotSupported {
                        message: format!(
                            "Bank type {:?} is not supported for ACH bank debit",
                            bank_type
                        ),
                        connector: "Payload",
                        context: Default::default(),
                    }))?
                }
            };

            // Mirror hyperswitch: derive account_class from the bank holder type
            // (serialised even when absent, matching the reference `"account_class": null`).
            let account_class = match bank_holder_type {
                Some(enums::BankHolderType::Business) => Some(requests::PayloadAccClass::Business),
                Some(enums::BankHolderType::Personal) => Some(requests::PayloadAccClass::Personal),
                None => None,
            };

            let bank = requests::PayloadBank {
                bank_account: requests::PayloadBankAccountInner {
                    account_class,
                    account_currency: currency.to_string(),
                    account_number: account_number.clone(),
                    account_type,
                    routing_number: routing_number.clone(),
                },
                account_holder,
            };

            // Billing address nested inside `payment_method` (mirrors the card builder
            // and the hyperswitch reference body).
            let billing_addr = resource_common_data.get_billing_address()?;
            let billing_address = Some(requests::BillingAddress {
                city: resource_common_data.get_billing_city()?,
                country_code: resource_common_data.get_billing_country()?,
                postal_code: billing_addr.zip.clone().ok_or(
                    IntegrationError::MissingRequiredField {
                        field_name: "billing.address.zip",
                        context: IntegrationErrorContext {
                            additional_context: Some(
                                "payload bank_debit authorize: billing address postal_code (zip) is required by the Payload /transactions API"
                                    .to_owned(),
                            ),
                            ..Default::default()
                        },
                    },
                )?,
                state_province: billing_addr.state.clone().ok_or(
                    IntegrationError::MissingRequiredField {
                        field_name: "billing.address.state",
                        context: IntegrationErrorContext {
                            additional_context: Some(
                                "payload bank_debit authorize: billing address state is required by the Payload /transactions API"
                                    .to_owned(),
                            ),
                            ..Default::default()
                        },
                    },
                )?,
                street_address: resource_common_data.get_billing_line1()?,
            });

            let status = if is_manual_capture(capture_method) {
                Some(responses::PayloadPaymentStatus::Authorized)
            } else {
                None
            };

            Ok(PayloadCardsRequestData {
                amount,
                payment_method: requests::PayloadPaymentMethod {
                    method: requests::PayloadPaymentMethods::BankAccount(bank),
                    billing_address,
                    keep_active: false,
                },
                transaction_types: requests::TransactionTypes::Payment,
                status,
                processing_id: get_processing_account_id_from_metadata(metadata)
                    .or(payload_auth.processing_account_id),
                customer_id: resource_common_data.connector_customer.clone(),
                description: resource_common_data.description.clone(),
                descriptor: get_description_from_billing_descriptor(billing_descriptor),
                attrs: get_filtered_metadata(metadata),
            })
        }
        BankDebitData::SepaBankDebit { .. }
        | BankDebitData::SepaGuaranteedBankDebit { .. }
        | BankDebitData::BecsBankDebit { .. }
        | BankDebitData::EftBankDebit { .. }
        | BankDebitData::BacsBankDebit { .. } => Err(IntegrationError::NotImplemented(
            domain_types::utils::get_unimplemented_payment_method_error_message("Payload"),
            Default::default(),
        )
        .into()),
    }
}

// TryFrom implementations for request bodies

// SetupMandate request
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        PayloadRouterData<
            RouterDataV2<
                SetupMandate,
                PaymentFlowData,
                SetupMandateRequestData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    > for PayloadCardsRequestData<T>
{
    type Error = Error;

    fn try_from(
        item: PayloadRouterData<
            RouterDataV2<
                SetupMandate,
                PaymentFlowData,
                SetupMandateRequestData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let router_data = &item.router_data;
        let metadata = router_data.request.metadata.clone().expose_option();

        match router_data.request.amount {
            Some(amount) if amount > 0 => Err(IntegrationError::FlowNotSupported {
                flow: "Setup mandate with non zero amount".to_string(),
                connector: "Payload".to_string(),
                context: Default::default(),
            }
            .into()),
            // NOTE: prism's SetupMandate is card-only today. If an ACH (bank
            // account) setup-mandate flow is ever added, its /payment_methods
            // request must NOT carry description/descriptor/attrs (HS PR #12710).
            _ => build_payload_card_request_data(
                &router_data.request.payment_method_data,
                &router_data.connector_config,
                router_data.request.currency,
                StringMajorUnit::zero(),
                &router_data.resource_common_data,
                None,
                true,
                metadata.as_ref(),
                router_data.request.billing_descriptor.as_ref(),
            ),
        }
    }
}

// Authorize request
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        PayloadRouterData<
            RouterDataV2<
                Authorize,
                PaymentFlowData,
                PaymentsAuthorizeData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    > for PayloadPaymentsRequest<T>
{
    type Error = Error;

    fn try_from(
        item: PayloadRouterData<
            RouterDataV2<
                Authorize,
                PaymentFlowData,
                PaymentsAuthorizeData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let router_data = &item.router_data;
        let metadata = router_data.request.metadata.clone().expose_option();

        // Convert amount using PayloadAmountConvertor
        let amount = PayloadAmountConvertor::convert(
            router_data.request.minor_amount,
            router_data.request.currency,
        )?;

        match &router_data.request.payment_method_data {
            PaymentMethodData::Card(_) => {
                let is_mandate = router_data.request.is_mandate_payment();

                let payment_data = build_payload_card_request_data(
                    &router_data.request.payment_method_data,
                    &router_data.connector_config,
                    router_data.request.currency,
                    amount,
                    &router_data.resource_common_data,
                    router_data.request.capture_method,
                    is_mandate,
                    metadata.as_ref(),
                    router_data.request.billing_descriptor.as_ref(),
                )?;

                Ok(Self::PayloadPaymentRequest(Box::new(payment_data)))
            }
            PaymentMethodData::BankDebit(bank_debit_data) => {
                let payment_data = build_payload_bank_account_request_data(
                    bank_debit_data,
                    &router_data.connector_config,
                    router_data.request.currency,
                    amount,
                    router_data.request.capture_method,
                    &router_data.resource_common_data,
                    metadata.as_ref(),
                    router_data.request.billing_descriptor.as_ref(),
                )?;

                Ok(Self::PayloadPaymentRequest(Box::new(payment_data)))
            }
            // Payload connector supports GooglePay and ApplePay wallets, but not yet integrated
            PaymentMethodData::Wallet(wallet_data) => match wallet_data {
                domain_types::payment_method_data::WalletData::GooglePay(_)
                | domain_types::payment_method_data::WalletData::ApplePay(_) => {
                    Err(IntegrationError::NotImplemented(
                        "Payment method".to_string(),
                        Default::default(),
                    )
                    .into())
                }
                _ => Err(IntegrationError::NotSupported {
                    message: "Wallet".to_string(),
                    connector: "Payload",
                    context: Default::default(),
                }
                .into()),
            },
            // Payload.js Secure Inputs return a payment_method_id (pm_xxx) that is
            // sent server-side to /transactions as a top-level `payment_method_id`
            // form field — same wire shape as the repeat-payment path.
            // Docs: https://docs.payload.com/ui/payloadjs/secure-input/handle-results/
            PaymentMethodData::PaymentMethodToken(t) => {
                let token = t.token.clone();

                let status = if is_manual_capture(router_data.request.capture_method) {
                    Some(responses::PayloadPaymentStatus::Authorized)
                } else {
                    None
                };

                Ok(Self::PayloadCardTokenRequest(Box::new(
                    requests::PayloadCardTokenRequestData {
                        amount,
                        transaction_types: requests::TransactionTypes::Payment,
                        payment_method_id: token,
                        status,
                    },
                )))
            }
            _ => Err(IntegrationError::NotSupported {
                message: "Payment method".to_string(),
                connector: "Payload",
                context: Default::default(),
            }
            .into()),
        }
    }
}

// Capture request
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        PayloadRouterData<
            RouterDataV2<Capture, PaymentFlowData, PaymentsCaptureData, PaymentsResponseData>,
            T,
        >,
    > for PayloadCaptureRequest
{
    type Error = Error;

    fn try_from(
        _item: PayloadRouterData<
            RouterDataV2<Capture, PaymentFlowData, PaymentsCaptureData, PaymentsResponseData>,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: responses::PayloadPaymentStatus::Processed,
        })
    }
}

// Void request
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        PayloadRouterData<
            RouterDataV2<Void, PaymentFlowData, PaymentVoidData, PaymentsResponseData>,
            T,
        >,
    > for PayloadVoidRequest
{
    type Error = Error;

    fn try_from(
        _item: PayloadRouterData<
            RouterDataV2<Void, PaymentFlowData, PaymentVoidData, PaymentsResponseData>,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: responses::PayloadPaymentStatus::Voided,
        })
    }
}

// RepeatPayment request - for recurring/mandate payments
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        PayloadRouterData<
            RouterDataV2<
                domain_types::connector_flow::RepeatPayment,
                PaymentFlowData,
                domain_types::connector_types::RepeatPaymentData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    > for PayloadRepeatPaymentRequest<T>
{
    type Error = Error;

    fn try_from(
        item: PayloadRouterData<
            RouterDataV2<
                domain_types::connector_flow::RepeatPayment,
                PaymentFlowData,
                domain_types::connector_types::RepeatPaymentData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let router_data = &item.router_data;

        // Convert amount using PayloadAmountConvertor
        let amount = PayloadAmountConvertor::convert(
            router_data.request.minor_amount,
            router_data.request.currency,
        )?;

        // For manual capture, set status to "authorized"
        let status = if is_manual_capture(router_data.request.capture_method) {
            Some(responses::PayloadPaymentStatus::Authorized)
        } else {
            None
        };

        // RepeatPayment flow requires a mandate reference
        let mandate_id = match &router_data.request.mandate_reference {
            domain_types::connector_types::MandateReferenceId::ConnectorMandateId(
                connector_mandate_ref,
            ) => connector_mandate_ref.get_connector_mandate_id().ok_or(
                IntegrationError::MissingRequiredField {
                    field_name: "connector_mandate_id",
                    context: Default::default(),
                },
            )?,
            _ => {
                return Err(IntegrationError::MissingRequiredField {
                    field_name: "connector_mandate_id for RepeatPayment",
                    context: Default::default(),
                }
                .into())
            }
        };

        let metadata = router_data.request.metadata.clone().expose_option();
        let processing_id = get_processing_account_id_from_metadata(metadata.as_ref());

        Ok(Self::PayloadMandateRequest(Box::new(
            requests::PayloadMandateRequestData {
                amount,
                transaction_types: requests::TransactionTypes::Payment,
                payment_method_id: Secret::new(mandate_id),
                status,
                processing_id,
                description: router_data.resource_common_data.description.clone(),
                descriptor: get_description_from_billing_descriptor(
                    router_data.request.billing_descriptor.as_ref(),
                ),
                attrs: get_filtered_metadata(metadata.as_ref()),
            },
        )))
    }
}

// Refund request
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        PayloadRouterData<
            RouterDataV2<Refund, RefundFlowData, RefundsData, RefundsResponseData>,
            T,
        >,
    > for PayloadRefundRequest
{
    type Error = Error;

    fn try_from(
        item: PayloadRouterData<
            RouterDataV2<Refund, RefundFlowData, RefundsData, RefundsResponseData>,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let router_data = &item.router_data;
        let connector_transaction_id = router_data.request.connector_transaction_id.clone();

        // Convert amount using PayloadAmountConvertor
        let amount = PayloadAmountConvertor::convert(
            router_data.request.minor_refund_amount,
            router_data.request.currency,
        )?;

        Ok(Self {
            transaction_type: requests::TransactionTypes::Refund,
            amount,
            ledger: vec![requests::PayloadRefundLedgerEntry {
                assoc_transaction_id: connector_transaction_id,
            }],
        })
    }
}

// TryFrom implementations for response bodies

impl From<responses::PayloadPaymentStatus> for common_enums::AttemptStatus {
    fn from(item: responses::PayloadPaymentStatus) -> Self {
        match item {
            responses::PayloadPaymentStatus::Authorized => Self::Authorized,
            responses::PayloadPaymentStatus::Processed => Self::Charged,
            responses::PayloadPaymentStatus::Processing => Self::Pending,
            responses::PayloadPaymentStatus::Rejected
            | responses::PayloadPaymentStatus::Declined => Self::Failure,
            responses::PayloadPaymentStatus::Voided => Self::Voided,
        }
    }
}

// Common function to handle PayloadPaymentsResponse
fn handle_payment_response<F, T>(
    response: PayloadPaymentsResponse,
    router_data: RouterDataV2<F, PaymentFlowData, T, PaymentsResponseData>,
    http_code: u16,
    is_mandate_payment: bool,
) -> RouterDataV2<F, PaymentFlowData, T, PaymentsResponseData> {
    match response {
        PayloadPaymentsResponse::PayloadCardsResponse(card_response) => {
            let status = common_enums::AttemptStatus::from(card_response.status);

            let mandate_reference = if is_mandate_payment {
                let connector_payment_method_id = card_response
                    .connector_payment_method_id
                    .clone()
                    .expose_option();
                connector_payment_method_id.map(|id| MandateReference {
                    connector_mandate_id: Some(id),
                    payment_method_id: None,
                    connector_mandate_request_reference_id: None,
                    mandate_metadata: None,
                })
            } else {
                None
            };

            let connector_response = card_response
                .avs
                .map(|avs_response| {
                    let payment_checks = serde_json::json!({
                        "avs_result": avs_response
                    });
                    AdditionalPaymentMethodConnectorResponse::Card {
                        authentication_data: None,
                        payment_checks: Some(payment_checks),
                        card_network: None,
                        domestic_network: None,
                        auth_code: None,
                    }
                })
                .map(ConnectorResponseData::with_additional_payment_method_data);

            let response_result = if status == common_enums::AttemptStatus::Failure {
                Err(ErrorResponse {
                    attempt_status: None,
                    code: card_response
                        .status_code
                        .clone()
                        .unwrap_or_else(|| NO_ERROR_CODE.to_string()),
                    message: card_response
                        .status_message
                        .clone()
                        .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
                    reason: card_response.status_message,
                    status_code: http_code,
                    connector_transaction_id: Some(card_response.transaction_id.clone()),
                    network_decline_code: None,
                    network_advice_code: None,
                    network_error_message: None,
                })
            } else {
                Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(card_response.transaction_id),
                    redirection_data: None,
                    mandate_reference: mandate_reference.map(Box::new),
                    connector_metadata: None,
                    network_txn_id: None,
                    network_txn_link_id: None,
                    connector_response_reference_id: card_response.ref_number,
                    incremental_authorization_allowed: None,
                    status_code: http_code,
                    splits: None,
                })
            };

            // Create a mutable copy to set the status
            let mut router_data_with_status = router_data;
            router_data_with_status
                .resource_common_data
                .set_status(status);

            RouterDataV2 {
                resource_common_data: PaymentFlowData {
                    connector_response,
                    ..router_data_with_status.resource_common_data
                },
                response: response_result,
                ..router_data_with_status
            }
        }
    }
}

// Authorize response
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<ResponseRouterData<PayloadPaymentsResponse, Self>>
    for RouterDataV2<Authorize, PaymentFlowData, PaymentsAuthorizeData<T>, PaymentsResponseData>
{
    type Error = ResponseError;

    fn try_from(
        item: ResponseRouterData<PayloadPaymentsResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let is_mandate_payment = item.router_data.request.is_mandate_payment();
        Ok(handle_payment_response(
            item.response,
            item.router_data,
            item.http_code,
            is_mandate_payment,
        ))
    }
}

// SetupMandate response
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<ResponseRouterData<PayloadPaymentsResponse, Self>>
    for RouterDataV2<
        SetupMandate,
        PaymentFlowData,
        SetupMandateRequestData<T>,
        PaymentsResponseData,
    >
{
    type Error = ResponseError;

    fn try_from(
        item: ResponseRouterData<PayloadPaymentsResponse, Self>,
    ) -> Result<Self, Self::Error> {
        // SetupMandate is always a mandate payment
        Ok(handle_payment_response(
            item.response,
            item.router_data,
            item.http_code,
            true,
        ))
    }
}

// RepeatPayment response - for recurring/mandate payments
impl<
        T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize + Serialize,
    > TryFrom<ResponseRouterData<PayloadPaymentsResponse, Self>>
    for RouterDataV2<
        domain_types::connector_flow::RepeatPayment,
        PaymentFlowData,
        domain_types::connector_types::RepeatPaymentData<T>,
        PaymentsResponseData,
    >
{
    type Error = ResponseError;

    fn try_from(
        item: ResponseRouterData<PayloadPaymentsResponse, Self>,
    ) -> Result<Self, Self::Error> {
        // RepeatPayment must return mandate_reference so HS's payment_response post-processing
        // can populate `mandate_reference_id` on the payment_attempt row.
        // See: hyperswitch/crates/router/src/core/payments/operations/payment_response.rs:3330
        // (HS legacy treats RepeatPayment as Authorize-with-mandate and extracts
        // `response.connector_payment_method_id` into MandateReference — we mirror that here).
        Ok(handle_payment_response(
            item.response,
            item.router_data,
            item.http_code,
            true,
        ))
    }
}

// PSync response
impl TryFrom<ResponseRouterData<PayloadPaymentsResponse, Self>>
    for RouterDataV2<
        domain_types::connector_flow::PSync,
        PaymentFlowData,
        domain_types::connector_types::PaymentsSyncData,
        PaymentsResponseData,
    >
{
    type Error = ResponseError;

    fn try_from(
        item: ResponseRouterData<PayloadPaymentsResponse, Self>,
    ) -> Result<Self, Self::Error> {
        Ok(handle_payment_response(
            item.response,
            item.router_data,
            item.http_code,
            false,
        ))
    }
}

// Capture response
impl TryFrom<ResponseRouterData<PayloadPaymentsResponse, Self>>
    for RouterDataV2<Capture, PaymentFlowData, PaymentsCaptureData, PaymentsResponseData>
{
    type Error = ResponseError;

    fn try_from(
        item: ResponseRouterData<PayloadPaymentsResponse, Self>,
    ) -> Result<Self, Self::Error> {
        Ok(handle_payment_response(
            item.response,
            item.router_data,
            item.http_code,
            false,
        ))
    }
}

// Void response
impl TryFrom<ResponseRouterData<PayloadPaymentsResponse, Self>>
    for RouterDataV2<Void, PaymentFlowData, PaymentVoidData, PaymentsResponseData>
{
    type Error = ResponseError;

    fn try_from(
        item: ResponseRouterData<PayloadPaymentsResponse, Self>,
    ) -> Result<Self, Self::Error> {
        Ok(handle_payment_response(
            item.response,
            item.router_data,
            item.http_code,
            false,
        ))
    }
}

// Refund status conversion
impl From<responses::RefundStatus> for enums::RefundStatus {
    fn from(item: responses::RefundStatus) -> Self {
        match item {
            responses::RefundStatus::Processed => Self::Success,
            responses::RefundStatus::Processing => Self::Pending,
            responses::RefundStatus::Declined | responses::RefundStatus::Rejected => Self::Failure,
        }
    }
}

// Refund response
impl TryFrom<ResponseRouterData<PayloadRefundResponse, Self>>
    for RouterDataV2<Refund, RefundFlowData, RefundsData, RefundsResponseData>
{
    type Error = ResponseError;

    fn try_from(
        item: ResponseRouterData<PayloadRefundResponse, Self>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.transaction_id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
                status_code: item.http_code,
                acquirer_reference_number: None,
            }),
            ..item.router_data
        })
    }
}

// Webhook helper function to parse incoming webhook events
pub fn parse_webhook_event(
    body: &[u8],
) -> Result<PayloadWebhookEvent, error_stack::Report<IntegrationError>> {
    serde_json::from_slice::<PayloadWebhookEvent>(body).change_context(
        IntegrationError::NotImplemented(
            "webhook body decoding failed".to_string(),
            Default::default(),
        ),
    )
}

// RSync response
impl TryFrom<ResponseRouterData<PayloadRefundResponse, Self>>
    for RouterDataV2<RSync, RefundFlowData, RefundSyncData, RefundsResponseData>
{
    type Error = ResponseError;

    fn try_from(
        item: ResponseRouterData<PayloadRefundResponse, Self>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.transaction_id.to_string(),
                refund_status: enums::RefundStatus::from(item.response.status),
                status_code: item.http_code,
                acquirer_reference_number: None,
            }),
            ..item.router_data
        })
    }
}

// Webhook event transformation
pub fn get_event_type_from_trigger(
    trigger: PayloadWebhooksTrigger,
) -> domain_types::connector_types::EventType {
    match trigger {
        // Payment Success Events
        PayloadWebhooksTrigger::Processed => {
            domain_types::connector_types::EventType::PaymentIntentSuccess
        }
        PayloadWebhooksTrigger::Authorized => {
            domain_types::connector_types::EventType::PaymentIntentAuthorizationSuccess
        }
        // Payment Processing Events
        PayloadWebhooksTrigger::Payment | PayloadWebhooksTrigger::AutomaticPayment => {
            domain_types::connector_types::EventType::PaymentIntentProcessing
        }
        // Payment Failure Events
        PayloadWebhooksTrigger::Decline
        | PayloadWebhooksTrigger::Reject
        | PayloadWebhooksTrigger::BankAccountReject => {
            domain_types::connector_types::EventType::PaymentIntentFailure
        }
        PayloadWebhooksTrigger::Void | PayloadWebhooksTrigger::Reversal => {
            domain_types::connector_types::EventType::PaymentIntentCancelled
        }
        // Refund Events
        PayloadWebhooksTrigger::Refund => domain_types::connector_types::EventType::RefundSuccess,
        // Dispute Events
        PayloadWebhooksTrigger::Chargeback => {
            domain_types::connector_types::EventType::DisputeOpened
        }
        PayloadWebhooksTrigger::ChargebackReversal => {
            domain_types::connector_types::EventType::DisputeWon
        }
        // Other payment-related events - treat as generic payment processing
        PayloadWebhooksTrigger::PaymentActivationStatus
        | PayloadWebhooksTrigger::Credit
        | PayloadWebhooksTrigger::Deposit
        | PayloadWebhooksTrigger::PaymentLinkStatus
        | PayloadWebhooksTrigger::ProcessingStatus
        | PayloadWebhooksTrigger::TransactionOperation
        | PayloadWebhooksTrigger::TransactionOperationClear => {
            domain_types::connector_types::EventType::PaymentIntentProcessing
        }
    }
}

// ClientAuthenticationToken request — POST /access_tokens
#[derive(Debug, Serialize)]
pub struct PayloadClientAuthRequest {
    #[serde(rename = "type")]
    pub token_type: String,
    pub intent: PayloadClientAuthIntent,
}

#[derive(Debug, Serialize)]
pub struct PayloadClientAuthIntent {
    pub payment_form: PayloadClientAuthPaymentForm,
}

#[derive(Debug, Serialize)]
pub struct PayloadClientAuthPaymentForm {
    pub payment: PayloadClientAuthPayment,
}

#[derive(Debug, Serialize)]
pub struct PayloadClientAuthPayment {
    pub amount: StringMajorUnit,
    pub description: String,
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        PayloadRouterData<
            RouterDataV2<
                ClientAuthenticationToken,
                MerchantAuthenticationFlowData,
                ClientAuthenticationTokenRequestData,
                PaymentsResponseData,
            >,
            T,
        >,
    > for PayloadClientAuthRequest
{
    type Error = Error;
    fn try_from(
        item: PayloadRouterData<
            RouterDataV2<
                ClientAuthenticationToken,
                MerchantAuthenticationFlowData,
                ClientAuthenticationTokenRequestData,
                PaymentsResponseData,
            >,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let router_data = item.router_data;

        let amount = PayloadAmountConvertor::convert(
            router_data.request.amount,
            router_data.request.currency,
        )?;

        let description = router_data
            .resource_common_data
            .connector_request_reference_id
            .clone();

        Ok(Self {
            token_type: "client".to_string(),
            intent: PayloadClientAuthIntent {
                payment_form: PayloadClientAuthPaymentForm {
                    payment: PayloadClientAuthPayment {
                        amount,
                        description,
                    },
                },
            },
        })
    }
}

// ClientAuthenticationToken response
#[derive(Debug, Deserialize, Serialize)]
pub struct PayloadClientAuthResponse {
    pub id: Secret<String>,
}

impl TryFrom<ResponseRouterData<PayloadClientAuthResponse, Self>>
    for RouterDataV2<
        ClientAuthenticationToken,
        MerchantAuthenticationFlowData,
        ClientAuthenticationTokenRequestData,
        PaymentsResponseData,
    >
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<PayloadClientAuthResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let response = item.response;

        let session_data = ClientAuthenticationTokenData::ConnectorSpecific(Box::new(
            ConnectorSpecificClientAuthenticationResponse::Payload(
                PayloadClientAuthenticationResponseDomain {
                    client_token: response.id,
                },
            ),
        ));

        Ok(Self {
            response: Ok(PaymentsResponseData::ClientAuthenticationTokenResponse {
                session_data,
                status_code: item.http_code,
            }),
            ..item.router_data
        })
    }
}

// ===== CREATE CONNECTOR CUSTOMER =====
// Mirrors the hyperswitch reference at
// hyperswitch/crates/hyperswitch_connectors/src/connectors/payload/transformers.rs
// (TryFrom<&ConnectorCustomerRouterData> for CustomerRequest). UCS wires this
// ahead of Authorize via ValidationTrait::should_create_connector_customer().
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        PayloadRouterData<
            RouterDataV2<
                CreateConnectorCustomer,
                PaymentFlowData,
                ConnectorCustomerData,
                ConnectorCustomerResponse,
            >,
            T,
        >,
    > for PayloadCustomerRequest
{
    type Error = Error;

    fn try_from(
        item: PayloadRouterData<
            RouterDataV2<
                CreateConnectorCustomer,
                PaymentFlowData,
                ConnectorCustomerData,
                ConnectorCustomerResponse,
            >,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let router_data = &item.router_data;

        let email = router_data.request.get_email()?;
        let name = router_data.request.get_name()?;

        let primary_processing_id =
            PayloadAuthType::try_from(&router_data.connector_config)?.primary_processing_id();

        Ok(Self {
            // `keep_active` controls whether the saved payment methods on this
            // customer stay active. UCS calls CreateConnectorCustomer ahead of
            // Authorize before we know if this is a mandate, so default to
            // false (one-time). Subsequent Authorize-time `keep_active` is set
            // independently on the transaction request.
            keep_active: false,
            email,
            name,
            primary_processing_id,
        })
    }
}

impl TryFrom<ResponseRouterData<PayloadCustomerResponse, Self>>
    for RouterDataV2<
        CreateConnectorCustomer,
        PaymentFlowData,
        ConnectorCustomerData,
        ConnectorCustomerResponse,
    >
{
    type Error = ResponseError;

    fn try_from(
        item: ResponseRouterData<PayloadCustomerResponse, Self>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(ConnectorCustomerResponse {
                connector_customer_id: item.response.id,
                status_code: item.http_code,
            }),
            ..item.router_data
        })
    }
}
