// File: crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs

use common_enums::{enums, CountryAlpha2, UsStatesAbbreviation};
use common_utils::{
    id_type,
    pii::{self, IpAddress},
    types::MinorUnit,
};
use hyperswitch_domain_models::{
    address::AddressDetails,
    payment_method_data::{BankDebitData, PaymentMethodData},
    router_data::{ConnectorAuthType, PaymentMethodToken, RouterData},
    router_flow_types::refunds::Execute,
    router_request_types::{
        ConnectorCustomerData, PaymentMethodTokenizationData, ResponseId, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorCustomerResponseData, MandateReference, PaymentsResponseData, RefundsResponseData,
    },
    types,
};
use hyperswitch_interfaces::errors;
use masking::{ExposeInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{
        PaymentsResponseRouterData, PaymentsSyncResponseRouterData, RefundsResponseRouterData,
        ResponseRouterData,
    },
    utils::{
        self, AddressDetailsData, BrowserInformationData, CustomerData, ForeignTryFrom,
        PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, RouterData as _,
    },
};

pub struct GocardlessRouterData<T> {
    pub amount: MinorUnit,
    pub router_data: T,
}

impl<T> From<(MinorUnit, T)> for GocardlessRouterData<T> {
    fn from((amount, item): (MinorUnit, T)) -> Self {
        Self {
            amount,
            router_data: item,
        }
    }
}

#[derive(Default, Debug, Serialize)]
pub struct GocardlessCustomerRequest {
    customers: GocardlessCustomer,
}

#[derive(Default, Debug, Serialize)]
pub struct GocardlessCustomer {
    address_line1: Option<Secret<String>>,
    address_line2: Option<Secret<String>>,
    address_line3: Option<Secret<String>>,
    city: Option<Secret<String>>,
    region: Option<Secret<String>>,
    country_code: Option<CountryAlpha2>,
    email: pii::Email,
    given_name: Secret<String>,
    family_name: Secret<String>,
    metadata: CustomerMetaData,
    danish_identity_number: Option<Secret<String>>,
    postal_code: Option<Secret<String>>,
    swedish_identity_number: Option<Secret<String>>,
}

#[derive(Default, Debug, Serialize)]
pub struct CustomerMetaData {
    crm_id: Option<Secret<id_type::CustomerId>>,
}

impl TryFrom<&types::ConnectorCustomerRouterData> for GocardlessCustomerRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::ConnectorCustomerRouterData) -> Result<Self, Self::Error> {
        let email = item.request.get_email()?;
        let billing_details_name = item.get_billing_full_name()?.expose();

        let (given_name, family_name) = billing_details_name
            .trim()
            .rsplit_once(' ')
            .unwrap_or((&billing_details_name, &billing_details_name));

        let billing_address = item.get_billing_address()?;

        let metadata = CustomerMetaData {
            crm_id: item.customer_id.clone().map(Secret::new),
        };
        let region = get_region(billing_address)?;
        Ok(Self {
            customers: GocardlessCustomer {
                email,
                given_name: Secret::new(given_name.to_string()),
                family_name: Secret::new(family_name.to_string()),
                metadata,
                address_line1: billing_address.line1.to_owned(),
                address_line2: billing_address.line2.to_owned(),
                address_line3: billing_address.line3.to_owned(),
                country_code: billing_address.country,
                region,
                // Should be populated based on the billing country
                danish_identity_number: None,
                postal_code: billing_address.zip.to_owned(),
                // Should be populated based on the billing country
                swedish_identity_number: None,
                city: billing_address.city.clone().map(Secret::new),
            },
        })
    }
}

fn get_region(
    address_details: &AddressDetails,
) -> Result<Option<Secret<String>>, error_stack::Report<errors::ConnectorError>> {
    match address_details.country {
        Some(CountryAlpha2::US) => {
            let state = address_details.get_state()?.to_owned();
            Ok(Some(Secret::new(
                UsStatesAbbreviation::foreign_try_from(state.expose())?.to_string(),
            )))
        }
        _ => Ok(None),
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GocardlessCustomerResponse {
    customers: Customers,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Customers {
    id: Secret<String>,
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            GocardlessCustomerResponse,
            ConnectorCustomerData,
            PaymentsResponseData,
        >,
    > for RouterData<F, ConnectorCustomerData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            GocardlessCustomerResponse,
            ConnectorCustomerData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::ConnectorCustomerResponse(
                ConnectorCustomerResponseData::new_with_customer_id(
                    item.response.customers.id.expose(),
                ),
            )),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
pub struct GocardlessBankAccountRequest {
    customer_bank_accounts: CustomerBankAccounts,
}

#[derive(Debug, Serialize)]
pub struct CustomerBankAccounts {
    #[serde(flatten)]
    accounts: CustomerBankAccount,
    links: CustomerAccountLink,
}

#[derive(Debug, Serialize)]
pub struct CustomerAccountLink {
    customer: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum CustomerBankAccount {
    InternationalBankAccount(InternationalBankAccount),
    AUBankAccount(AUBankAccount),
    USBankAccount(USBankAccount),
}

#[derive(Debug, Serialize)]
pub struct InternationalBankAccount {
    iban: Secret<String>,
    account_holder_name: Secret<String>,
}

#[derive(Debug, Serialize)]
pub struct AUBankAccount {
    country_code: CountryAlpha2,
    account_number: Secret<String>,
    branch_code: Secret<String>,
    account_holder_name: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub struct USBankAccount {
    country_code: CountryAlpha2,
    account_number: Secret<String>,
    bank_code: Secret<String>,
    account_type: AccountType,
    account_holder_name: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum AccountType {
    Checking,
    Savings,
}

impl TryFrom<&types::TokenizationRouterData> for GocardlessBankAccountRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
        let customer = item.get_connector_customer_id()?;
        let accounts = CustomerBankAccount::try_from(item)?;
        let links = CustomerAccountLink {
            customer: Secret::new(customer),
        };
        Ok(Self {
            customer_bank_accounts: CustomerBankAccounts { accounts, links },
        })
    }
}

impl TryFrom<&types::TokenizationRouterData> for CustomerBankAccount {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::TokenizationRouterData) -> Result<Self, Self::Error> {
        match &item.request.payment_method_data {
            PaymentMethodData::BankDebit(bank_debit_data) => {
                Self::try_from((bank_debit_data, item))
            }
            PaymentMethodData::Card(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Gocardless"),
                )
                .into())
            }
        }
    }
}

impl TryFrom<(&BankDebitData, &types::TokenizationRouterData)> for CustomerBankAccount {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (bank_debit_data, item): (&BankDebitData, &types::TokenizationRouterData),
    ) -> Result<Self, Self::Error> {
        match bank_debit_data {
            BankDebitData::AchBankDebit {
                account_number,
                routing_number,
                bank_type,
                ..
            } => {
                let bank_type = bank_type.ok_or_else(utils::missing_field_err("bank_type"))?;
                let country_code = item.get_billing_country()?;
                let account_holder_name = item.get_billing_full_name()?;
                let us_bank_account = USBankAccount {
                    country_code,
                    account_number: account_number.clone(),
                    bank_code: routing_number.clone(),
                    account_type: AccountType::from(bank_type),
                    account_holder_name,
                };
                Ok(Self::USBankAccount(us_bank_account))
            }
            BankDebitData::BecsBankDebit {
                account_number,
                bsb_number,
                ..
            } => {
                let country_code = item.get_billing_country()?;
                let account_holder_name = item.get_billing_full_name()?;
                let au_bank_account = AUBankAccount {
                    country_code,
                    account_number: account_number.clone(),
                    branch_code: bsb_number.clone(),
                    account_holder_name,
                };
                Ok(Self::AUBankAccount(au_bank_account))
            }
            BankDebitData::SepaBankDebit { iban, .. } => {
                let account_holder_name = item.get_billing_full_name()?;
                let international_bank_account = InternationalBankAccount {
                    iban: iban.clone(),
                    account_holder_name,
                };
                Ok(Self::InternationalBankAccount(international_bank_account))
            }
            BankDebitData::BacsBankDebit { .. } | BankDebitData::SepaGuarenteedBankDebit { .. } => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Gocardless"),
                )
                .into())
            }
        }
    }
}

impl From<common_enums::BankType> for AccountType {
    fn from(item: common_enums::BankType) -> Self {
        match item {
            common_enums::BankType::Checking => Self::Checking,
            common_enums::BankType::Savings => Self::Savings,
        }
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GocardlessBankAccountResponse {
    customer_bank_accounts: CustomerBankAccountResponse,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CustomerBankAccountResponse {
    pub id: Secret<String>,
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            GocardlessBankAccountResponse,
            PaymentMethodTokenizationData,
            PaymentsResponseData,
        >,
    > for RouterData<F, PaymentMethodTokenizationData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            GocardlessBankAccountResponse,
            PaymentMethodTokenizationData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::TokenizationResponse {
                token: item.response.customer_bank_accounts.id.expose(),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
pub struct GocardlessMandateRequest {
    mandates: Mandate,
}

#[derive(Debug, Serialize)]
pub struct Mandate {
    scheme: GocardlessScheme,
    metadata: MandateMetaData,
    payer_ip_address: Option<Secret<String, IpAddress>>,
    links: MandateLink,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GocardlessScheme {
    Becs,
    SepaCore,
    Ach,
    BecsNz,
}

#[derive(Debug, Serialize)]
pub struct MandateMetaData {
    payment_reference: String,
}

#[derive(Debug, Serialize)]
pub struct MandateLink {
    customer_bank_account: Secret<String>,
}

impl TryFrom<&types::SetupMandateRouterData> for GocardlessMandateRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::SetupMandateRouterData) -> Result<Self, Self::Error> {
        let (scheme, payer_ip_address) = match &item.request.payment_method_data {
            PaymentMethodData::BankDebit(bank_debit_data) => {
                let payer_ip_address = get_ip_if_required(bank_debit_data, item)?;
                Ok((
                    GocardlessScheme::try_from(bank_debit_data)?,
                    payer_ip_address,
                ))
            }
            PaymentMethodData::Card(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    "Setup Mandate flow for selected payment method through Gocardless".to_string(),
                ))
            }
        }?;
        let payment_method_token = item.get_payment_method_token()?;
        let customer_bank_account = match payment_method_token {
            PaymentMethodToken::Token(token) => Ok(token),
            PaymentMethodToken::ApplePayDecrypt(_)
            | PaymentMethodToken::PazeDecrypt(_)
            | PaymentMethodToken::GooglePayDecrypt(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    "Setup Mandate flow for selected payment method through Gocardless".to_string(),
                ))
            }
        }?;
        Ok(Self {
            mandates: Mandate {
                scheme,
                metadata: MandateMetaData {
                    payment_reference: item.connector_request_reference_id.clone(),
                },
                payer_ip_address,
                links: MandateLink {
                    customer_bank_account,
                },
            },
        })
    }
}

fn get_ip_if_required(
    bank_debit_data: &BankDebitData,
    item: &types::SetupMandateRouterData,
) -> Result<Option<Secret<String, IpAddress>>, error_stack::Report<errors::ConnectorError>> {
    let ip_address = item.request.get_browser_info()?.get_ip_address()?;
    match bank_debit_data {
        BankDebitData::AchBankDebit { .. } => Ok(Some(ip_address)),
        BankDebitData::SepaBankDebit { .. }
        | BankDebitData::SepaGuarenteedBankDebit { .. }
        | BankDebitData::BecsBankDebit { .. }
        | BankDebitData::BacsBankDebit { .. } => Ok(None),
    }
}

impl TryFrom<&BankDebitData> for GocardlessScheme {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &BankDebitData) -> Result<Self, Self::Error> {
        match item {
            BankDebitData::AchBankDebit { .. } => Ok(Self::Ach),
            BankDebitData::SepaBankDebit { .. } => Ok(Self::SepaCore),
            BankDebitData::BecsBankDebit { .. } => Ok(Self::Becs),
            BankDebitData::BacsBankDebit { .. } | BankDebitData::SepaGuarenteedBankDebit { .. } => {
                Err(errors::ConnectorError::NotImplemented(
                    "Setup Mandate flow for selected payment method through Gocardless".to_string(),
                )
                .into())
            }
        }
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GocardlessMandateResponse {
    mandates: MandateResponse,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MandateResponse {
    id: Secret<String>,
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            GocardlessMandateResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    > for RouterData<F, SetupMandateRequestData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            GocardlessMandateResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let mandate_reference = Some(MandateReference {
            connector_mandate_id: Some(item.response.mandates.id.clone().expose()),
            payment_method_id: None,
            mandate_metadata: None,
            connector_mandate_request_reference_id: None,
        });
        Ok(Self {
            response: Ok(PaymentsResponseData::TransactionResponse {
                connector_metadata: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                resource_id: ResponseId::NoResponseId,
                redirection_data: Box::new(None),
                mandate_reference: Box::new(mandate_reference),
                network_txn_id: None,
                charges: None,
            }),
            status: enums::AttemptStatus::Charged,
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
pub struct GocardlessPaymentsRequest {
    payments: GocardlessPayment,
}

#[derive(Debug, Serialize)]
pub struct GocardlessPayment {
    amount: MinorUnit,
    currency: enums::Currency,
    description: Option<String>,
    metadata: PaymentMetaData,
    links: PaymentLink,
}

#[derive(Debug, Serialize)]
pub struct PaymentMetaData {
    payment_reference: String,
}

#[derive(Debug, Serialize)]
pub struct PaymentLink {
    mandate: Secret<String>,
}

impl TryFrom<&GocardlessRouterData<&types::PaymentsAuthorizeRouterData>>
    for GocardlessPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &GocardlessRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let mandate_id = if item.router_data.request.is_mandate_payment() {
            item.router_data
                .request
                .connector_mandate_id()
                .ok_or_else(utils::missing_field_err("mandate_id"))
        } else {
            Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("gocardless"),
            )
            .into())
        }?;
        let payments = GocardlessPayment {
            amount: item.router_data.request.minor_amount,
            currency: item.router_data.request.currency,
            description: item.router_data.description.clone(),
            metadata: PaymentMetaData {
                payment_reference: item.router_data.connector_request_reference_id.clone(),
            },
            links: PaymentLink {
                mandate: Secret::new(mandate_id),
            },
        };
        Ok(Self { payments })
    }
}

// Auth Struct
pub struct GocardlessAuthType {
    pub(super) access_token: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for GocardlessAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                access_token: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GocardlessPaymentStatus {
    PendingCustomerApproval,
    PendingSubmission,
    Submitted,
    Confirmed,
    PaidOut,
    Cancelled,
    CustomerApprovalDenied,
    Failed,
}

impl From<GocardlessPaymentStatus> for enums::AttemptStatus {
    fn from(item: GocardlessPaymentStatus) -> Self {
        match item {
            GocardlessPaymentStatus::PendingCustomerApproval
            | GocardlessPaymentStatus::PendingSubmission
            | GocardlessPaymentStatus::Submitted => Self::Pending,
            GocardlessPaymentStatus::Confirmed | GocardlessPaymentStatus::PaidOut => Self::Charged,
            GocardlessPaymentStatus::Cancelled => Self::Voided,
            GocardlessPaymentStatus::CustomerApprovalDenied => Self::AuthenticationFailed,
            GocardlessPaymentStatus::Failed => Self::Failure,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GocardlessPaymentsResponse {
    payments: PaymentResponse,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentResponse {
    status: GocardlessPaymentStatus,
    id: String,
}

impl TryFrom<PaymentsResponseRouterData<GocardlessPaymentsResponse>>
    for types::PaymentsAuthorizeRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsResponseRouterData<GocardlessPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        let mandate_reference = MandateReference {
            connector_mandate_id: Some(item.data.request.get_connector_mandate_id()?),
            payment_method_id: None,
            mandate_metadata: None,
            connector_mandate_request_reference_id: None,
        };
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.payments.status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.payments.id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(Some(mandate_reference)),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl TryFrom<PaymentsSyncResponseRouterData<GocardlessPaymentsResponse>>
    for types::PaymentsSyncRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsSyncResponseRouterData<GocardlessPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.payments.status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.payments.id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

// REFUND :
#[derive(Default, Debug, Serialize)]
pub struct GocardlessRefundRequest {
    refunds: GocardlessRefund,
}

#[derive(Default, Debug, Serialize)]
pub struct GocardlessRefund {
    amount: MinorUnit,
    metadata: RefundMetaData,
    links: RefundLink,
}

#[derive(Default, Debug, Serialize)]
pub struct RefundMetaData {
    refund_reference: String,
}

#[derive(Default, Debug, Serialize)]
pub struct RefundLink {
    payment: String,
}

impl<F> TryFrom<&GocardlessRouterData<&types::RefundsRouterData<F>>> for GocardlessRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &GocardlessRouterData<&types::RefundsRouterData<F>>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            refunds: GocardlessRefund {
                amount: item.amount.to_owned(),
                metadata: RefundMetaData {
                    refund_reference: item.router_data.connector_request_reference_id.clone(),
                },
                links: RefundLink {
                    payment: item.router_data.request.connector_transaction_id.clone(),
                },
            },
        })
    }
}

#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct RefundResponse {
    id: String,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>>
    for types::RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, RefundResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id.to_string(),
                refund_status: enums::RefundStatus::Pending,
            }),
            ..item.data
        })
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GocardlessErrorResponse {
    pub error: GocardlessError,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GocardlessError {
    pub message: String,
    pub code: u16,
    pub errors: Vec<Error>,
    #[serde(rename = "type")]
    pub error_type: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Error {
    pub field: Option<String>,
    pub message: String,
}

#[derive(Debug, Deserialize)]
pub struct GocardlessWebhookEvent {
    pub events: Vec<WebhookEvent>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WebhookEvent {
    pub resource_type: WebhookResourceType,
    pub action: WebhookAction,
    pub links: WebhooksLink,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WebhookResourceType {
    Payments,
    Refunds,
    Mandates,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum WebhookAction {
    PaymentsAction(PaymentsAction),
    RefundsAction(RefundsAction),
    MandatesAction(MandatesAction),
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PaymentsAction {
    Created,
    CustomerApprovalGranted,
    CustomerApprovalDenied,
    Submitted,
    Confirmed,
    PaidOut,
    LateFailureSettled,
    SurchargeFeeDebited,
    Failed,
    Cancelled,
    ResubmissionRequired,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RefundsAction {
    Created,
    Failed,
    Paid,
    // Payout statuses
    RefundSettled,
    FundsReturned,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MandatesAction {
    Created,
    CustomerApprovalGranted,
    CustomerApprovalSkipped,
    Active,
    Cancelled,
    Failed,
    Transferred,
    Expired,
    Submitted,
    ResubmissionRequested,
    Reinstated,
    Replaced,
    Consumed,
    Blocked,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum WebhooksLink {
    PaymentWebhooksLink(PaymentWebhooksLink),
    RefundWebhookLink(RefundWebhookLink),
    MandateWebhookLink(MandateWebhookLink),
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RefundWebhookLink {
    pub refund: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PaymentWebhooksLink {
    pub payment: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MandateWebhookLink {
    pub mandate: String,
}

impl TryFrom<&WebhookEvent> for GocardlessPaymentsResponse {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &WebhookEvent) -> Result<Self, Self::Error> {
        let id = match &item.links {
            WebhooksLink::PaymentWebhooksLink(link) => link.payment.to_owned(),
            WebhooksLink::RefundWebhookLink(_) | WebhooksLink::MandateWebhookLink(_) => {
                Err(errors::ConnectorError::WebhookEventTypeNotFound)?
            }
        };
        Ok(Self {
            payments: PaymentResponse {
                status: GocardlessPaymentStatus::try_from(&item.action)?,
                id,
            },
        })
    }
}

impl TryFrom<&WebhookAction> for GocardlessPaymentStatus {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &WebhookAction) -> Result<Self, Self::Error> {
        match item {
            WebhookAction::PaymentsAction(action) => match action {
                PaymentsAction::CustomerApprovalGranted | PaymentsAction::Submitted => {
                    Ok(Self::Submitted)
                }
                PaymentsAction::CustomerApprovalDenied => Ok(Self::CustomerApprovalDenied),
                PaymentsAction::LateFailureSettled => Ok(Self::Failed),
                PaymentsAction::Failed => Ok(Self::Failed),
                PaymentsAction::Cancelled => Ok(Self::Cancelled),
                PaymentsAction::Confirmed => Ok(Self::Confirmed),
                PaymentsAction::PaidOut => Ok(Self::PaidOut),
                PaymentsAction::SurchargeFeeDebited
                | PaymentsAction::ResubmissionRequired
                | PaymentsAction::Created => Err(errors::ConnectorError::WebhookEventTypeNotFound)?,
            },
            WebhookAction::RefundsAction(_) | WebhookAction::MandatesAction(_) => {
                Err(errors::ConnectorError::WebhookEventTypeNotFound)?
            }
        }
    }
}


// File: crates/hyperswitch_connectors/src/connectors/netcetera/transformers.rs

use common_enums::enums;
use common_utils::{ext_traits::OptionExt as _, types::SemanticVersion};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    router_data::{ConnectorAuthType, ErrorResponse},
    router_flow_types::authentication::{Authentication, PreAuthentication},
    router_request_types::authentication::{
        AuthNFlowType, ChallengeParams, ConnectorAuthenticationRequestData,
        MessageExtensionAttribute, PreAuthNRequestData,
    },
    router_response_types::AuthenticationResponseData,
};
use hyperswitch_interfaces::{api::CurrencyUnit, errors::ConnectorError};
use masking::Secret;
use serde::{Deserialize, Serialize};

use super::netcetera_types;
use crate::{
    types::{ConnectorAuthenticationRouterData, PreAuthNRouterData, ResponseRouterData},
    utils::{get_card_details, to_connector_meta_from_secret, CardData as _},
};

//TODO: Fill the struct with respective fields
pub struct NetceteraRouterData<T> {
    pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
    pub router_data: T,
}

impl<T> TryFrom<(&CurrencyUnit, enums::Currency, i64, T)> for NetceteraRouterData<T> {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        (_currency_unit, _currency, amount, item): (&CurrencyUnit, enums::Currency, i64, T),
    ) -> Result<Self, Self::Error> {
        //Todo :  use utils to convert the amount to the type of amount that a connector accepts
        Ok(Self {
            amount,
            router_data: item,
        })
    }
}

impl<T> TryFrom<(i64, T)> for NetceteraRouterData<T> {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from((amount, router_data): (i64, T)) -> Result<Self, Self::Error> {
        Ok(Self {
            amount,
            router_data,
        })
    }
}

impl
    TryFrom<
        ResponseRouterData<
            PreAuthentication,
            NetceteraPreAuthenticationResponse,
            PreAuthNRequestData,
            AuthenticationResponseData,
        >,
    > for PreAuthNRouterData
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            PreAuthentication,
            NetceteraPreAuthenticationResponse,
            PreAuthNRequestData,
            AuthenticationResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let response = match item.response {
            NetceteraPreAuthenticationResponse::Success(pre_authn_response) => {
                // if card is not enrolled for 3ds, card_range will be None
                let card_range = pre_authn_response.get_card_range_if_available();
                let maximum_supported_3ds_version = card_range
                    .as_ref()
                    .map(|card_range| card_range.highest_common_supported_version.clone())
                    .unwrap_or_else(|| {
                        // Version "0.0.0" will be less that "2.0.0", hence we will treat this card as not eligible for 3ds authentication
                        SemanticVersion::new(0, 0, 0)
                    });
                let three_ds_method_data = card_range.as_ref().and_then(|card_range| {
                    card_range
                        .three_ds_method_data_form
                        .as_ref()
                        .map(|data| data.three_ds_method_data.clone())
                });
                let three_ds_method_url = card_range
                    .as_ref()
                    .and_then(|card_range| card_range.get_three_ds_method_url());
                Ok(AuthenticationResponseData::PreAuthNResponse {
                    threeds_server_transaction_id: pre_authn_response
                        .three_ds_server_trans_id
                        .clone(),
                    maximum_supported_3ds_version: maximum_supported_3ds_version.clone(),
                    connector_authentication_id: pre_authn_response.three_ds_server_trans_id,
                    three_ds_method_data,
                    three_ds_method_url,
                    message_version: maximum_supported_3ds_version,
                    connector_metadata: None,
                    directory_server_id: card_range
                        .as_ref()
                        .and_then(|card_range| card_range.directory_server_id.clone()),
                })
            }
            NetceteraPreAuthenticationResponse::Failure(error_response) => Err(ErrorResponse {
                code: error_response.error_details.error_code,
                message: error_response.error_details.error_description,
                reason: error_response.error_details.error_detail,
                status_code: item.http_code,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
        };
        Ok(Self {
            response,
            ..item.data.clone()
        })
    }
}

impl
    TryFrom<
        ResponseRouterData<
            Authentication,
            NetceteraAuthenticationResponse,
            ConnectorAuthenticationRequestData,
            AuthenticationResponseData,
        >,
    > for ConnectorAuthenticationRouterData
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            Authentication,
            NetceteraAuthenticationResponse,
            ConnectorAuthenticationRequestData,
            AuthenticationResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let response = match item.response {
            NetceteraAuthenticationResponse::Success(response) => {
                let authn_flow_type = match response.acs_challenge_mandated {
                    Some(ACSChallengeMandatedIndicator::Y) => {
                        AuthNFlowType::Challenge(Box::new(ChallengeParams {
                            acs_url: response.authentication_response.acs_url.clone(),
                            challenge_request: response.encoded_challenge_request,
                            acs_reference_number: response
                                .authentication_response
                                .acs_reference_number,
                            acs_trans_id: response.authentication_response.acs_trans_id,
                            three_dsserver_trans_id: Some(response.three_ds_server_trans_id),
                            acs_signed_content: response.authentication_response.acs_signed_content,
                            challenge_request_key: None,
                        }))
                    }
                    Some(ACSChallengeMandatedIndicator::N) | None => AuthNFlowType::Frictionless,
                };

                let challenge_code = response
                    .authentication_request
                    .as_ref()
                    .and_then(|req| req.three_ds_requestor_challenge_ind.as_ref())
                    .and_then(|ind| match ind {
                        ThreedsRequestorChallengeInd::Single(s) => Some(s.clone()),
                        ThreedsRequestorChallengeInd::Multiple(v) => v.first().cloned(),
                    });

                let message_extension = response
                    .authentication_response
                    .message_extension
                    .as_ref()
                    .and_then(|v| match serde_json::to_value(v) {
                        Ok(val) => Some(Secret::new(val)),
                        Err(e) => {
                            router_env::logger::error!(
                                "Failed to serialize message_extension: {:?}",
                                e
                            );
                            None
                        }
                    });

                Ok(AuthenticationResponseData::AuthNResponse {
                    authn_flow_type,
                    authentication_value: response.authentication_value,
                    trans_status: response.trans_status,
                    connector_metadata: None,
                    ds_trans_id: response.authentication_response.ds_trans_id,
                    eci: response.eci,
                    challenge_code,
                    challenge_cancel: None, // Note - challenge_cancel field is received in the RReq and updated in DB during external_authentication_incoming_webhook_flow
                    challenge_code_reason: response.authentication_response.trans_status_reason,
                    message_extension,
                })
            }
            NetceteraAuthenticationResponse::Error(error_response) => Err(ErrorResponse {
                code: error_response.error_details.error_code,
                message: error_response.error_details.error_description,
                reason: error_response.error_details.error_detail,
                status_code: item.http_code,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
        };
        Ok(Self {
            response,
            ..item.data.clone()
        })
    }
}

pub struct NetceteraAuthType {
    pub(super) certificate: Secret<String>,
    pub(super) private_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for NetceteraAuthType {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type.to_owned() {
            ConnectorAuthType::CertificateAuth {
                certificate,
                private_key,
            } => Ok(Self {
                certificate,
                private_key,
            }),
            _ => Err(ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetceteraErrorResponse {
    pub three_ds_server_trans_id: Option<String>,
    pub error_details: NetceteraErrorDetails,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetceteraErrorDetails {
    /// Universally unique identifier for the transaction assigned by the 3DS Server.
    #[serde(rename = "threeDSServerTransID")]
    pub three_ds_server_trans_id: Option<String>,

    /// Universally Unique identifier for the transaction assigned by the ACS.
    #[serde(rename = "acsTransID")]
    pub acs_trans_id: Option<String>,

    /// Universally unique identifier for the transaction assigned by the DS.
    #[serde(rename = "dsTransID")]
    pub ds_trans_id: Option<String>,

    /// Code indicating the type of problem identified.
    pub error_code: String,

    /// Code indicating the 3-D Secure component that identified the error.
    pub error_component: Option<String>,

    /// Text describing the problem identified.
    pub error_description: String,

    /// Additional detail regarding the problem identified.
    pub error_detail: Option<String>,

    /// Universally unique identifier for the transaction assigned by the 3DS SDK.
    #[serde(rename = "sdkTransID")]
    pub sdk_trans_id: Option<String>,

    /// The Message Type that was identified as erroneous.
    pub error_message_type: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct NetceteraMetaData {
    pub mcc: Option<String>,
    pub merchant_country_code: Option<String>,
    pub merchant_name: Option<String>,
    pub endpoint_prefix: String,
    pub three_ds_requestor_name: Option<String>,
    pub three_ds_requestor_id: Option<String>,
    pub merchant_configuration_id: Option<String>,
}

impl TryFrom<&Option<common_utils::pii::SecretSerdeValue>> for NetceteraMetaData {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        meta_data: &Option<common_utils::pii::SecretSerdeValue>,
    ) -> Result<Self, Self::Error> {
        let metadata: Self = to_connector_meta_from_secret::<Self>(meta_data.clone())
            .change_context(ConnectorError::InvalidConnectorConfig { config: "metadata" })?;
        Ok(metadata)
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NetceteraPreAuthenticationRequest {
    cardholder_account_number: cards::CardNumber,
    scheme_id: Option<netcetera_types::SchemeId>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum NetceteraPreAuthenticationResponse {
    Success(Box<NetceteraPreAuthenticationResponseData>),
    Failure(Box<NetceteraErrorResponse>),
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NetceteraPreAuthenticationResponseData {
    #[serde(rename = "threeDSServerTransID")]
    pub three_ds_server_trans_id: String,
    pub card_ranges: Vec<CardRange>,
}

impl NetceteraPreAuthenticationResponseData {
    pub fn get_card_range_if_available(&self) -> Option<CardRange> {
        let card_range = self
            .card_ranges
            .iter()
            .max_by_key(|card_range| &card_range.highest_common_supported_version);
        card_range.cloned()
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CardRange {
    pub scheme_id: netcetera_types::SchemeId,
    pub directory_server_id: Option<String>,
    pub acs_protocol_versions: Vec<AcsProtocolVersion>,
    #[serde(rename = "threeDSMethodDataForm")]
    pub three_ds_method_data_form: Option<ThreeDSMethodDataForm>,
    pub highest_common_supported_version: SemanticVersion,
}

impl CardRange {
    pub fn get_three_ds_method_url(&self) -> Option<String> {
        self.acs_protocol_versions
            .iter()
            .find(|acs_protocol_version| {
                acs_protocol_version.version == self.highest_common_supported_version
            })
            .and_then(|acs_version| acs_version.three_ds_method_url.clone())
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ThreeDSMethodDataForm {
    // base64 encoded value for 3ds method data collection
    #[serde(rename = "threeDSMethodData")]
    pub three_ds_method_data: String,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct AcsProtocolVersion {
    pub version: SemanticVersion,
    #[serde(rename = "threeDSMethodURL")]
    pub three_ds_method_url: Option<String>,
}

impl TryFrom<&NetceteraRouterData<&PreAuthNRouterData>> for NetceteraPreAuthenticationRequest {
    type Error = error_stack::Report<ConnectorError>;

    fn try_from(value: &NetceteraRouterData<&PreAuthNRouterData>) -> Result<Self, Self::Error> {
        let router_data = value.router_data;
        let is_cobadged_card = || {
            router_data
                .request
                .card
                .card_number
                .is_cobadged_card()
                .change_context(ConnectorError::RequestEncodingFailed)
                .attach_printable("error while checking is_cobadged_card")
        };
        Ok(Self {
            cardholder_account_number: router_data.request.card.card_number.clone(),
            scheme_id: router_data
                .request
                .card
                .card_network
                .clone()
                .map(|card_network| {
                    is_cobadged_card().map(|is_cobadged_card| {
                        is_cobadged_card
                            .then_some(netcetera_types::SchemeId::try_from(card_network))
                    })
                })
                .transpose()?
                .flatten()
                .transpose()?,
        })
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
#[serde_with::skip_serializing_none]
pub struct NetceteraAuthenticationRequest {
    /// Specifies the preferred version of 3D Secure protocol to be utilized while executing 3D Secure authentication.
    /// 3DS Server initiates an authentication request with the preferred version and if this version is not supported by
    /// other 3D Secure components, it falls back to the next supported version(s) and continues authentication.
    ///
    /// If the preferred version is enforced by setting  #enforcePreferredProtocolVersion flag, but this version
    /// is not supported by one of the 3D Secure components, 3DS Server does not initiate an authentication and provides
    /// corresponding error message to the customer.
    ///
    /// The accepted values are:
    /// - 2.1.0 -> prefer authentication with 2.1.0 version,
    /// - 2.2.0 -> prefer authentication with 2.2.0 version,
    /// - 2.3.1 -> prefer authentication with 2.3.1 version,
    /// - latest -> prefer authentication with the latest version, the 3DS Server is certified for. 2.3.1 at this moment.
    pub preferred_protocol_version: Option<SemanticVersion>,
    /// Boolean flag that enforces preferred 3D Secure protocol version to be used in 3D Secure authentication.
    /// The value should be set true to enforce preferred version. If value is false or not provided,
    /// 3DS Server can fall back to next supported 3DS protocol version while initiating 3D Secure authentication.
    ///
    /// For application initiated transactions (deviceChannel = '01'), the preferred protocol version must be enforced.
    pub enforce_preferred_protocol_version: Option<bool>,
    pub device_channel: netcetera_types::NetceteraDeviceChannel,
    /// Identifies the category of the message for a specific use case. The accepted values are:
    ///
    /// - 01 -> PA
    /// - 02 -> NPA
    /// - 80 - 99 -> PS Specific Values (80 -> MasterCard Identity Check Insights;
    ///                                85 -> MasterCard Identity Check, Production Validation PA;
    ///                                86 -> MasterCard Identity Check, Production Validation NPA)
    pub message_category: netcetera_types::NetceteraMessageCategory,
    #[serde(rename = "threeDSCompInd")]
    pub three_ds_comp_ind: Option<netcetera_types::ThreeDSMethodCompletionIndicator>,
    /**
     * Contains the 3DS Server Transaction ID used during the previous execution of the 3DS method. Accepted value
     * length is 36 characters. Accepted value is a Canonical format as defined in IETF RFC 4122. May utilise any of the
     * specified versions if the output meets specified requirements.
     *
     * This field is required if the 3DS Requestor reuses previous 3DS Method execution with deviceChannel = 02 (BRW).
     * Available for supporting EMV 3DS 2.3.1 and later versions.
     */
    #[serde(rename = "threeDSMethodId")]
    pub three_ds_method_id: Option<String>,
    #[serde(rename = "threeDSRequestor")]
    pub three_ds_requestor: Option<netcetera_types::ThreeDSRequestor>,
    #[serde(rename = "threeDSServerTransID")]
    pub three_ds_server_trans_id: String,
    #[serde(rename = "threeDSRequestorURL")]
    pub three_ds_requestor_url: Option<String>,
    pub cardholder_account: netcetera_types::CardholderAccount,
    pub cardholder: Option<netcetera_types::Cardholder>,
    pub purchase: Option<netcetera_types::Purchase>,
    pub acquirer: Option<netcetera_types::AcquirerData>,
    pub merchant: Option<netcetera_types::MerchantData>,
    pub broad_info: Option<String>,
    pub device_render_options: Option<netcetera_types::DeviceRenderingOptionsSupported>,
    pub message_extension: Option<Vec<MessageExtensionAttribute>>,
    pub challenge_message_extension: Option<Vec<MessageExtensionAttribute>>,
    pub browser_information: Option<netcetera_types::Browser>,
    #[serde(rename = "threeRIInd")]
    pub three_ri_ind: Option<String>,
    pub sdk_information: Option<netcetera_types::Sdk>,
    pub device: Option<String>,
    pub multi_transaction: Option<String>,
    pub device_id: Option<String>,
    pub user_id: Option<String>,
    pub payee_origin: Option<url::Url>,
}

impl TryFrom<&NetceteraRouterData<&ConnectorAuthenticationRouterData>>
    for NetceteraAuthenticationRequest
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: &NetceteraRouterData<&ConnectorAuthenticationRouterData>,
    ) -> Result<Self, Self::Error> {
        let now = common_utils::date_time::now();
        let request = item.router_data.request.clone();
        let pre_authn_data = request.pre_authentication_data.clone();
        let ip_address = request
            .browser_details
            .as_ref()
            .and_then(|browser| browser.ip_address);
        let three_ds_requestor = netcetera_types::ThreeDSRequestor::new(
            ip_address,
            item.router_data.psd2_sca_exemption_type,
            item.router_data.request.force_3ds_challenge,
            item.router_data
                .request
                .pre_authentication_data
                .message_version
                .clone(),
        );
        let card = get_card_details(request.payment_method_data, "netcetera")?;
        let is_cobadged_card = card
            .card_number
            .clone()
            .is_cobadged_card()
            .change_context(ConnectorError::RequestEncodingFailed)
            .attach_printable("error while checking is_cobadged_card")?;
        let cardholder_account = netcetera_types::CardholderAccount {
            acct_type: None,
            card_expiry_date: Some(card.get_expiry_date_as_yymm()?),
            acct_info: None,
            acct_number: card.card_number,
            scheme_id: card
                .card_network
                .clone()
                .and_then(|card_network| {
                    is_cobadged_card.then_some(netcetera_types::SchemeId::try_from(card_network))
                })
                .transpose()?,
            acct_id: None,
            pay_token_ind: None,
            pay_token_info: None,
            card_security_code: Some(card.card_cvc),
        };
        let currency = request
            .currency
            .get_required_value("currency")
            .change_context(ConnectorError::MissingRequiredField {
                field_name: "currency",
            })?;
        let purchase = netcetera_types::Purchase {
            purchase_instal_data: None,
            merchant_risk_indicator: None,
            purchase_amount: request.amount,
            purchase_currency: currency.iso_4217().to_string(),
            purchase_exponent: currency.number_of_digits_after_decimal_point(),
            purchase_date: Some(
                common_utils::date_time::format_date(
                    now,
                    common_utils::date_time::DateFormat::YYYYMMDDHHmmss,
                )
                .change_context(
                    ConnectorError::RequestEncodingFailedWithReason(
                        "Failed to format Date".to_string(),
                    ),
                )?,
            ),
            recurring_expiry: None,
            recurring_frequency: None,
            // 01 -> Goods and Services, hardcoding this as we serve this usecase only for now
            trans_type: Some("01".to_string()),
            recurring_amount: None,
            recurring_currency: None,
            recurring_exponent: None,
            recurring_date: None,
            amount_ind: None,
            frequency_ind: None,
        };
        let acquirer_details = netcetera_types::AcquirerData {
            acquirer_bin: request.pre_authentication_data.acquirer_bin,
            acquirer_merchant_id: request.pre_authentication_data.acquirer_merchant_id,
            acquirer_country_code: request.pre_authentication_data.acquirer_country_code,
        };
        let connector_meta_data: NetceteraMetaData = item
            .router_data
            .connector_meta_data
            .clone()
            .parse_value("NetceteraMetaData")
            .change_context(ConnectorError::RequestEncodingFailed)?;
        let merchant_data = netcetera_types::MerchantData {
            merchant_configuration_id: connector_meta_data.merchant_configuration_id,
            mcc: connector_meta_data.mcc,
            merchant_country_code: connector_meta_data.merchant_country_code,
            merchant_name: connector_meta_data.merchant_name,
            notification_url: request.return_url.clone(),
            three_ds_requestor_id: connector_meta_data.three_ds_requestor_id,
            three_ds_requestor_name: connector_meta_data.three_ds_requestor_name,
            white_list_status: None,
            trust_list_status: None,
            seller_info: None,
            results_response_notification_url: Some(request.webhook_url),
        };
        let browser_information = match request.device_channel {
            api_models::payments::DeviceChannel::Browser => {
                request.browser_details.map(netcetera_types::Browser::from)
            }
            api_models::payments::DeviceChannel::App => None,
        };
        let sdk_information = match request.device_channel {
            api_models::payments::DeviceChannel::App => {
                request.sdk_information.map(netcetera_types::Sdk::from)
            }
            api_models::payments::DeviceChannel::Browser => None,
        };
        let device_render_options = match request.device_channel {
            api_models::payments::DeviceChannel::App => {
                Some(netcetera_types::DeviceRenderingOptionsSupported {
                    // hard-coded until core provides these values.
                    sdk_interface: netcetera_types::SdkInterface::Both,
                    sdk_ui_type: vec![
                        netcetera_types::SdkUiType::Text,
                        netcetera_types::SdkUiType::SingleSelect,
                        netcetera_types::SdkUiType::MultiSelect,
                        netcetera_types::SdkUiType::Oob,
                        netcetera_types::SdkUiType::HtmlOther,
                    ],
                })
            }
            api_models::payments::DeviceChannel::Browser => None,
        };
        Ok(Self {
            preferred_protocol_version: Some(pre_authn_data.message_version),
            // For Device channel App, we should enforce the preferred protocol version
            enforce_preferred_protocol_version: Some(matches!(
                request.device_channel,
                api_models::payments::DeviceChannel::App
            )),
            device_channel: netcetera_types::NetceteraDeviceChannel::from(request.device_channel),
            message_category: netcetera_types::NetceteraMessageCategory::from(
                request.message_category,
            ),
            three_ds_comp_ind: Some(netcetera_types::ThreeDSMethodCompletionIndicator::from(
                request.threeds_method_comp_ind,
            )),
            three_ds_method_id: None,
            three_ds_requestor: Some(three_ds_requestor),
            three_ds_server_trans_id: pre_authn_data.threeds_server_transaction_id,
            three_ds_requestor_url: Some(request.three_ds_requestor_url),
            cardholder_account,
            cardholder: Some(netcetera_types::Cardholder::try_from((
                request.billing_address,
                request.shipping_address,
            ))?),
            purchase: Some(purchase),
            acquirer: Some(acquirer_details),
            merchant: Some(merchant_data),
            broad_info: None,
            device_render_options,
            message_extension: None,
            challenge_message_extension: None,
            browser_information,
            three_ri_ind: None,
            sdk_information,
            device: None,
            multi_transaction: None,
            device_id: None,
            user_id: None,
            payee_origin: None,
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum NetceteraAuthenticationResponse {
    Error(NetceteraAuthenticationFailureResponse),
    Success(NetceteraAuthenticationSuccessResponse),
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NetceteraAuthenticationSuccessResponse {
    #[serde(rename = "threeDSServerTransID")]
    pub three_ds_server_trans_id: String,
    pub trans_status: common_enums::TransactionStatus,
    pub authentication_value: Option<Secret<String>>,
    pub eci: Option<String>,
    pub acs_challenge_mandated: Option<ACSChallengeMandatedIndicator>,
    pub authentication_request: Option<AuthenticationRequest>,
    pub authentication_response: AuthenticationResponse,
    #[serde(rename = "base64EncodedChallengeRequest")]
    pub encoded_challenge_request: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NetceteraAuthenticationFailureResponse {
    pub error_details: NetceteraErrorDetails,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticationRequest {
    #[serde(rename = "threeDSRequestorChallengeInd")]
    pub three_ds_requestor_challenge_ind: Option<ThreedsRequestorChallengeInd>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ThreedsRequestorChallengeInd {
    Single(String),
    Multiple(Vec<String>),
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticationResponse {
    #[serde(rename = "acsURL")]
    pub acs_url: Option<url::Url>,
    pub acs_reference_number: Option<String>,
    #[serde(rename = "acsTransID")]
    pub acs_trans_id: Option<String>,
    #[serde(rename = "dsTransID")]
    pub ds_trans_id: Option<String>,
    pub acs_signed_content: Option<String>,
    pub trans_status_reason: Option<String>,
    pub message_extension: Option<serde_json::Value>,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub enum ACSChallengeMandatedIndicator {
    /// Challenge is mandated
    Y,
    /// Challenge is not mandated
    N,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ResultsResponseData {
    /// Universally unique transaction identifier assigned by the 3DS Server to identify a single transaction.
    /// It has the same value as the authentication request and conforms to the format defined in IETF RFC 4122.
    #[serde(rename = "threeDSServerTransID")]
    pub three_ds_server_trans_id: String,

    /// Indicates the status of a transaction in terms of its authentication.
    ///
    /// Valid values:
    /// - `Y`: Authentication / Account verification successful.
    /// - `N`: Not authenticated / Account not verified; Transaction denied.
    /// - `U`: Authentication / Account verification could not be performed; technical or other problem.
    /// - `C`: A challenge is required to complete the authentication.
    /// - `R`: Authentication / Account verification Rejected. Issuer is rejecting authentication/verification
    ///   and request that authorization not be attempted.
    /// - `A`: Attempts processing performed; Not authenticated / verified, but a proof of attempt
    ///   authentication / verification is provided.
    /// - `D`: A challenge is required to complete the authentication. Decoupled Authentication confirmed.
    /// - `I`: Informational Only; 3DS Requestor challenge preference acknowledged.
    pub trans_status: Option<common_enums::TransactionStatus>,

    /// Payment System-specific value provided as part of the ACS registration for each supported DS.
    /// Authentication Value may be used to provide proof of authentication.
    pub authentication_value: Option<Secret<String>>,

    /// Payment System-specific value provided by the ACS to indicate the results of the attempt to authenticate
    /// the Cardholder.
    pub eci: Option<String>,

    /// The received Results Request from the Directory Server.
    pub results_request: Option<serde_json::Value>,

    /// The sent Results Response to the Directory Server.
    pub results_response: Option<serde_json::Value>,

    /// Optional object containing error details if any errors occurred during the process.
    pub error_details: Option<NetceteraErrorDetails>,
}