use std::collections::HashMap;

use cards::CardNumber;
use common_utils::{
    ext_traits::OptionExt,
    pii,
    request::Method,
    types::{MinorUnit, StringMinorUnit},
};
use domain_types::{
    connector_flow::{
        self, Authorize, IncrementalAuthorization, PSync, RSync, RepeatPayment, SetupMandate, Void,
    },
    connector_types::{
        EventType, MandateReference, MandateReferenceId, PaymentFlowData, PaymentVoidData,
        PaymentsAuthorizeData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData,
        PaymentsResponseData, PaymentsSyncData, RefundFlowData, RefundSyncData,
        RefundWebhookDetailsResponse, RefundsData, RefundsResponseData, RepeatPaymentData,
        ResponseId, SetupMandateRequestData, WebhookDetailsResponse,
    },
    errors::{ConnectorError, IntegrationError},
    payment_method_data::{
        BankDebitData, PaymentMethodData, PaymentMethodDataTypes, RawCardNumber,
        WalletData as WalletDataPaymentMethod,
    },
    router_data::{ConnectorSpecificConfig, ErrorResponse},
    router_data_v2::RouterDataV2,
    router_response_types::RedirectForm,
    utils::{self, ForeignTryFrom},
};
use error_stack::ResultExt;
use hyperswitch_masking::{ExposeInterface, Secret};
use serde::{Deserialize, Serialize};
use strum::Display;

use crate::{connectors::novalnet::NovalnetRouterData, types::ResponseRouterData};

/// Default locale
const DEFAULT_LOCALE: &str = "en";

const MINIMAL_CUSTOMER_DATA_PASSED: i64 = 1;
const CREATE_TOKEN_REQUIRED: i8 = 1;

const TEST_MODE_ENABLED: i8 = 1;
const TEST_MODE_DISABLED: i8 = 0;

fn get_test_mode(item: Option<bool>) -> i8 {
    match item {
        Some(true) => TEST_MODE_ENABLED,
        Some(false) | None => TEST_MODE_DISABLED,
    }
}

#[derive(Debug, Copy, Serialize, Deserialize, Clone)]
pub enum NovalNetPaymentTypes {
    CREDITCARD,
    PAYPAL,
    GOOGLEPAY,
    APPLEPAY,
    #[serde(rename = "DIRECT_DEBIT_SEPA")]
    DirectDebitSepa,
    #[serde(rename = "DIRECT_DEBIT_ACH")]
    DirectDebitAch,
}

#[derive(Default, Debug, Serialize, Clone)]
pub struct NovalnetPaymentsRequestMerchant {
    signature: Secret<String>,
    tariff: Secret<String>,
}

#[derive(Default, Debug, Serialize, Clone)]
pub struct NovalnetPaymentsRequestBilling {
    house_no: Option<Secret<String>>,
    street: Option<Secret<String>>,
    city: Option<Secret<String>>,
    zip: Option<Secret<String>>,
    country_code: Option<common_enums::CountryAlpha2>,
}

#[derive(Default, Debug, Serialize, Clone)]
pub struct NovalnetPaymentsRequestCustomer {
    first_name: Option<Secret<String>>,
    last_name: Option<Secret<String>>,
    email: pii::Email,
    mobile: Option<Secret<String>>,
    billing: Option<NovalnetPaymentsRequestBilling>,
    no_nc: i64,
    birth_date: Option<String>, // Mandatory for SEPA Guarantee Payment
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetCard<
    T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize,
> {
    card_number: RawCardNumber<T>,
    card_expiry_month: Secret<String>,
    card_expiry_year: Secret<String>,
    card_cvc: Secret<String>,
    card_holder: Secret<String>,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetRawCardDetails {
    card_number: CardNumber,
    card_expiry_month: Secret<String>,
    card_expiry_year: Secret<String>,
    scheme_tid: Secret<String>,
}

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

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

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NovalnetAchDebit {
    account_holder: Secret<String>,
    account_number: Secret<String>,
    routing_number: Secret<String>,
}

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

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

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum NovalNetPaymentData<
    T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize,
> {
    Card(NovalnetCard<T>),
    RawCardForNTI(NovalnetRawCardDetails),
    GooglePay(NovalnetGooglePay),
    ApplePay(NovalnetApplePay),
    MandatePayment(NovalnetMandate),
    Sepa(NovalnetSepaDebit),
    Ach(NovalnetAchDebit),
}

#[derive(Default, Debug, Serialize, Clone)]
pub struct NovalnetCustom {
    lang: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum NovalNetAmount {
    StringMinor(StringMinorUnit),
    Int(i64),
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NovalnetPaymentsRequestTransaction<
    T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize,
> {
    test_mode: i8,
    payment_type: NovalNetPaymentTypes,
    amount: NovalNetAmount,
    currency: common_enums::Currency,
    order_no: String,
    payment_data: Option<NovalNetPaymentData<T>>,
    hook_url: Option<String>,
    return_url: Option<String>,
    error_return_url: Option<String>,
    enforce_3d: Option<i8>, //NOTE: Needed for CREDITCARD, GOOGLEPAY
    create_token: Option<i8>,
}

#[derive(Debug, Serialize, Clone)]
pub struct NovalnetPaymentsRequest<
    T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize,
> {
    merchant: NovalnetPaymentsRequestMerchant,
    customer: NovalnetPaymentsRequestCustomer,
    transaction: NovalnetPaymentsRequestTransaction<T>,
    custom: NovalnetCustom,
}

impl TryFrom<&common_enums::PaymentMethodType> for NovalNetPaymentTypes {
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(item: &common_enums::PaymentMethodType) -> Result<Self, Self::Error> {
        match item {
            common_enums::PaymentMethodType::ApplePay => Ok(Self::APPLEPAY),
            common_enums::PaymentMethodType::Card => Ok(Self::CREDITCARD),
            common_enums::PaymentMethodType::GooglePay => Ok(Self::GOOGLEPAY),
            common_enums::PaymentMethodType::Paypal => Ok(Self::PAYPAL),
            common_enums::PaymentMethodType::Sepa => Ok(Self::DirectDebitSepa),
            common_enums::PaymentMethodType::Ach => Ok(Self::DirectDebitAch),
            _ => Err(error_stack::report!(IntegrationError::NotSupported {
                message: utils::get_unimplemented_payment_method_error_message("Novalnet"),
                connector: "Novalnet",
                context: Default::default(),
            }))?,
        }
    }
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        NovalnetRouterData<
            RouterDataV2<
                Authorize,
                PaymentFlowData,
                PaymentsAuthorizeData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    > for NovalnetPaymentsRequest<T>
{
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(
        item: NovalnetRouterData<
            RouterDataV2<
                Authorize,
                PaymentFlowData,
                PaymentsAuthorizeData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let auth = NovalnetAuthType::try_from(&item.router_data.connector_config)?;

        let merchant = NovalnetPaymentsRequestMerchant {
            signature: auth.product_activation_key,
            tariff: auth.tariff_id,
        };

        let enforce_3d = match item.router_data.resource_common_data.auth_type {
            common_enums::AuthenticationType::ThreeDs => Some(1),
            common_enums::AuthenticationType::NoThreeDs => None,
        };
        let test_mode = get_test_mode(item.router_data.resource_common_data.test_mode);

        let billing = NovalnetPaymentsRequestBilling {
            house_no: item
                .router_data
                .resource_common_data
                .get_optional_billing_line1(),
            street: item
                .router_data
                .resource_common_data
                .get_optional_billing_line2(),
            city: item
                .router_data
                .resource_common_data
                .get_optional_billing_city(),
            zip: item
                .router_data
                .resource_common_data
                .get_optional_billing_zip(),
            country_code: item
                .router_data
                .resource_common_data
                .get_optional_billing_country(),
        };

        let customer = NovalnetPaymentsRequestCustomer {
            first_name: item
                .router_data
                .resource_common_data
                .get_optional_billing_first_name(),
            last_name: item
                .router_data
                .resource_common_data
                .get_optional_billing_last_name(),
            email: item
                .router_data
                .resource_common_data
                .get_billing_email()
                .or(item.router_data.request.get_email())?,
            mobile: item
                .router_data
                .resource_common_data
                .get_optional_billing_phone_number(),
            billing: Some(billing),
            // no_nc is used to indicate if minimal customer data is passed or not
            no_nc: MINIMAL_CUSTOMER_DATA_PASSED,
            // DOB should be populated in case of SepaGuaranteedBankDebit payments
            birth_date: None,
        };

        let lang = item
            .router_data
            .request
            .get_optional_language_from_browser_info()
            .unwrap_or(DEFAULT_LOCALE.to_string().to_string());
        let custom = NovalnetCustom { lang };
        let hook_url = item.router_data.request.get_webhook_url()?;
        let return_url = item.router_data.request.get_router_return_url()?;
        let create_token = if item.router_data.request.is_mandate_payment() {
            Some(CREATE_TOKEN_REQUIRED)
        } else {
            None
        };

        let amount = item
            .connector
            .amount_converter
            .convert(
                item.router_data.request.minor_amount,
                item.router_data.request.currency,
            )
            .change_context(IntegrationError::AmountConversionFailed {
                context: Default::default(),
            })?;

        match item.router_data.request.payment_method_data {
            PaymentMethodData::Card(ref req_card) => {
                let novalnet_card = NovalNetPaymentData::Card(NovalnetCard {
                    card_number: req_card.card_number.clone(),
                    card_expiry_month: req_card.card_exp_month.clone(),
                    card_expiry_year: req_card.card_exp_year.clone(),
                    card_cvc: req_card.card_cvc.clone(),
                    card_holder: item
                        .router_data
                        .resource_common_data
                        .get_billing_full_name()?,
                });

                let transaction = NovalnetPaymentsRequestTransaction {
                    test_mode,
                    payment_type: NovalNetPaymentTypes::CREDITCARD,
                    amount: NovalNetAmount::StringMinor(amount.clone()),
                    currency: item.router_data.request.currency,
                    order_no: item
                        .router_data
                        .resource_common_data
                        .connector_request_reference_id
                        .clone(),
                    hook_url: Some(hook_url),
                    return_url: Some(return_url.clone()),
                    error_return_url: Some(return_url.clone()),
                    payment_data: Some(novalnet_card),
                    enforce_3d,
                    create_token,
                };

                Ok(Self {
                    merchant,
                    transaction,
                    customer,
                    custom,
                })
            }
            PaymentMethodData::Wallet(ref wallet_data) => match wallet_data {
                WalletDataPaymentMethod::GooglePay(ref req_wallet) => {
                    let novalnet_google_pay: NovalNetPaymentData<T> =
                        NovalNetPaymentData::GooglePay(NovalnetGooglePay {
                            wallet_data: Secret::new(
                                req_wallet
                                    .tokenization_data
                                    .get_encrypted_google_pay_token()
                                    .change_context(IntegrationError::MissingRequiredField {
                                        field_name: "gpay wallet_token",
                                        context: Default::default(),
                                    })?
                                    .clone(),
                            ),
                        });

                    let transaction = NovalnetPaymentsRequestTransaction {
                        test_mode,
                        payment_type: NovalNetPaymentTypes::GOOGLEPAY,
                        amount: NovalNetAmount::StringMinor(amount.clone()),
                        currency: item.router_data.request.currency,
                        order_no: item
                            .router_data
                            .resource_common_data
                            .connector_request_reference_id
                            .clone(),
                        hook_url: Some(hook_url),
                        return_url: None,
                        error_return_url: None,
                        payment_data: Some(novalnet_google_pay),
                        enforce_3d,
                        create_token,
                    };

                    Ok(Self {
                        merchant,
                        transaction,
                        customer,
                        custom,
                    })
                }
                WalletDataPaymentMethod::ApplePay(payment_method_data) => {
                    let transaction = NovalnetPaymentsRequestTransaction {
                        test_mode,
                        payment_type: NovalNetPaymentTypes::APPLEPAY,
                        amount: NovalNetAmount::StringMinor(amount.clone()),
                        currency: item.router_data.request.currency,
                        order_no: item
                            .router_data
                            .resource_common_data
                            .connector_request_reference_id
                            .clone(),
                        hook_url: Some(hook_url),
                        return_url: None,
                        error_return_url: None,
                        payment_data: Some(NovalNetPaymentData::ApplePay(NovalnetApplePay {
                            wallet_data: payment_method_data.get_applepay_decoded_payment_data()?,
                        })),
                        enforce_3d: None,
                        create_token,
                    };

                    Ok(Self {
                        merchant,
                        transaction,
                        customer,
                        custom,
                    })
                }
                WalletDataPaymentMethod::PaypalRedirect(_) => {
                    let transaction = NovalnetPaymentsRequestTransaction {
                        test_mode,
                        payment_type: NovalNetPaymentTypes::PAYPAL,
                        amount: NovalNetAmount::StringMinor(amount.clone()),
                        currency: item.router_data.request.currency,
                        order_no: item
                            .router_data
                            .resource_common_data
                            .connector_request_reference_id
                            .clone(),
                        hook_url: Some(hook_url),
                        return_url: Some(return_url.clone()),
                        error_return_url: Some(return_url.clone()),
                        payment_data: None,
                        enforce_3d: None,
                        create_token,
                    };
                    Ok(Self {
                        merchant,
                        transaction,
                        customer,
                        custom,
                    })
                }
                WalletDataPaymentMethod::AliPayQr(_)
                | WalletDataPaymentMethod::BluecodeRedirect {}
                | WalletDataPaymentMethod::AliPayRedirect(_)
                | WalletDataPaymentMethod::AliPayHkRedirect(_)
                | WalletDataPaymentMethod::AmazonPayRedirect(_)
                | WalletDataPaymentMethod::MomoRedirect(_)
                | WalletDataPaymentMethod::KakaoPayRedirect(_)
                | WalletDataPaymentMethod::GoPayRedirect(_)
                | WalletDataPaymentMethod::GcashRedirect(_)
                | WalletDataPaymentMethod::ApplePayRedirect(_)
                | WalletDataPaymentMethod::ApplePayThirdPartySdk(_)
                | WalletDataPaymentMethod::DanaRedirect {}
                | WalletDataPaymentMethod::GooglePayRedirect(_)
                | WalletDataPaymentMethod::GooglePayThirdPartySdk(_)
                | WalletDataPaymentMethod::MbWayRedirect(_)
                | WalletDataPaymentMethod::MobilePayRedirect(_)
                | WalletDataPaymentMethod::RevolutPay(_)
                | WalletDataPaymentMethod::PaypalSdk(_)
                | WalletDataPaymentMethod::Paze(_)
                | WalletDataPaymentMethod::SamsungPay(_)
                | WalletDataPaymentMethod::TwintRedirect {}
                | WalletDataPaymentMethod::VippsRedirect {}
                | WalletDataPaymentMethod::TouchNGoRedirect(_)
                | WalletDataPaymentMethod::WeChatPayRedirect(_)
                | WalletDataPaymentMethod::CashappQr(_)
                | WalletDataPaymentMethod::SwishQr(_)
                | WalletDataPaymentMethod::WeChatPayQr(_)
                | WalletDataPaymentMethod::Mifinity(_)
                | WalletDataPaymentMethod::MbWay(_)
                | WalletDataPaymentMethod::Satispay(_)
                | WalletDataPaymentMethod::Wero(_)
                | WalletDataPaymentMethod::LazyPayRedirect(_)
                | WalletDataPaymentMethod::PhonePeRedirect(_)
                | WalletDataPaymentMethod::BillDeskRedirect(_)
                | WalletDataPaymentMethod::CashfreeRedirect(_)
                | WalletDataPaymentMethod::PayURedirect(_)
                | WalletDataPaymentMethod::EaseBuzzRedirect(_)
                | WalletDataPaymentMethod::PaymayaRedirect(_)
                | WalletDataPaymentMethod::QwikcilverWalletDirect(_)
                | WalletDataPaymentMethod::Skrill(_) => Err(IntegrationError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("novalnet"),
                    Default::default(),
                )
                .into()),
            },
            PaymentMethodData::BankDebit(ref bank_debit_data) => {
                let payment_type = NovalNetPaymentTypes::try_from(
                    &item.router_data.request.payment_method_type.ok_or(
                        IntegrationError::MissingPaymentMethodType {
                            context: Default::default(),
                        },
                    )?,
                )?;

                let transaction = match bank_debit_data {
                    BankDebitData::SepaBankDebit {
                        iban,
                        bank_account_holder_name,
                    } => {
                        let account_holder = match bank_account_holder_name {
                            Some(name) => name.clone(),
                            None => item
                                .router_data
                                .resource_common_data
                                .get_billing_full_name()?,
                        };
                        NovalnetPaymentsRequestTransaction {
                            test_mode,
                            payment_type,
                            amount: NovalNetAmount::StringMinor(amount.clone()),
                            currency: item.router_data.request.currency,
                            order_no: item
                                .router_data
                                .resource_common_data
                                .connector_request_reference_id
                                .clone(),
                            hook_url: Some(hook_url),
                            return_url: Some(return_url.clone()),
                            error_return_url: Some(return_url.clone()),
                            payment_data: Some(NovalNetPaymentData::Sepa(NovalnetSepaDebit {
                                account_holder: account_holder.clone(),
                                iban: iban.clone(),
                            })),
                            enforce_3d,
                            create_token,
                        }
                    }
                    BankDebitData::AchBankDebit {
                        account_number,
                        routing_number,
                        bank_account_holder_name,
                        ..
                    } => {
                        let account_holder = match bank_account_holder_name {
                            Some(name) => name.clone(),
                            None => item
                                .router_data
                                .resource_common_data
                                .get_billing_full_name()?,
                        };
                        NovalnetPaymentsRequestTransaction {
                            test_mode,
                            payment_type,
                            amount: NovalNetAmount::StringMinor(amount.clone()),
                            currency: item.router_data.request.currency,
                            order_no: item
                                .router_data
                                .resource_common_data
                                .connector_request_reference_id
                                .clone(),
                            hook_url: Some(hook_url),
                            return_url: Some(return_url.clone()),
                            error_return_url: Some(return_url.clone()),
                            payment_data: Some(NovalNetPaymentData::Ach(NovalnetAchDebit {
                                account_holder: account_holder.clone(),
                                account_number: account_number.clone(),
                                routing_number: routing_number.clone(),
                            })),
                            enforce_3d,
                            create_token,
                        }
                    }
                    BankDebitData::SepaGuaranteedBankDebit { .. }
                    | BankDebitData::BecsBankDebit { .. }
                    | BankDebitData::EftBankDebit { .. }
                    | BankDebitData::BacsBankDebit { .. } => {
                        return Err(IntegrationError::NotImplemented(
                            utils::get_unimplemented_payment_method_error_message("novalnet"),
                            Default::default(),
                        )
                        .into());
                    }
                };

                Ok(Self {
                    merchant,
                    transaction,
                    customer,
                    custom,
                })
            }
            _ => Err(IntegrationError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("novalnet"),
                Default::default(),
            )
            .into()),
        }
    }
}

// Auth Struct
pub struct NovalnetAuthType {
    pub(super) product_activation_key: Secret<String>,
    pub(super) payment_access_key: Secret<String>,
    pub(super) tariff_id: Secret<String>,
}

impl TryFrom<&ConnectorSpecificConfig> for NovalnetAuthType {
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(auth_type: &ConnectorSpecificConfig) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorSpecificConfig::Novalnet {
                product_activation_key,
                payment_access_key,
                tariff_id,
                ..
            } => Ok(Self {
                product_activation_key: product_activation_key.to_owned(),
                payment_access_key: payment_access_key.to_owned(),
                tariff_id: tariff_id.to_owned(),
            }),
            _ => Err(IntegrationError::FailedToObtainAuthType {
                context: Default::default(),
            }
            .into()),
        }
    }
}

// PaymentsResponse
#[derive(Debug, Display, Copy, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NovalnetTransactionStatus {
    Success,
    Failure,
    Confirmed,
    OnHold,
    Pending,
    Deactivated,
    Progress,
    Error,
}

#[derive(Debug, Copy, Display, Clone, Serialize, Deserialize, PartialEq)]
#[strum(serialize_all = "UPPERCASE")]
#[serde(rename_all = "UPPERCASE")]
pub enum NovalnetAPIStatus {
    Success,
    Failure,
}

impl From<NovalnetTransactionStatus> for common_enums::AttemptStatus {
    fn from(item: NovalnetTransactionStatus) -> Self {
        match item {
            NovalnetTransactionStatus::Success | NovalnetTransactionStatus::Confirmed => {
                Self::Charged
            }
            NovalnetTransactionStatus::OnHold => Self::Authorized,
            NovalnetTransactionStatus::Pending => Self::Pending,
            NovalnetTransactionStatus::Progress => Self::AuthenticationPending,
            NovalnetTransactionStatus::Deactivated => Self::Voided,
            NovalnetTransactionStatus::Failure | NovalnetTransactionStatus::Error => Self::Failure,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResultData {
    pub redirect_url: Option<Secret<url::Url>>,
    pub status: NovalnetAPIStatus,
    pub status_code: u64,
    pub status_text: String,
    pub additional_message: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetPaymentsResponseTransactionData {
    pub amount: Option<MinorUnit>,
    pub currency: Option<common_enums::Currency>,
    pub date: Option<String>,
    pub order_no: Option<String>,
    pub payment_data: Option<NovalnetResponsePaymentData>,
    pub payment_type: Option<String>,
    pub status_code: Option<u64>,
    pub txn_secret: Option<Secret<String>>,
    pub tid: Option<i64>,
    pub test_mode: Option<i8>,
    pub status: Option<NovalnetTransactionStatus>,
    pub authorization: Option<NovalnetAuthorizationResponse>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetPaymentsResponse {
    result: ResultData,
    transaction: Option<NovalnetPaymentsResponseTransactionData>,
}

pub fn get_error_response(
    result: ResultData,
    status_code: u16,
    transaction_id: Option<String>,
) -> ErrorResponse {
    let error_code = result.status;
    let error_reason = result.status_text.clone();

    ErrorResponse {
        code: error_code.to_string(),
        message: error_reason.clone(),
        reason: Some(error_reason),
        status_code,
        attempt_status: None,
        connector_transaction_id: transaction_id,
        network_advice_code: None,
        network_decline_code: None,
        network_error_message: None,
    }
}

impl NovalnetPaymentsResponseTransactionData {
    pub fn get_token(transaction_data: Option<&Self>) -> Option<String> {
        if let Some(data) = transaction_data {
            match &data.payment_data {
                Some(NovalnetResponsePaymentData::Card(card_data)) => {
                    card_data.token.clone().map(|token| token.expose())
                }
                Some(NovalnetResponsePaymentData::Paypal(paypal_data)) => {
                    paypal_data.token.clone().map(|token| token.expose())
                }
                None => None,
            }
        } else {
            None
        }
    }
}

// Specific implementations for Authorize flow
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<ResponseRouterData<NovalnetPaymentsResponse, Self>>
    for RouterDataV2<Authorize, PaymentFlowData, PaymentsAuthorizeData<T>, PaymentsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<NovalnetPaymentsResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = item
            .response
            .transaction
            .clone()
            .and_then(|data| data.tid.map(|tid| tid.to_string()));

        match item.response.result.status {
            NovalnetAPIStatus::Success => {
                let redirection_data: Option<RedirectForm> =
                    item.response
                        .result
                        .redirect_url
                        .map(|url| RedirectForm::Form {
                            endpoint: url.expose().to_string(),
                            method: Method::Get,
                            form_fields: HashMap::new(),
                        });

                let mandate_reference_id = NovalnetPaymentsResponseTransactionData::get_token(
                    item.response.transaction.clone().as_ref(),
                );

                let transaction_status = item
                    .response
                    .transaction
                    .as_ref()
                    .and_then(|transaction_data| transaction_data.status)
                    .unwrap_or(if redirection_data.is_some() {
                        NovalnetTransactionStatus::Progress
                        // NOTE: Novalnet does not send us the transaction.status for redirection flow
                        // so status is mapped to Progress if flow has redirection data
                    } else {
                        NovalnetTransactionStatus::Pending
                    });

                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::from(transaction_status),
                        ..item.router_data.resource_common_data
                    },
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: transaction_id
                            .clone()
                            .map(ResponseId::ConnectorTransactionId)
                            .unwrap_or(ResponseId::NoResponseId),
                        redirection_data: redirection_data.map(Box::new),
                        mandate_reference: mandate_reference_id
                            .as_ref()
                            .map(|id| MandateReference {
                                connector_mandate_id: Some(id.clone()),
                                payment_method_id: None,
                                connector_mandate_request_reference_id: None,
                                mandate_metadata: None,
                            })
                            .map(Box::new),
                        connector_metadata: None,
                        network_txn_id: item.response.transaction.and_then(|data| {
                            data.payment_data
                                .and_then(|payment_data| match payment_data {
                                    NovalnetResponsePaymentData::Card(card) => {
                                        card.scheme_tid.map(|tid| tid.expose())
                                    }
                                    NovalnetResponsePaymentData::Paypal(_) => None,
                                })
                        }),
                        network_txn_link_id: None,
                        connector_response_reference_id: transaction_id.clone(),
                        incremental_authorization_allowed: None,
                        status_code: item.http_code,
                        splits: None,
                    }),
                    ..item.router_data
                })
            }
            NovalnetAPIStatus::Failure => {
                let response = Err(get_error_response(
                    item.response.result,
                    item.http_code,
                    transaction_id,
                ));
                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::Failure,
                        ..item.router_data.resource_common_data
                    },
                    response,
                    ..item.router_data
                })
            }
        }
    }
}

// Specific implementations for SetupMandate flow
impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<ResponseRouterData<NovalnetPaymentsResponse, Self>>
    for RouterDataV2<
        SetupMandate,
        PaymentFlowData,
        SetupMandateRequestData<T>,
        PaymentsResponseData,
    >
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<NovalnetPaymentsResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = item
            .response
            .transaction
            .clone()
            .and_then(|data| data.tid.map(|tid| tid.to_string()));

        match item.response.result.status {
            NovalnetAPIStatus::Success => {
                let redirection_data: Option<RedirectForm> =
                    item.response
                        .result
                        .redirect_url
                        .map(|url| RedirectForm::Form {
                            endpoint: url.expose().to_string(),
                            method: Method::Get,
                            form_fields: HashMap::new(),
                        });

                let mandate_reference_id = NovalnetPaymentsResponseTransactionData::get_token(
                    item.response.transaction.clone().as_ref(),
                );

                let transaction_status = item
                    .response
                    .transaction
                    .as_ref()
                    .and_then(|transaction_data| transaction_data.status)
                    .unwrap_or(if redirection_data.is_some() {
                        NovalnetTransactionStatus::Progress
                        // NOTE: Novalnet does not send us the transaction.status for redirection flow
                        // so status is mapped to Progress if flow has redirection data
                    } else {
                        NovalnetTransactionStatus::Pending
                    });

                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::from(transaction_status),
                        ..item.router_data.resource_common_data
                    },
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: transaction_id
                            .clone()
                            .map(ResponseId::ConnectorTransactionId)
                            .unwrap_or(ResponseId::NoResponseId),
                        redirection_data: redirection_data.map(Box::new),
                        mandate_reference: mandate_reference_id
                            .as_ref()
                            .map(|id| MandateReference {
                                connector_mandate_id: Some(id.clone()),
                                payment_method_id: None,
                                connector_mandate_request_reference_id: None,
                                mandate_metadata: None,
                            })
                            .map(Box::new),
                        connector_metadata: None,
                        network_txn_id: item.response.transaction.and_then(|data| {
                            data.payment_data
                                .and_then(|payment_data| match payment_data {
                                    NovalnetResponsePaymentData::Card(card) => {
                                        card.scheme_tid.map(|tid| tid.expose())
                                    }
                                    NovalnetResponsePaymentData::Paypal(_) => None,
                                })
                        }),
                        network_txn_link_id: None,
                        connector_response_reference_id: transaction_id.clone(),
                        incremental_authorization_allowed: None,
                        status_code: item.http_code,
                        splits: None,
                    }),
                    ..item.router_data
                })
            }
            NovalnetAPIStatus::Failure => {
                let response = Err(get_error_response(
                    item.response.result,
                    item.http_code,
                    transaction_id,
                ));
                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::Failure,
                        ..item.router_data.resource_common_data
                    },
                    response,
                    ..item.router_data
                })
            }
        }
    }
}

// Specific implementations for RepeatPayment flow
impl<
        T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize + Serialize,
    > TryFrom<ResponseRouterData<NovalnetPaymentsResponse, Self>>
    for RouterDataV2<RepeatPayment, PaymentFlowData, RepeatPaymentData<T>, PaymentsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<NovalnetPaymentsResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = item
            .response
            .transaction
            .clone()
            .and_then(|data| data.tid.map(|tid| tid.to_string()));

        match item.response.result.status {
            NovalnetAPIStatus::Success => {
                let redirection_data: Option<RedirectForm> =
                    item.response
                        .result
                        .redirect_url
                        .map(|url| RedirectForm::Form {
                            endpoint: url.expose().to_string(),
                            method: Method::Get,
                            form_fields: HashMap::new(),
                        });

                let mandate_reference_id = NovalnetPaymentsResponseTransactionData::get_token(
                    item.response.transaction.clone().as_ref(),
                );

                let transaction_status = item
                    .response
                    .transaction
                    .as_ref()
                    .and_then(|transaction_data| transaction_data.status)
                    .unwrap_or(if redirection_data.is_some() {
                        NovalnetTransactionStatus::Progress
                        // NOTE: Novalnet does not send us the transaction.status for redirection flow
                        // so status is mapped to Progress if flow has redirection data
                    } else {
                        NovalnetTransactionStatus::Pending
                    });

                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::from(transaction_status),
                        ..item.router_data.resource_common_data
                    },
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: transaction_id
                            .clone()
                            .map(ResponseId::ConnectorTransactionId)
                            .unwrap_or(ResponseId::NoResponseId),
                        redirection_data: redirection_data.map(Box::new),
                        mandate_reference: mandate_reference_id
                            .as_ref()
                            .map(|id| MandateReference {
                                connector_mandate_id: Some(id.clone()),
                                payment_method_id: None,
                                connector_mandate_request_reference_id: None,
                                mandate_metadata: None,
                            })
                            .map(Box::new),
                        connector_metadata: None,
                        network_txn_id: item.response.transaction.and_then(|data| {
                            data.payment_data
                                .and_then(|payment_data| match payment_data {
                                    NovalnetResponsePaymentData::Card(card) => {
                                        card.scheme_tid.map(|tid| tid.expose())
                                    }
                                    NovalnetResponsePaymentData::Paypal(_) => None,
                                })
                        }),
                        network_txn_link_id: None,
                        connector_response_reference_id: transaction_id.clone(),
                        incremental_authorization_allowed: None,
                        status_code: item.http_code,
                        splits: None,
                    }),
                    ..item.router_data
                })
            }
            NovalnetAPIStatus::Failure => {
                let response = Err(get_error_response(
                    item.response.result,
                    item.http_code,
                    transaction_id,
                ));
                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::Failure,
                        ..item.router_data.resource_common_data
                    },
                    response,
                    ..item.router_data
                })
            }
        }
    }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NovalnetResponseCustomer {
    pub billing: Option<NovalnetResponseBilling>,
    pub customer_ip: Option<Secret<String>>,
    pub email: Option<pii::Email>,
    pub first_name: Option<Secret<String>>,
    pub gender: Option<Secret<String>>,
    pub last_name: Option<Secret<String>>,
    pub mobile: Option<Secret<String>>,
    pub tel: Option<Secret<String>>,
    pub fax: Option<Secret<String>>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NovalnetResponseBilling {
    pub city: Option<Secret<String>>,
    pub country_code: Option<Secret<String>>,
    pub house_no: Option<Secret<String>>,
    pub street: Option<Secret<String>>,
    pub zip: Option<Secret<String>>,
    pub state: Option<Secret<String>>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NovalnetResponseMerchant {
    pub project: Option<Secret<i64>>,
    pub project_name: Option<Secret<String>>,
    pub project_url: Option<url::Url>,
    pub vendor: Option<Secret<i64>>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NovalnetAuthorizationResponse {
    expiry_date: Option<String>,
    auto_action: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NovalnetSyncResponseTransactionData {
    pub amount: Option<MinorUnit>,
    pub currency: Option<common_enums::Currency>,
    pub date: Option<String>,
    pub order_no: Option<String>,
    pub payment_data: Option<NovalnetResponsePaymentData>,
    pub payment_type: String,
    pub status: NovalnetTransactionStatus,
    pub status_code: u64,
    pub test_mode: u8,
    pub tid: Option<i64>,
    pub txn_secret: Option<Secret<String>>,
    pub authorization: Option<NovalnetAuthorizationResponse>,
    pub reason: Option<String>,
    pub reason_code: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum NovalnetResponsePaymentData {
    Card(NovalnetResponseCard),
    Paypal(NovalnetResponsePaypal),
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NovalnetResponseCard {
    pub card_brand: Option<Secret<String>>,
    pub card_expiry_month: Secret<u8>,
    pub card_expiry_year: Secret<u16>,
    pub card_holder: Secret<String>,
    pub card_number: Secret<String>,
    pub cc_3d: Option<Secret<u8>>,
    pub last_four: Option<Secret<String>>,
    pub token: Option<Secret<String>>,
    pub scheme_tid: Option<Secret<String>>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NovalnetResponsePaypal {
    pub paypal_account: Option<pii::Email>,
    pub paypal_transaction_id: Option<Secret<String>>,
    pub token: Option<Secret<String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetPSyncResponse {
    pub customer: Option<NovalnetResponseCustomer>,
    pub merchant: Option<NovalnetResponseMerchant>,
    pub result: ResultData,
    pub transaction: Option<NovalnetSyncResponseTransactionData>,
}

#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)]
pub enum CaptureType {
    #[default]
    Partial,
    Final,
}

#[derive(Default, Debug, Serialize)]
pub struct Capture {
    #[serde(rename = "type")]
    cap_type: CaptureType,
    reference: String,
}
#[derive(Default, Debug, Serialize)]
pub struct NovalnetTransaction {
    tid: String,
    amount: Option<StringMinorUnit>,
    capture: Capture,
}

#[derive(Default, Debug, Serialize)]
pub struct NovalnetCaptureRequest {
    pub transaction: NovalnetTransaction,
    pub custom: NovalnetCustom,
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        NovalnetRouterData<
            RouterDataV2<
                connector_flow::Capture,
                PaymentFlowData,
                PaymentsCaptureData,
                PaymentsResponseData,
            >,
            T,
        >,
    > for NovalnetCaptureRequest
{
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(
        item: NovalnetRouterData<
            RouterDataV2<
                connector_flow::Capture,
                PaymentFlowData,
                PaymentsCaptureData,
                PaymentsResponseData,
            >,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let capture_type = CaptureType::Final;
        let reference = item
            .router_data
            .resource_common_data
            .connector_request_reference_id
            .clone();
        let capture = Capture {
            cap_type: capture_type,
            reference,
        };

        let amount = item
            .connector
            .amount_converter
            .convert(
                item.router_data.request.minor_amount_to_capture,
                item.router_data.request.currency,
            )
            .change_context(IntegrationError::AmountConversionFailed {
                context: Default::default(),
            })?;

        let transaction = NovalnetTransaction {
            tid: item
                .router_data
                .request
                .get_connector_transaction_id()
                .change_context(IntegrationError::MissingConnectorTransactionID {
                    context: Default::default(),
                })?,
            capture,
            amount: Some(amount.to_owned()),
        };

        let custom = NovalnetCustom {
            lang: item
                .router_data
                .request
                .get_optional_language_from_browser_info()
                .unwrap_or(DEFAULT_LOCALE.to_string()),
        };
        Ok(Self {
            transaction,
            custom,
        })
    }
}

// Type definition for RefundRequest
#[derive(Default, Debug, Serialize)]
pub struct NovalnetRefundTransaction {
    tid: String,
    amount: Option<StringMinorUnit>,
}

#[derive(Default, Debug, Serialize)]
pub struct NovalnetRefundRequest {
    pub transaction: NovalnetRefundTransaction,
    pub custom: NovalnetCustom,
}

impl<F, T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        NovalnetRouterData<RouterDataV2<F, RefundFlowData, RefundsData, RefundsResponseData>, T>,
    > for NovalnetRefundRequest
{
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(
        item: NovalnetRouterData<
            RouterDataV2<F, RefundFlowData, RefundsData, RefundsResponseData>,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let amount = item
            .connector
            .amount_converter
            .convert(
                item.router_data.request.minor_refund_amount,
                item.router_data.request.currency,
            )
            .change_context(IntegrationError::AmountConversionFailed {
                context: Default::default(),
            })?;

        let transaction = NovalnetRefundTransaction {
            tid: item.router_data.request.connector_transaction_id.clone(),
            amount: Some(amount.to_owned()),
        };

        let custom = NovalnetCustom {
            lang: item
                .router_data
                .request
                .get_optional_language_from_browser_info()
                .unwrap_or(DEFAULT_LOCALE.to_string().to_string()),
        };
        Ok(Self {
            transaction,
            custom,
        })
    }
}

impl From<NovalnetTransactionStatus> for common_enums::RefundStatus {
    fn from(item: NovalnetTransactionStatus) -> Self {
        match item {
            NovalnetTransactionStatus::Success | NovalnetTransactionStatus::Confirmed => {
                Self::Success
            }
            NovalnetTransactionStatus::Pending => Self::Pending,
            NovalnetTransactionStatus::Failure
            | NovalnetTransactionStatus::Error
            | NovalnetTransactionStatus::OnHold
            | NovalnetTransactionStatus::Deactivated
            | NovalnetTransactionStatus::Progress => Self::Failure,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetRefundSyncResponse {
    result: ResultData,
    transaction: Option<NovalnetSyncResponseTransactionData>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetRefundsTransactionData {
    pub amount: Option<MinorUnit>,
    pub date: Option<String>,
    pub currency: Option<common_enums::Currency>,
    pub order_no: Option<String>,
    pub payment_type: String,
    pub refund: RefundData,
    pub refunded_amount: Option<u64>,
    pub status: NovalnetTransactionStatus,
    pub status_code: u64,
    pub test_mode: u8,
    pub tid: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefundData {
    amount: u64,
    currency: common_enums::Currency,
    payment_type: Option<String>,
    tid: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetRefundResponse {
    pub customer: Option<NovalnetResponseCustomer>,
    pub merchant: Option<NovalnetResponseMerchant>,
    pub result: ResultData,
    pub transaction: Option<NovalnetRefundsTransactionData>,
}

impl<F> TryFrom<ResponseRouterData<NovalnetRefundResponse, Self>>
    for RouterDataV2<F, RefundFlowData, RefundsData, RefundsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<NovalnetRefundResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let refund_id = item
            .response
            .transaction
            .clone()
            .and_then(|data| data.refund.tid.map(|tid| tid.to_string()))
            .ok_or(crate::utils::response_handling_fail_for_connector(
                item.http_code,
                "novalnet",
            ))?;

        match item.response.result.status {
            NovalnetAPIStatus::Success => {
                let transaction_status = item
                    .response
                    .transaction
                    .map(|transaction| transaction.status)
                    .unwrap_or(NovalnetTransactionStatus::Pending);

                Ok(Self {
                    response: Ok(RefundsResponseData {
                        connector_refund_id: refund_id,
                        refund_status: common_enums::RefundStatus::from(transaction_status),
                        status_code: item.http_code,
                        acquirer_reference_number: None,
                    }),
                    ..item.router_data
                })
            }
            NovalnetAPIStatus::Failure => {
                let response = Err(get_error_response(
                    item.response.result,
                    item.http_code,
                    Some(refund_id),
                ));
                Ok(Self {
                    response,
                    ..item.router_data
                })
            }
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct NovalnetRedirectionResponse {
    status: NovalnetTransactionStatus,
    tid: Option<String>,
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        NovalnetRouterData<
            RouterDataV2<PSync, PaymentFlowData, PaymentsSyncData, PaymentsResponseData>,
            T,
        >,
    > for NovalnetSyncRequest
{
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(
        item: NovalnetRouterData<
            RouterDataV2<PSync, PaymentFlowData, PaymentsSyncData, PaymentsResponseData>,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let transaction = if item
            .router_data
            .request
            .encoded_data
            .clone()
            .get_required_value("encoded_data")
            .is_ok()
        {
            let encoded_data = item
                .router_data
                .request
                .encoded_data
                .clone()
                .get_required_value("encoded_data")
                .change_context(IntegrationError::RequestEncodingFailed {
                    context: Default::default(),
                })?;
            let novalnet_redirection_response =
                serde_urlencoded::from_str::<NovalnetRedirectionResponse>(encoded_data.as_str())
                    .change_context(IntegrationError::InvalidDataFormat {
                        field_name: "encoded_data",
                        context: Default::default(),
                    })?;
            let tid = novalnet_redirection_response.tid.ok_or(
                IntegrationError::MissingRequiredField {
                    field_name: "tid",
                    context: Default::default(),
                },
            )?;
            NovalnetSyncTransaction { tid }
        } else {
            NovalnetSyncTransaction {
                tid: item
                    .router_data
                    .request
                    .get_connector_transaction_id()
                    .change_context(IntegrationError::MissingConnectorTransactionID {
                        context: Default::default(),
                    })?,
            }
        };

        let custom = NovalnetCustom {
            lang: DEFAULT_LOCALE.to_string().to_string(),
        };
        Ok(Self {
            transaction,
            custom,
        })
    }
}

impl NovalnetSyncResponseTransactionData {
    pub fn get_token(transaction_data: Option<&Self>) -> Option<String> {
        if let Some(data) = transaction_data {
            match &data.payment_data {
                Some(NovalnetResponsePaymentData::Card(card_data)) => {
                    card_data.token.clone().map(|token| token.expose())
                }
                Some(NovalnetResponsePaymentData::Paypal(paypal_data)) => {
                    paypal_data.token.clone().map(|token| token.expose())
                }
                None => None,
            }
        } else {
            None
        }
    }
}

impl<F> TryFrom<ResponseRouterData<NovalnetPSyncResponse, Self>>
    for RouterDataV2<F, PaymentFlowData, PaymentsSyncData, PaymentsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<NovalnetPSyncResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = item
            .response
            .transaction
            .clone()
            .and_then(|data| data.tid)
            .map(|tid| tid.to_string());

        match item.response.result.status {
            NovalnetAPIStatus::Success => {
                let transaction_status = item
                    .response
                    .transaction
                    .clone()
                    .map(|transaction_data| transaction_data.status)
                    .unwrap_or(NovalnetTransactionStatus::Pending);
                let mandate_reference_id = NovalnetSyncResponseTransactionData::get_token(
                    item.response.transaction.as_ref(),
                );

                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::from(transaction_status),
                        ..item.router_data.resource_common_data
                    },
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: transaction_id
                            .clone()
                            .map(ResponseId::ConnectorTransactionId)
                            .unwrap_or(ResponseId::NoResponseId),
                        redirection_data: None,
                        mandate_reference: mandate_reference_id
                            .as_ref()
                            .map(|id| MandateReference {
                                connector_mandate_id: Some(id.clone()),
                                payment_method_id: None,
                                connector_mandate_request_reference_id: None,
                                mandate_metadata: None,
                            })
                            .map(Box::new),
                        connector_metadata: None,
                        network_txn_id: item.response.transaction.and_then(|data| {
                            data.payment_data
                                .and_then(|payment_data| match payment_data {
                                    NovalnetResponsePaymentData::Card(card) => {
                                        card.scheme_tid.map(|tid| tid.expose())
                                    }
                                    NovalnetResponsePaymentData::Paypal(_) => None,
                                })
                        }),
                        network_txn_link_id: None,
                        connector_response_reference_id: transaction_id.clone(),
                        incremental_authorization_allowed: None,
                        status_code: item.http_code,
                        splits: None,
                    }),
                    ..item.router_data
                })
            }
            NovalnetAPIStatus::Failure => {
                let response = Err(get_error_response(
                    item.response.result,
                    item.http_code,
                    transaction_id,
                ));
                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::Failure,
                        ..item.router_data.resource_common_data
                    },
                    response,
                    ..item.router_data
                })
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetCaptureTransactionData {
    pub amount: Option<MinorUnit>,
    pub capture: CaptureData,
    pub currency: Option<common_enums::Currency>,
    pub order_no: Option<String>,
    pub payment_type: String,
    pub status: NovalnetTransactionStatus,
    pub status_code: Option<u64>,
    pub test_mode: Option<u8>,
    pub tid: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaptureData {
    amount: Option<u64>,
    payment_type: Option<String>,
    status: Option<String>,
    status_code: u64,
    tid: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetCaptureResponse {
    pub result: ResultData,
    pub transaction: Option<NovalnetCaptureTransactionData>,
}

impl<F> TryFrom<ResponseRouterData<NovalnetCaptureResponse, Self>>
    for RouterDataV2<F, PaymentFlowData, PaymentsCaptureData, PaymentsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<NovalnetCaptureResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = item
            .response
            .transaction
            .clone()
            .map(|data| data.tid.to_string());

        match item.response.result.status {
            NovalnetAPIStatus::Success => {
                let transaction_status = item
                    .response
                    .transaction
                    .map(|transaction_data| transaction_data.status)
                    .unwrap_or(NovalnetTransactionStatus::Pending);

                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::from(transaction_status),
                        ..item.router_data.resource_common_data
                    },
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: transaction_id
                            .clone()
                            .map(ResponseId::ConnectorTransactionId)
                            .unwrap_or(ResponseId::NoResponseId),
                        redirection_data: None,
                        mandate_reference: None,
                        connector_metadata: None,
                        network_txn_id: None,
                        network_txn_link_id: None,
                        connector_response_reference_id: transaction_id.clone(),
                        incremental_authorization_allowed: None,
                        status_code: item.http_code,
                        splits: None,
                    }),
                    ..item.router_data
                })
            }
            NovalnetAPIStatus::Failure => {
                let response = Err(get_error_response(
                    item.response.result,
                    item.http_code,
                    transaction_id,
                ));
                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::Failure,
                        ..item.router_data.resource_common_data
                    },
                    response,
                    ..item.router_data
                })
            }
        }
    }
}

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

#[derive(Default, Debug, Serialize)]
pub struct NovalnetSyncRequest {
    pub transaction: NovalnetSyncTransaction,
    pub custom: NovalnetCustom,
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        NovalnetRouterData<
            RouterDataV2<RSync, RefundFlowData, RefundSyncData, RefundsResponseData>,
            T,
        >,
    > for NovalnetSyncRequest
{
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(
        item: NovalnetRouterData<
            RouterDataV2<RSync, RefundFlowData, RefundSyncData, RefundsResponseData>,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let transaction = NovalnetSyncTransaction {
            tid: item.router_data.request.connector_transaction_id.clone(),
        };

        let custom = NovalnetCustom {
            lang: item
                .router_data
                .request
                .get_optional_language_from_browser_info()
                .unwrap_or(DEFAULT_LOCALE.to_string().to_string()),
        };
        Ok(Self {
            transaction,
            custom,
        })
    }
}

impl<F> TryFrom<ResponseRouterData<NovalnetRefundSyncResponse, Self>>
    for RouterDataV2<F, RefundFlowData, RefundSyncData, RefundsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<NovalnetRefundSyncResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let refund_id = item
            .response
            .transaction
            .clone()
            .and_then(|data| data.tid)
            .map(|tid| tid.to_string())
            .unwrap_or("".to_string());
        //NOTE: Mapping refund_id with "" incase we dont get any tid

        match item.response.result.status {
            NovalnetAPIStatus::Success => {
                let transaction_status = item
                    .response
                    .transaction
                    .map(|transaction_data| transaction_data.status)
                    .unwrap_or(NovalnetTransactionStatus::Pending);

                Ok(Self {
                    response: Ok(RefundsResponseData {
                        connector_refund_id: refund_id,
                        refund_status: common_enums::RefundStatus::from(transaction_status),
                        status_code: item.http_code,
                        acquirer_reference_number: None,
                    }),
                    ..item.router_data
                })
            }
            NovalnetAPIStatus::Failure => {
                let response = Err(get_error_response(
                    item.response.result,
                    item.http_code,
                    Some(refund_id),
                ));
                Ok(Self {
                    response,
                    ..item.router_data
                })
            }
        }
    }
}

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

#[derive(Default, Debug, Serialize)]
pub struct NovalnetCancelRequest {
    pub transaction: NovalnetCancelTransaction,
    pub custom: NovalnetCustom,
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        NovalnetRouterData<
            RouterDataV2<Void, PaymentFlowData, PaymentVoidData, PaymentsResponseData>,
            T,
        >,
    > for NovalnetCancelRequest
{
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(
        item: NovalnetRouterData<
            RouterDataV2<Void, PaymentFlowData, PaymentVoidData, PaymentsResponseData>,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let transaction = NovalnetCancelTransaction {
            tid: item.router_data.request.connector_transaction_id.clone(),
        };

        let custom = NovalnetCustom {
            lang: item
                .router_data
                .request
                .get_optional_language_from_browser_info()
                .unwrap_or(DEFAULT_LOCALE.to_string().to_string()),
        };
        Ok(Self {
            transaction,
            custom,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetCancelResponse {
    result: ResultData,
    transaction: Option<NovalnetPaymentsResponseTransactionData>,
}

impl<F> TryFrom<ResponseRouterData<NovalnetCancelResponse, Self>>
    for RouterDataV2<F, PaymentFlowData, PaymentVoidData, PaymentsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;

    fn try_from(
        item: ResponseRouterData<NovalnetCancelResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = item
            .response
            .transaction
            .clone()
            .and_then(|data| data.tid.map(|tid| tid.to_string()));

        match item.response.result.status {
            NovalnetAPIStatus::Success => {
                let transaction_status = item
                    .response
                    .transaction
                    .and_then(|transaction_data| transaction_data.status)
                    .unwrap_or(NovalnetTransactionStatus::Pending);
                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: if transaction_status == NovalnetTransactionStatus::Deactivated {
                            common_enums::AttemptStatus::Voided
                        } else {
                            common_enums::AttemptStatus::VoidFailed
                        },
                        ..item.router_data.resource_common_data
                    },

                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: transaction_id
                            .clone()
                            .map(ResponseId::ConnectorTransactionId)
                            .unwrap_or(ResponseId::NoResponseId),
                        redirection_data: None,
                        mandate_reference: None,
                        connector_metadata: None,
                        network_txn_id: None,
                        network_txn_link_id: None,
                        connector_response_reference_id: transaction_id.clone(),
                        incremental_authorization_allowed: None,
                        status_code: item.http_code,
                        splits: None,
                    }),
                    ..item.router_data
                })
            }
            NovalnetAPIStatus::Failure => {
                let response = Err(get_error_response(
                    item.response.result,
                    item.http_code,
                    transaction_id,
                ));
                Ok(Self {
                    resource_common_data: PaymentFlowData {
                        status: common_enums::AttemptStatus::Failure,
                        ..item.router_data.resource_common_data
                    },
                    response,
                    ..item.router_data
                })
            }
        }
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct NovalnetErrorResponse {
    pub status_code: u64,
    pub code: String,
    pub message: String,
    pub reason: Option<String>,
}

#[derive(Display, Debug, Serialize, Deserialize)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum WebhookEventType {
    Payment,
    TransactionCapture,
    TransactionCancel,
    TransactionRefund,
    Chargeback,
    Credit,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct NovalnetWebhookEvent {
    pub checksum: String,
    pub tid: i64,
    pub parent_tid: Option<i64>,
    #[serde(rename = "type")]
    pub event_type: WebhookEventType,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum NovalnetWebhookTransactionData {
    SyncTransactionData(NovalnetSyncResponseTransactionData),
    CaptureTransactionData(NovalnetCaptureTransactionData),
    CancelTransactionData(NovalnetPaymentsResponseTransactionData),
    RefundsTransactionData(NovalnetRefundsTransactionData),
}
#[derive(Serialize, Deserialize, Debug)]
pub struct NovalnetWebhookNotificationResponse {
    pub event: NovalnetWebhookEvent,
    pub result: ResultData,
    pub transaction: NovalnetWebhookTransactionData,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct NovalnetWebhookNotificationResponseRefunds {
    pub event: NovalnetWebhookEvent,
    pub result: ResultData,
    pub transaction: NovalnetRefundsTransactionData,
}

pub fn is_refund_event(event_code: &WebhookEventType) -> bool {
    matches!(event_code, WebhookEventType::TransactionRefund)
}

pub fn reverse_string(s: &str) -> String {
    s.chars().rev().collect()
}

#[derive(Display, Debug, Serialize, Deserialize)]
pub enum WebhookDisputeStatus {
    DisputeOpened,
    DisputeWon,
    Unknown,
}

pub fn get_novalnet_dispute_status(status: WebhookEventType) -> WebhookDisputeStatus {
    match status {
        WebhookEventType::Chargeback => WebhookDisputeStatus::DisputeOpened,
        WebhookEventType::Credit => WebhookDisputeStatus::DisputeWon,
        _ => WebhookDisputeStatus::Unknown,
    }
}

impl ForeignTryFrom<WebhookDisputeStatus> for common_enums::DisputeStatus {
    type Error = IntegrationError;

    fn foreign_try_from(value: WebhookDisputeStatus) -> error_stack::Result<Self, Self::Error> {
        match value {
            WebhookDisputeStatus::DisputeOpened => Ok(Self::DisputeOpened),
            WebhookDisputeStatus::DisputeWon => Ok(Self::DisputeWon),
            WebhookDisputeStatus::Unknown => Err(IntegrationError::NotImplemented(
                "webhook body decoding failed".to_string(),
                Default::default(),
            ))?,
        }
    }
}

pub fn option_to_result<T>(opt: Option<T>) -> Result<T, IntegrationError> {
    opt.ok_or(IntegrationError::NotImplemented(
        "webhook body decoding failed".to_string(),
        Default::default(),
    ))
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        NovalnetRouterData<
            RouterDataV2<
                SetupMandate,
                PaymentFlowData,
                SetupMandateRequestData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    > for NovalnetPaymentsRequest<T>
{
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(
        item: NovalnetRouterData<
            RouterDataV2<
                SetupMandate,
                PaymentFlowData,
                SetupMandateRequestData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    ) -> Result<Self, error_stack::Report<IntegrationError>> {
        let auth = NovalnetAuthType::try_from(&item.router_data.connector_config)?;

        let merchant = NovalnetPaymentsRequestMerchant {
            signature: auth.product_activation_key,
            tariff: auth.tariff_id,
        };

        let enforce_3d = match item.router_data.resource_common_data.auth_type {
            common_enums::AuthenticationType::ThreeDs => Some(1),
            common_enums::AuthenticationType::NoThreeDs => None,
        };
        let test_mode = get_test_mode(item.router_data.resource_common_data.test_mode);
        let req_address = item.router_data.resource_common_data.get_optional_billing();

        let billing = NovalnetPaymentsRequestBilling {
            house_no: item
                .router_data
                .resource_common_data
                .get_optional_billing_line1(),
            street: item
                .router_data
                .resource_common_data
                .get_optional_billing_line2(),
            city: item
                .router_data
                .resource_common_data
                .get_optional_billing_city(),
            zip: item
                .router_data
                .resource_common_data
                .get_optional_billing_zip(),
            country_code: item
                .router_data
                .resource_common_data
                .get_optional_billing_country(),
        };

        let email = item
            .router_data
            .resource_common_data
            .get_billing_email()
            .or(item.router_data.request.get_email())?;

        let customer = NovalnetPaymentsRequestCustomer {
            first_name: req_address.and_then(|addr| addr.get_optional_first_name()),
            last_name: req_address.and_then(|addr| addr.get_optional_last_name()),
            email,
            mobile: item
                .router_data
                .resource_common_data
                .get_optional_billing_phone_number(),
            billing: Some(billing),
            // no_nc is used to indicate if minimal customer data is passed or not
            no_nc: MINIMAL_CUSTOMER_DATA_PASSED,
            // DOB should be populated in case of SepaGuaranteedBankDebit payments
            birth_date: None,
        };

        let lang = item
            .router_data
            .request
            .get_optional_language_from_browser_info()
            .unwrap_or(DEFAULT_LOCALE.to_string().to_string());

        let custom = NovalnetCustom { lang };
        let hook_url = item.router_data.request.get_webhook_url()?;
        let return_url = item.router_data.request.get_router_return_url()?;
        let create_token = Some(CREATE_TOKEN_REQUIRED);

        match item.router_data.request.payment_method_data {
            PaymentMethodData::Card(ref req_card) => {
                let novalnet_card = NovalNetPaymentData::Card(NovalnetCard {
                    card_number: req_card.card_number.clone(),
                    card_expiry_month: req_card.card_exp_month.clone(),
                    card_expiry_year: req_card.card_exp_year.clone(),
                    card_cvc: req_card.card_cvc.clone(),
                    card_holder: item
                        .router_data
                        .resource_common_data
                        .get_billing_address()?
                        .get_full_name()?,
                });

                let transaction = NovalnetPaymentsRequestTransaction {
                    test_mode,
                    payment_type: NovalNetPaymentTypes::CREDITCARD,
                    amount: NovalNetAmount::Int(0),
                    currency: item.router_data.request.currency,
                    order_no: item
                        .router_data
                        .resource_common_data
                        .connector_request_reference_id
                        .clone(),
                    hook_url: Some(hook_url),
                    return_url: Some(return_url.clone()),
                    error_return_url: Some(return_url.clone()),
                    payment_data: Some(novalnet_card),
                    enforce_3d,
                    create_token,
                };

                Ok(Self {
                    merchant,
                    transaction,
                    customer,
                    custom,
                })
            }

            PaymentMethodData::Wallet(ref wallet_data) => match wallet_data {
                WalletDataPaymentMethod::GooglePay(ref req_wallet) => {
                    let novalnet_google_pay: NovalNetPaymentData<T> =
                        NovalNetPaymentData::GooglePay(NovalnetGooglePay {
                            wallet_data: Secret::new(
                                req_wallet
                                    .tokenization_data
                                    .get_encrypted_google_pay_token()
                                    .change_context(IntegrationError::MissingRequiredField {
                                        field_name: "gpay wallet_token",
                                        context: Default::default(),
                                    })?
                                    .clone(),
                            ),
                        });

                    let transaction = NovalnetPaymentsRequestTransaction {
                        test_mode,
                        payment_type: NovalNetPaymentTypes::GOOGLEPAY,
                        amount: NovalNetAmount::Int(0),
                        currency: item.router_data.request.currency,
                        order_no: item
                            .router_data
                            .resource_common_data
                            .connector_request_reference_id
                            .clone(),
                        hook_url: Some(hook_url),
                        return_url: None,
                        error_return_url: None,
                        payment_data: Some(novalnet_google_pay),
                        enforce_3d,
                        create_token,
                    };

                    Ok(Self {
                        merchant,
                        transaction,
                        customer,
                        custom,
                    })
                }
                WalletDataPaymentMethod::ApplePay(payment_method_data) => {
                    let transaction = NovalnetPaymentsRequestTransaction {
                        test_mode,
                        payment_type: NovalNetPaymentTypes::APPLEPAY,
                        amount: NovalNetAmount::Int(0),
                        currency: item.router_data.request.currency,
                        order_no: item
                            .router_data
                            .resource_common_data
                            .connector_request_reference_id
                            .clone(),
                        hook_url: Some(hook_url),
                        return_url: None,
                        error_return_url: None,
                        payment_data: Some(NovalNetPaymentData::ApplePay(NovalnetApplePay {
                            wallet_data: payment_method_data.get_applepay_decoded_payment_data()?,
                        })),
                        enforce_3d: None,
                        create_token,
                    };

                    Ok(Self {
                        merchant,
                        transaction,
                        customer,
                        custom,
                    })
                }
                WalletDataPaymentMethod::AliPayQr(_)
                | WalletDataPaymentMethod::BluecodeRedirect { .. }
                | WalletDataPaymentMethod::AliPayRedirect(_)
                | WalletDataPaymentMethod::AliPayHkRedirect(_)
                | WalletDataPaymentMethod::AmazonPayRedirect(_)
                | WalletDataPaymentMethod::MomoRedirect(_)
                | WalletDataPaymentMethod::KakaoPayRedirect(_)
                | WalletDataPaymentMethod::GoPayRedirect(_)
                | WalletDataPaymentMethod::GcashRedirect(_)
                | WalletDataPaymentMethod::ApplePayRedirect(_)
                | WalletDataPaymentMethod::ApplePayThirdPartySdk(_)
                | WalletDataPaymentMethod::DanaRedirect {}
                | WalletDataPaymentMethod::GooglePayRedirect(_)
                | WalletDataPaymentMethod::GooglePayThirdPartySdk(_)
                | WalletDataPaymentMethod::MbWayRedirect(_)
                | WalletDataPaymentMethod::MobilePayRedirect(_)
                | WalletDataPaymentMethod::RevolutPay(_) => Err(IntegrationError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("novalnet"),
                    Default::default(),
                ))?,
                WalletDataPaymentMethod::PaypalRedirect(_) => {
                    let transaction = NovalnetPaymentsRequestTransaction {
                        test_mode,
                        payment_type: NovalNetPaymentTypes::PAYPAL,
                        amount: NovalNetAmount::Int(0),
                        currency: item.router_data.request.currency,
                        order_no: item
                            .router_data
                            .resource_common_data
                            .connector_request_reference_id
                            .clone(),
                        hook_url: Some(hook_url),
                        return_url: Some(return_url.clone()),
                        error_return_url: Some(return_url.clone()),
                        payment_data: None,
                        enforce_3d: None,
                        create_token,
                    };

                    Ok(Self {
                        merchant,
                        transaction,
                        customer,
                        custom,
                    })
                }
                WalletDataPaymentMethod::PaypalSdk(_)
                | WalletDataPaymentMethod::Paze(_)
                | WalletDataPaymentMethod::SamsungPay(_)
                | WalletDataPaymentMethod::TwintRedirect {}
                | WalletDataPaymentMethod::VippsRedirect {}
                | WalletDataPaymentMethod::TouchNGoRedirect(_)
                | WalletDataPaymentMethod::WeChatPayRedirect(_)
                | WalletDataPaymentMethod::CashappQr(_)
                | WalletDataPaymentMethod::SwishQr(_)
                | WalletDataPaymentMethod::WeChatPayQr(_)
                | WalletDataPaymentMethod::Mifinity(_)
                | WalletDataPaymentMethod::MbWay(_)
                | WalletDataPaymentMethod::Satispay(_)
                | WalletDataPaymentMethod::Wero(_)
                | WalletDataPaymentMethod::LazyPayRedirect(_)
                | WalletDataPaymentMethod::PhonePeRedirect(_)
                | WalletDataPaymentMethod::BillDeskRedirect(_)
                | WalletDataPaymentMethod::CashfreeRedirect(_)
                | WalletDataPaymentMethod::PayURedirect(_)
                | WalletDataPaymentMethod::EaseBuzzRedirect(_)
                | WalletDataPaymentMethod::PaymayaRedirect(_)
                | WalletDataPaymentMethod::QwikcilverWalletDirect(_)
                | WalletDataPaymentMethod::Skrill(_) => Err(IntegrationError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("novalnet"),
                    Default::default(),
                ))?,
            },
            _ => Err(IntegrationError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("novalnet"),
                Default::default(),
            ))?,
        }
    }
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        NovalnetRouterData<
            RouterDataV2<
                RepeatPayment,
                PaymentFlowData,
                RepeatPaymentData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    > for NovalnetPaymentsRequest<T>
{
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(
        item: NovalnetRouterData<
            RouterDataV2<
                RepeatPayment,
                PaymentFlowData,
                RepeatPaymentData<T>,
                PaymentsResponseData,
            >,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let auth = NovalnetAuthType::try_from(&item.router_data.connector_config)?;

        let merchant = NovalnetPaymentsRequestMerchant {
            signature: auth.product_activation_key,
            tariff: auth.tariff_id,
        };

        let enforce_3d = match item.router_data.resource_common_data.auth_type {
            common_enums::AuthenticationType::ThreeDs => Some(1),
            common_enums::AuthenticationType::NoThreeDs => None,
        };
        let test_mode = get_test_mode(item.router_data.resource_common_data.test_mode);

        let billing = NovalnetPaymentsRequestBilling {
            house_no: item
                .router_data
                .resource_common_data
                .get_optional_billing_line1(),
            street: item
                .router_data
                .resource_common_data
                .get_optional_billing_line2(),
            city: item
                .router_data
                .resource_common_data
                .get_optional_billing_city(),
            zip: item
                .router_data
                .resource_common_data
                .get_optional_billing_zip(),
            country_code: item
                .router_data
                .resource_common_data
                .get_optional_billing_country(),
        };

        let customer = NovalnetPaymentsRequestCustomer {
            first_name: item
                .router_data
                .resource_common_data
                .get_optional_billing_first_name(),
            last_name: item
                .router_data
                .resource_common_data
                .get_optional_billing_last_name(),
            email: item
                .router_data
                .resource_common_data
                .get_billing_email()
                .or(item.router_data.request.get_email())?,
            mobile: item
                .router_data
                .resource_common_data
                .get_optional_billing_phone_number(),
            billing: Some(billing),
            // no_nc is used to indicate if minimal customer data is passed or not
            no_nc: MINIMAL_CUSTOMER_DATA_PASSED,
            // DOB should be populated in case of SepaGuaranteedBankDebit payments
            birth_date: None,
        };

        let lang = item
            .router_data
            .request
            .get_optional_language_from_browser_info()
            .unwrap_or(DEFAULT_LOCALE.to_string().to_string());
        let custom = NovalnetCustom { lang };
        let hook_url = item.router_data.request.get_webhook_url()?;
        let return_url = item.router_data.request.get_router_return_url()?;

        let amount = item
            .connector
            .amount_converter
            .convert(
                item.router_data.request.minor_amount,
                item.router_data.request.currency,
            )
            .change_context(IntegrationError::AmountConversionFailed {
                context: Default::default(),
            })?;

        match item.router_data.request.mandate_reference {
            MandateReferenceId::ConnectorMandateId(mandate_data) => {
                let connector_mandate_id = mandate_data.get_connector_mandate_id().ok_or(
                    IntegrationError::MissingRequiredField {
                        field_name: "connector_mandate_id",
                        context: Default::default(),
                    },
                )?;

                let novalnet_mandate_data = NovalNetPaymentData::MandatePayment(NovalnetMandate {
                    token: Secret::new(connector_mandate_id),
                });

                let payment_type = match item.router_data.request.payment_method_type {
                    Some(pm_type) => NovalNetPaymentTypes::try_from(&pm_type)?,
                    None => NovalNetPaymentTypes::CREDITCARD,
                };

                let transaction = NovalnetPaymentsRequestTransaction {
                    test_mode,
                    payment_type,
                    amount: NovalNetAmount::StringMinor(amount.clone()),
                    currency: item.router_data.request.currency,
                    order_no: item
                        .router_data
                        .resource_common_data
                        .connector_request_reference_id
                        .clone(),
                    hook_url: Some(hook_url),
                    return_url: None,
                    error_return_url: None,
                    payment_data: Some(novalnet_mandate_data),
                    enforce_3d,
                    create_token: None,
                };

                Ok(Self {
                    merchant,
                    transaction,
                    customer,
                    custom,
                })
            }
            MandateReferenceId::NetworkMandateId(network_transaction_id) => {
                match item.router_data.request.payment_method_data {
                    PaymentMethodData::CardDetailsForNetworkTransactionId(ref raw_card_details) => {
                        let novalnet_card =
                            NovalNetPaymentData::RawCardForNTI(NovalnetRawCardDetails {
                                card_number: raw_card_details.card_number.clone(),
                                card_expiry_month: raw_card_details.card_exp_month.clone(),
                                card_expiry_year: raw_card_details.card_exp_year.clone(),
                                scheme_tid: network_transaction_id.network_transaction_id.into(),
                            });

                        let transaction = NovalnetPaymentsRequestTransaction {
                            test_mode,
                            payment_type: NovalNetPaymentTypes::CREDITCARD,
                            amount: NovalNetAmount::StringMinor(amount.clone()),
                            currency: item.router_data.request.currency,
                            order_no: item
                                .router_data
                                .resource_common_data
                                .connector_request_reference_id
                                .clone(),
                            hook_url: Some(hook_url),
                            return_url: Some(return_url.clone()),
                            error_return_url: Some(return_url.clone()),
                            payment_data: Some(novalnet_card),
                            enforce_3d,
                            create_token: Some(CREATE_TOKEN_REQUIRED),
                        };

                        Ok(Self {
                            merchant,
                            transaction,
                            customer,
                            custom,
                        })
                    }
                    _ => Err(IntegrationError::NotImplemented(
                        utils::get_unimplemented_payment_method_error_message("novalnet"),
                        Default::default(),
                    )
                    .into()),
                }
            }
            _ => Err(IntegrationError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("novalnet"),
                Default::default(),
            )
            .into()),
        }
    }
}

pub fn get_incoming_webhook_event(
    status: WebhookEventType,
    transaction_status: NovalnetTransactionStatus,
) -> EventType {
    match status {
        WebhookEventType::Payment => match transaction_status {
            NovalnetTransactionStatus::Confirmed | NovalnetTransactionStatus::Success => {
                EventType::PaymentIntentSuccess
            }
            NovalnetTransactionStatus::OnHold => EventType::PaymentIntentAuthorizationSuccess,
            NovalnetTransactionStatus::Pending => EventType::PaymentIntentProcessing,
            NovalnetTransactionStatus::Progress => EventType::IncomingWebhookEventUnspecified,
            _ => EventType::PaymentIntentFailure,
        },
        WebhookEventType::TransactionCapture => match transaction_status {
            NovalnetTransactionStatus::Confirmed | NovalnetTransactionStatus::Success => {
                EventType::PaymentIntentCaptureSuccess
            }
            _ => EventType::PaymentIntentCaptureFailure,
        },
        WebhookEventType::TransactionCancel => match transaction_status {
            NovalnetTransactionStatus::Deactivated => EventType::PaymentIntentCancelled,
            _ => EventType::PaymentIntentCancelFailure,
        },
        WebhookEventType::TransactionRefund => match transaction_status {
            NovalnetTransactionStatus::Confirmed | NovalnetTransactionStatus::Success => {
                EventType::RefundSuccess
            }
            _ => EventType::RefundFailure,
        },
        WebhookEventType::Chargeback => EventType::DisputeOpened,
        WebhookEventType::Credit => EventType::DisputeWon,
    }
}

impl TryFrom<NovalnetWebhookNotificationResponse> for WebhookDetailsResponse {
    type Error = error_stack::Report<IntegrationError>;

    fn try_from(notif: NovalnetWebhookNotificationResponse) -> Result<Self, Self::Error> {
        match notif.transaction {
            NovalnetWebhookTransactionData::SyncTransactionData(response) => {
                let transaction_id = response.tid.map(|tid| tid.to_string());

                match notif.result.status {
                    NovalnetAPIStatus::Success => {
                        let mandate_reference_id =
                            NovalnetSyncResponseTransactionData::get_token(Some(&response));
                        let transaction_status = response.status;

                        Ok(Self {
                            status: common_enums::AttemptStatus::from(transaction_status),
                            resource_id: Some(
                                transaction_id
                                    .clone()
                                    .map(ResponseId::ConnectorTransactionId)
                                    .unwrap_or(ResponseId::NoResponseId),
                            ),
                            mandate_reference: mandate_reference_id
                                .as_ref()
                                .map(|id| MandateReference {
                                    connector_mandate_id: Some(id.clone()),
                                    payment_method_id: None,
                                    connector_mandate_request_reference_id: None,
                                    mandate_metadata: None,
                                })
                                .map(Box::new),
                            status_code: 200,
                            connector_response_reference_id: transaction_id.clone(),
                            connector_request_reference_id: transaction_id.clone(),
                            error_code: None,
                            error_message: None,
                            raw_connector_response: None,
                            response_headers: None,
                            minor_amount_captured: None,
                            amount_captured: None,
                            error_reason: None,
                            network_txn_id: response.payment_data.and_then(|payment_data| {
                                match payment_data {
                                    NovalnetResponsePaymentData::Card(card) => {
                                        card.scheme_tid.map(|tid| tid.expose())
                                    }
                                    NovalnetResponsePaymentData::Paypal(_) => None,
                                }
                            }),
                            payment_method_update: None,
                            sender_payment_instrument_id: None,
                        })
                    }
                    NovalnetAPIStatus::Failure => Ok(Self {
                        status: common_enums::AttemptStatus::Failure,
                        resource_id: Some(
                            transaction_id
                                .clone()
                                .map(ResponseId::ConnectorTransactionId)
                                .unwrap_or(ResponseId::NoResponseId),
                        ),
                        status_code: 200,
                        mandate_reference: None,
                        connector_response_reference_id: None,
                        connector_request_reference_id: None,
                        error_code: Some(notif.result.status.to_string()),
                        error_message: Some(notif.result.status_text),
                        raw_connector_response: None,
                        response_headers: None,
                        minor_amount_captured: None,
                        amount_captured: None,
                        error_reason: None,
                        network_txn_id: None,
                        payment_method_update: None,
                        sender_payment_instrument_id: None,
                    }),
                }
            }
            _ => Err(IntegrationError::NotImplemented(
                "webhook body decoding failed".to_string(),
                Default::default(),
            ))?,
        }
    }
}

impl TryFrom<NovalnetWebhookNotificationResponseRefunds> for RefundWebhookDetailsResponse {
    type Error = error_stack::Report<IntegrationError>;

    fn try_from(notif: NovalnetWebhookNotificationResponseRefunds) -> Result<Self, Self::Error> {
        let refund_id = notif
            .transaction
            .refund
            .tid
            .map(|tid| tid.to_string())
            .ok_or(IntegrationError::NotImplemented(
                "missing refund transaction id in webhook".to_string(),
                Default::default(),
            ))?;

        match notif.result.status {
            NovalnetAPIStatus::Success => {
                let transaction_status = notif.transaction.status;

                Ok(Self {
                    connector_refund_id: Some(refund_id),
                    merchant_transaction_id: None,
                    status: common_enums::RefundStatus::from(transaction_status),
                    status_code: 200,
                    connector_response_reference_id: None,
                    error_code: None,
                    error_message: None,
                    raw_connector_response: None,
                    response_headers: None,
                })
            }
            NovalnetAPIStatus::Failure => Ok(Self {
                status: common_enums::RefundStatus::Failure,
                connector_refund_id: Some(refund_id),
                merchant_transaction_id: None,
                status_code: 200,
                connector_response_reference_id: None,
                error_code: Some(notif.result.status.to_string()),
                error_message: Some(notif.result.status_text),
                raw_connector_response: None,
                response_headers: None,
            }),
        }
    }
}

// =============================================================================
// Incremental Authorization
//
// Novalnet exposes incremental authorization through its transaction amount
// update endpoint: POST https://payport.novalnet.de/v2/transaction/update
// The endpoint allows a merchant to update the authorized amount of an
// existing on-hold / pending transaction (primarily supported for Direct
// Debit SEPA, Direct Debit ACH, Invoice, Prepayment & Barzahlen/viacash).
// The response envelope is the same `{ result, transaction }` shape used by
// capture/void — we reuse the shared `ResultData` type here.
// =============================================================================

#[derive(Debug, Serialize)]
pub struct NovalnetIncrementalAuthTransaction {
    pub tid: String,
    pub amount: StringMinorUnit,
}

#[derive(Debug, Serialize)]
pub struct NovalnetIncrementalAuthRequest {
    pub transaction: NovalnetIncrementalAuthTransaction,
    pub custom: NovalnetCustom,
}

impl<T: PaymentMethodDataTypes + std::fmt::Debug + Sync + Send + 'static + Serialize>
    TryFrom<
        NovalnetRouterData<
            RouterDataV2<
                IncrementalAuthorization,
                PaymentFlowData,
                PaymentsIncrementalAuthorizationData,
                PaymentsResponseData,
            >,
            T,
        >,
    > for NovalnetIncrementalAuthRequest
{
    type Error = error_stack::Report<IntegrationError>;
    fn try_from(
        item: NovalnetRouterData<
            RouterDataV2<
                IncrementalAuthorization,
                PaymentFlowData,
                PaymentsIncrementalAuthorizationData,
                PaymentsResponseData,
            >,
            T,
        >,
    ) -> Result<Self, Self::Error> {
        let tid = item
            .router_data
            .request
            .connector_transaction_id
            .get_connector_transaction_id()
            .change_context(IntegrationError::MissingConnectorTransactionID {
                context: Default::default(),
            })?;

        let amount = item
            .connector
            .amount_converter
            .convert(
                item.router_data.request.minor_amount,
                item.router_data.request.currency,
            )
            .change_context(IntegrationError::AmountConversionFailed {
                context: Default::default(),
            })?;

        let transaction = NovalnetIncrementalAuthTransaction { tid, amount };

        let custom = NovalnetCustom {
            lang: DEFAULT_LOCALE.to_string(),
        };

        Ok(Self {
            transaction,
            custom,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetIncrementalAuthTransactionData {
    pub amount: Option<MinorUnit>,
    pub currency: Option<common_enums::Currency>,
    pub order_no: Option<String>,
    pub payment_type: Option<String>,
    pub status: Option<NovalnetTransactionStatus>,
    pub status_code: Option<u64>,
    pub test_mode: Option<u8>,
    pub tid: Option<i64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NovalnetIncrementalAuthResponse {
    pub result: ResultData,
    pub transaction: Option<NovalnetIncrementalAuthTransactionData>,
}

impl TryFrom<ResponseRouterData<NovalnetIncrementalAuthResponse, Self>>
    for RouterDataV2<
        IncrementalAuthorization,
        PaymentFlowData,
        PaymentsIncrementalAuthorizationData,
        PaymentsResponseData,
    >
{
    type Error = error_stack::Report<ConnectorError>;

    fn try_from(
        item: ResponseRouterData<NovalnetIncrementalAuthResponse, Self>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = item
            .response
            .transaction
            .as_ref()
            .and_then(|data| data.tid.map(|tid| tid.to_string()));

        let transaction_status = item
            .response
            .transaction
            .as_ref()
            .and_then(|data| data.status);

        match item.response.result.status {
            NovalnetAPIStatus::Success => {
                // Novalnet can return a SUCCESS envelope with a transaction-level
                // status that still indicates the increment was not honoured
                // (Failure/Deactivated). Route those back through the error path
                // rather than falsely reporting AuthorizationStatus::Success.
                let authorization_status = match transaction_status {
                    Some(NovalnetTransactionStatus::Success)
                    | Some(NovalnetTransactionStatus::Confirmed)
                    | Some(NovalnetTransactionStatus::OnHold) => {
                        // OnHold = authorization still held after a successful
                        // amount update (Novalnet keeps the auth in ON_HOLD
                        // until a subsequent capture/void).
                        common_enums::AuthorizationStatus::Success
                    }
                    Some(NovalnetTransactionStatus::Pending)
                    | Some(NovalnetTransactionStatus::Progress)
                    | None => {
                        // Missing transaction / no definitive signal — let the
                        // caller re-sync rather than optimistically marking
                        // success.
                        common_enums::AuthorizationStatus::Processing
                    }
                    Some(NovalnetTransactionStatus::Failure)
                    | Some(NovalnetTransactionStatus::Error)
                    | Some(NovalnetTransactionStatus::Deactivated) => {
                        common_enums::AuthorizationStatus::Failure
                    }
                };

                if matches!(
                    authorization_status,
                    common_enums::AuthorizationStatus::Failure
                ) {
                    // Incremental-auth failure does not invalidate the
                    // original authorization — leave the attempt status
                    // untouched so the initial auth remains capturable at
                    // its existing amount.
                    return Ok(Self {
                        response: Err(get_error_response(
                            item.response.result,
                            item.http_code,
                            transaction_id,
                        )),
                        ..item.router_data
                    });
                }

                Ok(Self {
                    response: Ok(PaymentsResponseData::IncrementalAuthorizationResponse {
                        status: authorization_status,
                        connector_authorization_id: transaction_id,
                        status_code: item.http_code,
                    }),
                    ..item.router_data
                })
            }
            NovalnetAPIStatus::Failure => {
                // Incremental-auth failure does not invalidate the original
                // authorization — surface the error but keep the attempt
                // status so the initial auth remains valid.
                Ok(Self {
                    response: Err(get_error_response(
                        item.response.result,
                        item.http_code,
                        transaction_id,
                    )),
                    ..item.router_data
                })
            }
        }
    }
}
