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

#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
use std::str::FromStr;

use api_models::subscription as api;
use common_enums::{connector_enums, enums};
use common_utils::{
    errors::CustomResult,
    ext_traits::ByteSliceExt,
    id_type::{CustomerId, InvoiceId, SubscriptionId},
    pii::{self, Email},
    types::MinorUnit,
};
use error_stack::ResultExt;
#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
use hyperswitch_domain_models::revenue_recovery;
use hyperswitch_domain_models::{
    payment_method_data::PaymentMethodData,
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::{subscriptions::SubscriptionAutoCollection, ResponseId},
    router_response_types::{
        revenue_recovery::InvoiceRecordBackResponse,
        subscriptions::{
            self, GetSubscriptionEstimateResponse, GetSubscriptionPlanPricesResponse,
            GetSubscriptionPlansResponse, SubscriptionCancelResponse, SubscriptionCreateResponse,
            SubscriptionInvoiceData, SubscriptionLineItem, SubscriptionPauseResponse,
            SubscriptionResumeResponse, SubscriptionStatus,
        },
        ConnectorCustomerResponseData, PaymentsResponseData, RefundsResponseData,
    },
    types::{
        GetSubscriptionEstimateRouterData, InvoiceRecordBackRouterData,
        PaymentsAuthorizeRouterData, RefundsRouterData, SubscriptionCancelRouterData,
        SubscriptionPauseRouterData, SubscriptionResumeRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::{ExposeInterface, Secret};
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;

use crate::{
    convert_connector_response_to_domain_response,
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{self, PaymentsAuthorizeRequestData, RouterData as OtherRouterData},
};

// SubscriptionCreate structures
#[derive(Debug, Serialize)]
pub struct ChargebeeSubscriptionCreateRequest {
    #[serde(rename = "id")]
    pub subscription_id: SubscriptionId,
    #[serde(rename = "subscription_items[item_price_id][0]")]
    pub item_price_id: String,
    #[serde(rename = "subscription_items[quantity][0]")]
    pub quantity: Option<u32>,
    #[serde(rename = "billing_address[line1]")]
    pub billing_address_line1: Option<Secret<String>>,
    #[serde(rename = "billing_address[city]")]
    pub billing_address_city: Option<String>,
    #[serde(rename = "billing_address[state]")]
    pub billing_address_state: Option<Secret<String>>,
    #[serde(rename = "billing_address[zip]")]
    pub billing_address_zip: Option<Secret<String>>,
    #[serde(rename = "billing_address[country]")]
    pub billing_address_country: Option<common_enums::CountryAlpha2>,
    pub auto_collection: ChargebeeAutoCollection,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeAutoCollection {
    On,
    Off,
}

impl From<SubscriptionAutoCollection> for ChargebeeAutoCollection {
    fn from(auto_collection: SubscriptionAutoCollection) -> Self {
        match auto_collection {
            SubscriptionAutoCollection::On => Self::On,
            SubscriptionAutoCollection::Off => Self::Off,
        }
    }
}

impl TryFrom<&ChargebeeRouterData<&hyperswitch_domain_models::types::SubscriptionCreateRouterData>>
    for ChargebeeSubscriptionCreateRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &ChargebeeRouterData<&hyperswitch_domain_models::types::SubscriptionCreateRouterData>,
    ) -> Result<Self, Self::Error> {
        let req = &item.router_data.request;

        let first_item =
            req.subscription_items
                .first()
                .ok_or(errors::ConnectorError::MissingRequiredField {
                    field_name: "subscription_items",
                })?;

        Ok(Self {
            subscription_id: req.subscription_id.clone(),
            item_price_id: first_item.item_price_id.clone(),
            quantity: first_item.quantity,
            billing_address_line1: item.router_data.get_optional_billing_line1(),
            billing_address_city: item.router_data.get_optional_billing_city(),
            billing_address_state: item.router_data.get_optional_billing_state(),
            billing_address_zip: item.router_data.get_optional_billing_zip(),
            billing_address_country: item.router_data.get_optional_billing_country(),
            auto_collection: req.auto_collection.clone().into(),
        })
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ChargebeeSubscriptionCreateResponse {
    pub subscription: ChargebeeSubscriptionDetails,
    pub invoice: Option<ChargebeeInvoiceData>,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ChargebeeSubscriptionDetails {
    pub id: SubscriptionId,
    pub status: ChargebeeSubscriptionStatus,
    pub customer_id: CustomerId,
    pub currency_code: enums::Currency,
    pub total_dues: Option<MinorUnit>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub next_billing_at: Option<PrimitiveDateTime>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub created_at: Option<PrimitiveDateTime>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub pause_date: Option<PrimitiveDateTime>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    cancelled_at: Option<PrimitiveDateTime>,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeSubscriptionStatus {
    Future,
    #[serde(rename = "in_trial")]
    InTrial,
    Active,
    #[serde(rename = "non_renewing")]
    NonRenewing,
    Paused,
    Cancelled,
    Transferred,
}

impl From<ChargebeeSubscriptionStatus> for SubscriptionStatus {
    fn from(status: ChargebeeSubscriptionStatus) -> Self {
        match status {
            ChargebeeSubscriptionStatus::Future => Self::Pending,
            ChargebeeSubscriptionStatus::InTrial => Self::Trial,
            ChargebeeSubscriptionStatus::Active => Self::Active,
            ChargebeeSubscriptionStatus::NonRenewing => Self::Onetime,
            ChargebeeSubscriptionStatus::Paused => Self::Paused,
            ChargebeeSubscriptionStatus::Cancelled => Self::Cancelled,
            ChargebeeSubscriptionStatus::Transferred => Self::Cancelled,
        }
    }
}

convert_connector_response_to_domain_response!(
    ChargebeeSubscriptionCreateResponse,
    SubscriptionCreateResponse,
    |item: ResponseRouterData<_, ChargebeeSubscriptionCreateResponse, _, _>| {
        let subscription = &item.response.subscription;
        Ok(Self {
            response: Ok(SubscriptionCreateResponse {
                subscription_id: subscription.id.clone(),
                status: subscription.status.clone().into(),
                customer_id: subscription.customer_id.clone(),
                currency_code: subscription.currency_code,
                total_amount: subscription.total_dues.unwrap_or(MinorUnit::new(0)),
                next_billing_at: subscription.next_billing_at,
                created_at: subscription.created_at,
                invoice_details: item.response.invoice.map(SubscriptionInvoiceData::from),
            }),
            ..item.data
        })
    }
);

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

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

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Serialize, PartialEq)]
pub struct ChargebeePaymentsRequest {
    amount: MinorUnit,
    card: ChargebeeCard,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct ChargebeeCard {
    number: cards::CardNumber,
    expiry_month: Secret<String>,
    expiry_year: Secret<String>,
    cvc: Secret<String>,
    complete: bool,
}

impl TryFrom<&ChargebeeRouterData<&PaymentsAuthorizeRouterData>> for ChargebeePaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &ChargebeeRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Card(req_card) => {
                let card = ChargebeeCard {
                    number: req_card.card_number,
                    expiry_month: req_card.card_exp_month,
                    expiry_year: req_card.card_exp_year,
                    cvc: req_card.card_cvc,
                    complete: item.router_data.request.is_auto_capture()?,
                };
                Ok(Self {
                    amount: item.amount,
                    card,
                })
            }
            _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
        }
    }
}

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

#[derive(Debug, Serialize, Deserialize)]
pub struct ChargebeeMetadata {
    pub(super) site: Secret<String>,
}

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

impl TryFrom<&ConnectorAuthType> for ChargebeeAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                full_access_key_v1: api_key.clone(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}
// PaymentsResponse
//TODO: Append the remaining status flags
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ChargebeePaymentStatus {
    Succeeded,
    Failed,
    #[default]
    Processing,
}

impl From<ChargebeePaymentStatus> for common_enums::AttemptStatus {
    fn from(item: ChargebeePaymentStatus) -> Self {
        match item {
            ChargebeePaymentStatus::Succeeded => Self::Charged,
            ChargebeePaymentStatus::Failed => Self::Failure,
            ChargebeePaymentStatus::Processing => Self::Authorizing,
        }
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ChargebeePaymentsResponse {
    status: ChargebeePaymentStatus,
    id: String,
}

convert_connector_response_to_domain_response!(
    ChargebeePaymentsResponse,
    PaymentsResponseData,
    |item: ResponseRouterData<_, ChargebeePaymentsResponse, _, _>| {
        Ok(Self {
            status: common_enums::AttemptStatus::from(item.response.status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
);

//TODO: Fill the struct with respective fields
// REFUND :
// Type definition for RefundRequest
#[derive(Default, Debug, Serialize)]
pub struct ChargebeeRefundRequest {
    pub amount: MinorUnit,
}

impl<F> TryFrom<&ChargebeeRouterData<&RefundsRouterData<F>>> for ChargebeeRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &ChargebeeRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: item.amount.to_owned(),
        })
    }
}

// Type definition for Refund Response

#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
pub enum RefundStatus {
    Succeeded,
    Failed,
    #[default]
    Processing,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Succeeded => Self::Success,
            RefundStatus::Failed => Self::Failure,
            RefundStatus::Processing => Self::Pending,
            //TODO: Review mapping
        }
    }
}

//TODO: Fill the struct with respective fields
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct RefundResponse {
    id: String,
    status: RefundStatus,
}

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

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

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct ChargebeeErrorResponse {
    pub api_error_code: String,
    pub message: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChargebeeWebhookBody {
    pub content: ChargebeeWebhookContent,
    pub event_type: ChargebeeEventType,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChargebeeInvoiceBody {
    pub content: ChargebeeInvoiceContent,
    pub event_type: ChargebeeEventType,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChargebeeInvoiceContent {
    pub invoice: ChargebeeInvoiceData,
    pub subscription: Option<ChargebeeSubscriptionData>,
}

#[derive(Serialize, Deserialize, Debug)]

pub struct ChargebeeWebhookContent {
    pub transaction: ChargebeeTransactionData,
    pub invoice: ChargebeeInvoiceData,
    pub customer: Option<ChargebeeCustomer>,
    pub subscription: Option<ChargebeeSubscriptionData>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChargebeeSubscriptionData {
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub current_term_start: Option<PrimitiveDateTime>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub next_billing_at: Option<PrimitiveDateTime>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeEventType {
    PaymentSucceeded,
    PaymentFailed,
    InvoiceDeleted,
    InvoiceGenerated,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ChargebeeInvoiceData {
    // invoice id
    pub id: InvoiceId,
    pub total: MinorUnit,
    pub currency_code: enums::Currency,
    pub status: Option<ChargebeeInvoiceStatus>,
    pub billing_address: Option<ChargebeeInvoiceBillingAddress>,
    pub linked_payments: Option<Vec<ChargebeeInvoicePayments>>,
    pub customer_id: CustomerId,
    pub subscription_id: SubscriptionId,
    pub first_invoice: Option<bool>,
}

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

#[derive(Serialize, Deserialize, Debug)]
pub struct ChargebeeTransactionData {
    id_at_gateway: Option<String>,
    status: ChargebeeTranasactionStatus,
    error_code: Option<String>,
    error_text: Option<String>,
    gateway_account_id: String,
    currency_code: enums::Currency,
    amount: MinorUnit,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    date: Option<PrimitiveDateTime>,
    payment_method: ChargebeeTransactionPaymentMethod,
    payment_method_details: String,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeTransactionPaymentMethod {
    Card,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChargebeePaymentMethodDetails {
    card: ChargebeeCardDetails,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChargebeeCardDetails {
    funding_type: ChargebeeFundingType,
    brand: common_enums::CardNetwork,
    iin: String,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeFundingType {
    Credit,
    Debit,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeTranasactionStatus {
    // Waiting for response from the payment gateway.
    InProgress,
    // The transaction is successful.
    Success,
    // Transaction failed.
    Failure,
    // No response received while trying to charge the card.
    Timeout,
    // Indicates that a successful payment transaction has failed now due to a late failure notification from the payment gateway,
    // typically caused by issues like insufficient funds or a closed bank account.
    LateFailure,
    // Connection with Gateway got terminated abruptly. So, status of this transaction needs to be resolved manually
    NeedsAttention,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChargebeeCustomer {
    pub payment_method: ChargebeePaymentMethod,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ChargebeeInvoiceBillingAddress {
    pub line1: Option<Secret<String>>,
    pub line2: Option<Secret<String>>,
    pub line3: Option<Secret<String>>,
    pub state: Option<Secret<String>>,
    pub country: Option<enums::CountryAlpha2>,
    pub zip: Option<Secret<String>>,
    pub city: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChargebeePaymentMethod {
    pub reference_id: String,
    pub gateway: ChargebeeGateway,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeGateway {
    Stripe,
    Braintree,
}

impl ChargebeeWebhookBody {
    pub fn get_webhook_object_from_body(body: &[u8]) -> CustomResult<Self, errors::ConnectorError> {
        let webhook_body = body
            .parse_struct::<Self>("ChargebeeWebhookBody")
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        Ok(webhook_body)
    }
}

impl ChargebeeInvoiceBody {
    pub fn get_invoice_webhook_data_from_body(
        body: &[u8],
    ) -> CustomResult<Self, errors::ConnectorError> {
        let webhook_body = body
            .parse_struct::<Self>("ChargebeeInvoiceBody")
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        Ok(webhook_body)
    }
}
// Structure to extract MIT payment data from invoice_generated webhook
#[derive(Debug, Clone)]
pub struct ChargebeeMitPaymentData {
    pub invoice_id: InvoiceId,
    pub amount_due: MinorUnit,
    pub currency_code: enums::Currency,
    pub status: Option<ChargebeeInvoiceStatus>,
    pub customer_id: CustomerId,
    pub subscription_id: SubscriptionId,
    pub first_invoice: bool,
}

impl TryFrom<ChargebeeInvoiceBody> for ChargebeeMitPaymentData {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(webhook_body: ChargebeeInvoiceBody) -> Result<Self, Self::Error> {
        let invoice = webhook_body.content.invoice;

        Ok(Self {
            invoice_id: invoice.id,
            amount_due: invoice.total,
            currency_code: invoice.currency_code,
            status: invoice.status,
            customer_id: invoice.customer_id,
            subscription_id: invoice.subscription_id,
            first_invoice: invoice.first_invoice.unwrap_or(false),
        })
    }
}
pub struct ChargebeeMandateDetails {
    pub customer_id: String,
    pub mandate_id: String,
}

impl ChargebeeCustomer {
    // the logic to find connector customer id & mandate id is different for different gateways, reference : https://apidocs.chargebee.com/docs/api/customers?prod_cat_ver=2#customer_payment_method_reference_id .
    pub fn find_connector_ids(&self) -> Result<ChargebeeMandateDetails, errors::ConnectorError> {
        match self.payment_method.gateway {
            ChargebeeGateway::Stripe | ChargebeeGateway::Braintree => {
                let mut parts = self.payment_method.reference_id.split('/');
                let customer_id = parts
                    .next()
                    .ok_or(errors::ConnectorError::WebhookBodyDecodingFailed)?
                    .to_string();
                let mandate_id = parts
                    .next_back()
                    .ok_or(errors::ConnectorError::WebhookBodyDecodingFailed)?
                    .to_string();
                Ok(ChargebeeMandateDetails {
                    customer_id,
                    mandate_id,
                })
            }
        }
    }
}

#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
impl TryFrom<ChargebeeWebhookBody> for revenue_recovery::RevenueRecoveryAttemptData {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: ChargebeeWebhookBody) -> Result<Self, Self::Error> {
        let amount = item.content.transaction.amount;
        let currency = item.content.transaction.currency_code.to_owned();
        let merchant_reference_id = common_utils::id_type::PaymentReferenceId::from_str(
            item.content.invoice.id.get_string_repr(),
        )
        .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        let connector_transaction_id = item
            .content
            .transaction
            .id_at_gateway
            .map(common_utils::types::ConnectorTransactionId::TxnId);
        let error_code = item.content.transaction.error_code.clone();
        let error_message = item.content.transaction.error_text.clone();
        let connector_mandate_details = item
            .content
            .customer
            .as_ref()
            .map(|customer| customer.find_connector_ids())
            .transpose()?
            .ok_or(errors::ConnectorError::MissingRequiredField {
                field_name: "connector_mandate_details",
            })?;
        let connector_account_reference_id = item.content.transaction.gateway_account_id.clone();
        let transaction_created_at = item.content.transaction.date;
        let status = enums::AttemptStatus::from(item.content.transaction.status);
        let payment_method_type =
            enums::PaymentMethod::from(item.content.transaction.payment_method);
        let payment_method_details: ChargebeePaymentMethodDetails =
            serde_json::from_str(&item.content.transaction.payment_method_details)
                .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        let payment_method_sub_type =
            enums::PaymentMethodType::from(payment_method_details.card.funding_type);
        // Chargebee retry count will always be less than u16 always. Chargebee can have maximum 12 retry attempts
        #[allow(clippy::as_conversions)]
        let retry_count = item
            .content
            .invoice
            .linked_payments
            .map(|linked_payments| linked_payments.len() as u16);
        let invoice_next_billing_time = item
            .content
            .subscription
            .as_ref()
            .and_then(|subscription| subscription.next_billing_at);
        let invoice_billing_started_at_time = item
            .content
            .subscription
            .as_ref()
            .and_then(|subscription| subscription.current_term_start);
        Ok(Self {
            amount,
            currency,
            merchant_reference_id,
            connector_transaction_id,
            error_code,
            error_message,
            processor_payment_method_token: connector_mandate_details.mandate_id,
            connector_customer_id: connector_mandate_details.customer_id,
            connector_account_reference_id,
            transaction_created_at,
            status,
            payment_method_type,
            payment_method_sub_type,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            retry_count,
            invoice_next_billing_time,
            invoice_billing_started_at_time,
            // This field is none because it is specific to stripebilling.
            charge_id: None,
            // Need to populate these card info field
            card_info: api_models::payments::AdditionalCardInfo {
                card_network: Some(payment_method_details.card.brand),
                card_isin: Some(payment_method_details.card.iin),
                card_issuer: None,
                card_type: None,
                card_issuing_country: None,
                bank_code: None,
                last4: None,
                card_extended_bin: None,
                card_exp_month: None,
                card_exp_year: None,
                card_holder_name: None,
                payment_checks: None,
                authentication_data: None,
                is_regulated: None,
                signature_network: None,
            },
        })
    }
}

impl From<ChargebeeTranasactionStatus> for enums::AttemptStatus {
    fn from(status: ChargebeeTranasactionStatus) -> Self {
        match status {
            ChargebeeTranasactionStatus::InProgress
            | ChargebeeTranasactionStatus::NeedsAttention => Self::Pending,
            ChargebeeTranasactionStatus::Success => Self::Charged,
            ChargebeeTranasactionStatus::Failure
            | ChargebeeTranasactionStatus::Timeout
            | ChargebeeTranasactionStatus::LateFailure => Self::Failure,
        }
    }
}

impl From<ChargebeeTransactionPaymentMethod> for enums::PaymentMethod {
    fn from(payment_method: ChargebeeTransactionPaymentMethod) -> Self {
        match payment_method {
            ChargebeeTransactionPaymentMethod::Card => Self::Card,
        }
    }
}

impl From<ChargebeeFundingType> for enums::PaymentMethodType {
    fn from(funding_type: ChargebeeFundingType) -> Self {
        match funding_type {
            ChargebeeFundingType::Credit => Self::Credit,
            ChargebeeFundingType::Debit => Self::Debit,
        }
    }
}
#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
impl From<ChargebeeEventType> for api_models::webhooks::IncomingWebhookEvent {
    fn from(event: ChargebeeEventType) -> Self {
        match event {
            ChargebeeEventType::PaymentSucceeded => Self::RecoveryPaymentSuccess,
            ChargebeeEventType::PaymentFailed => Self::RecoveryPaymentFailure,
            ChargebeeEventType::InvoiceDeleted => Self::RecoveryInvoiceCancel,
            ChargebeeEventType::InvoiceGenerated => Self::InvoiceGenerated,
        }
    }
}

#[cfg(feature = "v1")]
impl From<ChargebeeEventType> for api_models::webhooks::IncomingWebhookEvent {
    fn from(event: ChargebeeEventType) -> Self {
        match event {
            ChargebeeEventType::PaymentSucceeded => Self::PaymentIntentSuccess,
            ChargebeeEventType::PaymentFailed => Self::PaymentIntentFailure,
            ChargebeeEventType::InvoiceDeleted => Self::EventNotSupported,
            ChargebeeEventType::InvoiceGenerated => Self::InvoiceGenerated,
        }
    }
}

#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
impl TryFrom<ChargebeeInvoiceBody> for revenue_recovery::RevenueRecoveryInvoiceData {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: ChargebeeInvoiceBody) -> Result<Self, Self::Error> {
        let merchant_reference_id = common_utils::id_type::PaymentReferenceId::from_str(
            item.content.invoice.id.get_string_repr(),
        )
        .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

        // The retry count will never exceed u16 limit in a billing connector. It can have maximum of 12 in case of charge bee so its ok to suppress this
        #[allow(clippy::as_conversions)]
        let retry_count = item
            .content
            .invoice
            .linked_payments
            .as_ref()
            .map(|linked_payments| linked_payments.len() as u16);
        let invoice_next_billing_time = item
            .content
            .subscription
            .as_ref()
            .and_then(|subscription| subscription.next_billing_at);
        let billing_started_at = item
            .content
            .subscription
            .as_ref()
            .and_then(|subscription| subscription.current_term_start);
        Ok(Self {
            amount: item.content.invoice.total,
            currency: item.content.invoice.currency_code,
            merchant_reference_id,
            billing_address: Some(api_models::payments::Address::from(item.content.invoice)),
            retry_count,
            next_billing_at: invoice_next_billing_time,
            billing_started_at,
            metadata: None,
            // TODO! This field should be handled for billing connnector integrations
            enable_partial_authorization: None,
        })
    }
}

impl From<ChargebeeInvoiceData> for api_models::payments::Address {
    fn from(item: ChargebeeInvoiceData) -> Self {
        Self {
            address: item
                .billing_address
                .map(api_models::payments::AddressDetails::from),
            phone: None,
            email: None,
        }
    }
}

impl From<ChargebeeInvoiceBillingAddress> for api_models::payments::AddressDetails {
    fn from(item: ChargebeeInvoiceBillingAddress) -> Self {
        Self {
            city: item.city,
            country: item.country,
            state: item.state,
            zip: item.zip,
            line1: item.line1,
            line2: item.line2,
            line3: item.line3,
            first_name: None,
            last_name: None,
            origin_zip: None,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct ChargebeeRecordPaymentRequest {
    #[serde(rename = "transaction[amount]")]
    pub amount: MinorUnit,
    #[serde(rename = "transaction[payment_method]")]
    pub payment_method: ChargebeeRecordPaymentMethod,
    #[serde(rename = "transaction[id_at_gateway]")]
    pub connector_payment_id: Option<String>,
    #[serde(rename = "transaction[status]")]
    pub status: ChargebeeRecordStatus,
}

#[derive(Debug, Serialize, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeRecordPaymentMethod {
    Other,
}

#[derive(Debug, Serialize, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeRecordStatus {
    Success,
    Failure,
}

impl TryFrom<&ChargebeeRouterData<&InvoiceRecordBackRouterData>> for ChargebeeRecordPaymentRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &ChargebeeRouterData<&InvoiceRecordBackRouterData>,
    ) -> Result<Self, Self::Error> {
        let req = &item.router_data.request;
        Ok(Self {
            amount: req.amount,
            payment_method: ChargebeeRecordPaymentMethod::Other,
            connector_payment_id: req
                .connector_transaction_id
                .as_ref()
                .map(|connector_payment_id| connector_payment_id.get_id().to_string()),
            status: ChargebeeRecordStatus::try_from(req.attempt_status)?,
        })
    }
}

impl TryFrom<enums::AttemptStatus> for ChargebeeRecordStatus {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(status: enums::AttemptStatus) -> Result<Self, Self::Error> {
        match status {
            enums::AttemptStatus::Charged
            | enums::AttemptStatus::PartialCharged
            | enums::AttemptStatus::PartialChargedAndChargeable => Ok(Self::Success),
            enums::AttemptStatus::Failure
            | enums::AttemptStatus::CaptureFailed
            | enums::AttemptStatus::RouterDeclined => Ok(Self::Failure),
            enums::AttemptStatus::AuthenticationFailed
            | enums::AttemptStatus::Started
            | enums::AttemptStatus::AuthenticationPending
            | enums::AttemptStatus::AuthenticationSuccessful
            | enums::AttemptStatus::Authorized
            | enums::AttemptStatus::PartiallyAuthorized
            | enums::AttemptStatus::AuthorizationFailed
            | enums::AttemptStatus::Authorizing
            | enums::AttemptStatus::CodInitiated
            | enums::AttemptStatus::Voided
            | enums::AttemptStatus::VoidedPostCharge
            | enums::AttemptStatus::VoidInitiated
            | enums::AttemptStatus::CaptureInitiated
            | enums::AttemptStatus::VoidFailed
            | enums::AttemptStatus::AutoRefunded
            | enums::AttemptStatus::Unresolved
            | enums::AttemptStatus::Pending
            | enums::AttemptStatus::PaymentMethodAwaited
            | enums::AttemptStatus::ConfirmationAwaited
            | enums::AttemptStatus::DeviceDataCollectionPending
            | enums::AttemptStatus::IntegrityFailure
            | enums::AttemptStatus::Expired => Err(errors::ConnectorError::NotSupported {
                message: "Record back flow is only supported for terminal status".to_string(),
                connector: "chargebee",
            }
            .into()),
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ChargebeeRecordbackResponse {
    pub invoice: ChargebeeRecordbackInvoice,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ChargebeeRecordbackInvoice {
    pub id: common_utils::id_type::PaymentReferenceId,
}

convert_connector_response_to_domain_response!(
    ChargebeeRecordbackResponse,
    InvoiceRecordBackResponse,
    |item: ResponseRouterData<_, ChargebeeRecordbackResponse, _, _>| {
        let merchant_reference_id = item.response.invoice.id;
        Ok(Self {
            response: Ok(InvoiceRecordBackResponse {
                merchant_reference_id,
            }),
            ..item.data
        })
    }
);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChargebeeListPlansResponse {
    pub list: Vec<ChargebeeItemList>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChargebeeItemList {
    pub item: ChargebeeItem,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChargebeeItem {
    pub id: String,
    pub name: String,
    #[serde(rename = "type")]
    pub plan_type: String,
    pub is_giftable: bool,
    pub enabled_for_checkout: bool,
    pub enabled_in_portal: bool,
    pub metered: bool,
    pub deleted: bool,
    pub description: Option<String>,
}

convert_connector_response_to_domain_response!(
    SubscriptionEstimateResponse,
    GetSubscriptionEstimateResponse,
    |item: ResponseRouterData<_, SubscriptionEstimateResponse, _, _>| {
        let estimate = item.response.estimate;
        Ok(Self {
            response: Ok(GetSubscriptionEstimateResponse {
                sub_total: estimate.invoice_estimate.sub_total,
                total: estimate.invoice_estimate.total,
                amount_paid: Some(estimate.invoice_estimate.amount_paid),
                amount_due: Some(estimate.invoice_estimate.amount_due),
                currency: estimate.subscription_estimate.currency_code,
                next_billing_at: estimate.subscription_estimate.next_billing_at,
                credits_applied: Some(estimate.invoice_estimate.credits_applied),
                customer_id: Some(estimate.invoice_estimate.customer_id),
                line_items: estimate
                    .invoice_estimate
                    .line_items
                    .into_iter()
                    .map(|line_item| SubscriptionLineItem {
                        item_id: line_item.entity_id,
                        item_type: line_item.entity_type,
                        description: line_item.description,
                        amount: line_item.amount,
                        currency: estimate.invoice_estimate.currency_code,
                        unit_amount: Some(line_item.unit_amount),
                        quantity: line_item.quantity,
                        pricing_model: Some(line_item.pricing_model),
                    })
                    .collect(),
            }),
            ..item.data
        })
    }
);

convert_connector_response_to_domain_response!(
    ChargebeeListPlansResponse,
    GetSubscriptionPlansResponse,
    |item: ResponseRouterData<_, ChargebeeListPlansResponse, _, _>| {
        let plans = item
            .response
            .list
            .into_iter()
            .map(|plan| subscriptions::SubscriptionPlans {
                subscription_provider_plan_id: plan.item.id,
                name: plan.item.name,
                description: plan.item.description,
            })
            .collect();
        Ok(Self {
            response: Ok(GetSubscriptionPlansResponse { list: plans }),
            ..item.data
        })
    }
);

#[derive(Debug, Serialize)]
pub struct ChargebeeCustomerCreateRequest {
    #[serde(rename = "id")]
    pub customer_id: CustomerId,
    #[serde(rename = "first_name")]
    pub name: Option<Secret<String>>,
    pub email: Option<Email>,
    #[serde(rename = "billing_address[first_name]")]
    pub billing_address_first_name: Option<Secret<String>>,
    #[serde(rename = "billing_address[last_name]")]
    pub billing_address_last_name: Option<Secret<String>>,
    #[serde(rename = "billing_address[line1]")]
    pub billing_address_line1: Option<Secret<String>>,
    #[serde(rename = "billing_address[city]")]
    pub billing_address_city: Option<String>,
    #[serde(rename = "billing_address[state]")]
    pub billing_address_state: Option<Secret<String>>,
    #[serde(rename = "billing_address[zip]")]
    pub billing_address_zip: Option<Secret<String>>,
    #[serde(rename = "billing_address[country]")]
    pub billing_address_country: Option<String>,
}

impl TryFrom<&ChargebeeRouterData<&hyperswitch_domain_models::types::ConnectorCustomerRouterData>>
    for ChargebeeCustomerCreateRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: &ChargebeeRouterData<&hyperswitch_domain_models::types::ConnectorCustomerRouterData>,
    ) -> Result<Self, Self::Error> {
        let req = &item.router_data.request;

        Ok(Self {
            customer_id: req
                .customer_id
                .as_ref()
                .ok_or_else(|| errors::ConnectorError::MissingRequiredField {
                    field_name: "customer_id",
                })?
                .clone(),
            name: req.name.clone(),
            email: req.email.clone(),
            billing_address_first_name: req
                .billing_address
                .as_ref()
                .and_then(|address| address.first_name.clone()),
            billing_address_last_name: req
                .billing_address
                .as_ref()
                .and_then(|address| address.last_name.clone()),
            billing_address_line1: req
                .billing_address
                .as_ref()
                .and_then(|addr| addr.line1.clone()),
            billing_address_city: req
                .billing_address
                .as_ref()
                .and_then(|addr| addr.city.clone()),
            billing_address_country: req
                .billing_address
                .as_ref()
                .and_then(|addr| addr.country.map(|country| country.to_string())),
            billing_address_state: req
                .billing_address
                .as_ref()
                .and_then(|addr| addr.state.clone()),
            billing_address_zip: req
                .billing_address
                .as_ref()
                .and_then(|addr| addr.zip.clone()),
        })
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ChargebeeCustomerCreateResponse {
    pub customer: ChargebeeCustomerDetails,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ChargebeeCustomerDetails {
    pub id: String,
    #[serde(rename = "first_name")]
    pub name: Option<Secret<String>>,
    pub email: Option<Email>,
    pub billing_address: Option<api_models::payments::AddressDetails>,
}

convert_connector_response_to_domain_response!(
    ChargebeeCustomerCreateResponse,
    PaymentsResponseData,
    |item: ResponseRouterData<_, ChargebeeCustomerCreateResponse, _, _>| {
        let customer_response = &item.response.customer;

        Ok(Self {
            response: Ok(PaymentsResponseData::ConnectorCustomerResponse(
                ConnectorCustomerResponseData::new(
                    customer_response.id.clone(),
                    customer_response
                        .name
                        .as_ref()
                        .map(|name| name.clone().expose()),
                    customer_response
                        .email
                        .as_ref()
                        .map(|email| email.clone().expose().expose()),
                    customer_response.billing_address.clone(),
                ),
            )),
            ..item.data
        })
    }
);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChargebeeSubscriptionEstimateRequest {
    #[serde(rename = "subscription_items[item_price_id][0]")]
    pub price_id: String,
}

impl TryFrom<&GetSubscriptionEstimateRouterData> for ChargebeeSubscriptionEstimateRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &GetSubscriptionEstimateRouterData) -> Result<Self, Self::Error> {
        let price_id = item.request.price_id.to_owned();
        Ok(Self { price_id })
    }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChargebeeGetPlanPricesResponse {
    pub list: Vec<ChargebeeGetPlanPriceList>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChargebeeGetPlanPriceList {
    pub item_price: ChargebeePlanPriceItem,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChargebeePlanPriceItem {
    pub id: String,
    pub name: String,
    pub currency_code: common_enums::Currency,
    pub free_quantity: i64,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub created_at: Option<PrimitiveDateTime>,
    pub deleted: bool,
    pub item_id: Option<String>,
    pub period: i64,
    pub period_unit: ChargebeePeriodUnit,
    pub trial_period: Option<i64>,
    pub trial_period_unit: Option<ChargebeeTrialPeriodUnit>,
    pub price: MinorUnit,
    pub pricing_model: ChargebeePricingModel,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeePricingModel {
    FlatFee,
    PerUnit,
    Tiered,
    Volume,
    Stairstep,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeePeriodUnit {
    Day,
    Week,
    Month,
    Year,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeTrialPeriodUnit {
    Day,
    Month,
}

convert_connector_response_to_domain_response!(
    ChargebeeGetPlanPricesResponse,
    GetSubscriptionPlanPricesResponse,
    |item: ResponseRouterData<_, ChargebeeGetPlanPricesResponse, _, _>| {
        let plan_prices = item
            .response
            .list
            .into_iter()
            .map(|prices| subscriptions::SubscriptionPlanPrices {
                price_id: prices.item_price.id,
                plan_id: prices.item_price.item_id,
                amount: prices.item_price.price,
                currency: prices.item_price.currency_code,
                interval: match prices.item_price.period_unit {
                    ChargebeePeriodUnit::Day => subscriptions::PeriodUnit::Day,
                    ChargebeePeriodUnit::Week => subscriptions::PeriodUnit::Week,
                    ChargebeePeriodUnit::Month => subscriptions::PeriodUnit::Month,
                    ChargebeePeriodUnit::Year => subscriptions::PeriodUnit::Year,
                },
                interval_count: prices.item_price.period,
                trial_period: prices.item_price.trial_period,
                trial_period_unit: match prices.item_price.trial_period_unit {
                    Some(ChargebeeTrialPeriodUnit::Day) => Some(subscriptions::PeriodUnit::Day),
                    Some(ChargebeeTrialPeriodUnit::Month) => Some(subscriptions::PeriodUnit::Month),
                    None => None,
                },
            })
            .collect();
        Ok(Self {
            response: Ok(GetSubscriptionPlanPricesResponse { list: plan_prices }),
            ..item.data
        })
    }
);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscriptionEstimateResponse {
    pub estimate: ChargebeeEstimate,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChargebeeEstimate {
    pub created_at: i64,
    /// type of the object will be `estimate`
    pub object: String,
    pub subscription_estimate: SubscriptionEstimate,
    pub invoice_estimate: InvoiceEstimate,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscriptionEstimate {
    pub status: String,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub next_billing_at: Option<PrimitiveDateTime>,
    /// type of the object will be `subscription_estimate`
    pub object: String,
    pub currency_code: enums::Currency,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvoiceEstimate {
    pub recurring: bool,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub date: Option<PrimitiveDateTime>,
    pub price_type: String,
    pub sub_total: MinorUnit,
    pub total: MinorUnit,
    pub credits_applied: MinorUnit,
    pub amount_paid: MinorUnit,
    pub amount_due: MinorUnit,
    /// type of the object will be `invoice_estimate`
    pub object: String,
    pub customer_id: CustomerId,
    pub line_items: Vec<LineItem>,
    pub currency_code: enums::Currency,
    pub round_off_amount: MinorUnit,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LineItem {
    pub id: String,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub date_from: Option<PrimitiveDateTime>,
    #[serde(default, with = "common_utils::custom_serde::timestamp::option")]
    pub date_to: Option<PrimitiveDateTime>,
    pub unit_amount: MinorUnit,
    pub quantity: i64,
    pub amount: MinorUnit,
    pub pricing_model: String,
    pub is_taxed: bool,
    pub tax_amount: MinorUnit,
    /// type of the object will be `line_item`
    pub object: String,
    pub customer_id: String,
    pub description: String,
    pub entity_type: String,
    pub entity_id: String,
    pub discount_amount: MinorUnit,
    pub item_level_discount_amount: MinorUnit,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ChargebeeInvoiceStatus {
    Paid,
    Posted,
    PaymentDue,
    NotPaid,
    Voided,
    #[serde(other)]
    Pending,
}

impl From<ChargebeeInvoiceData> for SubscriptionInvoiceData {
    fn from(item: ChargebeeInvoiceData) -> Self {
        Self {
            billing_address: Some(api_models::payments::Address::from(item.clone())),
            id: item.id,
            total: item.total,
            currency_code: item.currency_code,
            status: item.status.map(connector_enums::InvoiceStatus::from),
        }
    }
}

impl From<ChargebeeInvoiceStatus> for connector_enums::InvoiceStatus {
    fn from(status: ChargebeeInvoiceStatus) -> Self {
        match status {
            ChargebeeInvoiceStatus::Paid => Self::InvoicePaid,
            ChargebeeInvoiceStatus::Posted => Self::PaymentPendingTimeout,
            ChargebeeInvoiceStatus::PaymentDue => Self::PaymentPending,
            ChargebeeInvoiceStatus::NotPaid => Self::PaymentFailed,
            ChargebeeInvoiceStatus::Voided => Self::Voided,
            ChargebeeInvoiceStatus::Pending => Self::InvoiceCreated,
        }
    }
}

// Pause Subscription structures
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ChargebeePauseSubscriptionRequest {
    #[serde(rename = "pause_option")]
    pub pause_option: Option<api::PauseOption>,
    #[serde(rename = "resume_date", skip_serializing_if = "Option::is_none")]
    pub resume_date: Option<i64>,
}

impl From<&SubscriptionPauseRouterData> for ChargebeePauseSubscriptionRequest {
    fn from(req: &SubscriptionPauseRouterData) -> Self {
        Self {
            pause_option: req.request.pause_option.clone(),
            resume_date: req
                .request
                .pause_date
                .map(|date| date.assume_utc().unix_timestamp()),
        }
    }
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ChargebeePauseSubscriptionResponse {
    pub subscription: ChargebeeSubscriptionDetails,
}

// Resume Subscription structures
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ChargebeeResumeSubscriptionRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub resume_option: Option<api::ResumeOption>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub resume_date: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub charges_handling: Option<api::ChargesHandling>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unpaid_invoices_handling: Option<api::UnpaidInvoicesHandling>,
}

impl From<&SubscriptionResumeRouterData> for ChargebeeResumeSubscriptionRequest {
    fn from(req: &SubscriptionResumeRouterData) -> Self {
        Self {
            resume_option: req.request.resume_option.clone(),
            resume_date: req
                .request
                .resume_date
                .map(|date| date.assume_utc().unix_timestamp()),
            charges_handling: req.request.charges_handling.clone(),
            unpaid_invoices_handling: req.request.unpaid_invoices_handling.clone(),
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ChargebeeResumeSubscriptionResponse {
    pub subscription: ChargebeeSubscriptionDetails,
}

// Cancel Subscription structures
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ChargebeeCancelSubscriptionRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cancel_at: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cancel_option: Option<api::CancelOption>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub unbilled_charges_option: Option<api::UnbilledChargesOption>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub credit_option_for_current_term_charges: Option<api::CreditOption>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub account_receivables_handling: Option<api::AccountReceivablesHandling>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub refundable_credits_handling: Option<api::RefundableCreditsHandling>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cancel_reason_code: Option<String>,
}

impl From<&SubscriptionCancelRouterData> for ChargebeeCancelSubscriptionRequest {
    fn from(req: &SubscriptionCancelRouterData) -> Self {
        Self {
            cancel_at: req
                .request
                .cancel_date
                .map(|date| date.assume_utc().unix_timestamp()),
            cancel_option: req.request.cancel_option.clone(),
            unbilled_charges_option: req.request.unbilled_charges_option.clone(),
            credit_option_for_current_term_charges: req
                .request
                .credit_option_for_current_term_charges
                .clone(),
            account_receivables_handling: req.request.account_receivables_handling.clone(),
            refundable_credits_handling: req.request.refundable_credits_handling.clone(),
            cancel_reason_code: req.request.cancel_reason_code.clone(),
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ChargebeeCancelSubscriptionResponse {
    pub subscription: ChargebeeSubscriptionDetails,
}

convert_connector_response_to_domain_response!(
    ChargebeePauseSubscriptionResponse,
    SubscriptionPauseResponse,
    |item: ResponseRouterData<_, ChargebeePauseSubscriptionResponse, _, _>| {
        let subscription = item.response.subscription;
        Ok(Self {
            response: Ok(SubscriptionPauseResponse {
                subscription_id: subscription.id.clone(),
                status: subscription.status.clone().into(),
                paused_at: subscription.pause_date,
            }),
            ..item.data
        })
    }
);

convert_connector_response_to_domain_response!(
    ChargebeeResumeSubscriptionResponse,
    SubscriptionResumeResponse,
    |item: ResponseRouterData<_, ChargebeeResumeSubscriptionResponse, _, _>| {
        let subscription = item.response.subscription;
        Ok(Self {
            response: Ok(SubscriptionResumeResponse {
                subscription_id: subscription.id.clone(),
                status: subscription.status.clone().into(),
                next_billing_at: subscription.next_billing_at,
            }),
            ..item.data
        })
    }
);

convert_connector_response_to_domain_response!(
    ChargebeeCancelSubscriptionResponse,
    SubscriptionCancelResponse,
    |item: ResponseRouterData<_, ChargebeeCancelSubscriptionResponse, _, _>| {
        let subscription = item.response.subscription;
        Ok(Self {
            response: Ok(SubscriptionCancelResponse {
                subscription_id: subscription.id.clone(),
                status: subscription.status.clone().into(),
                cancelled_at: subscription.cancelled_at,
            }),
            ..item.data
        })
    }
);


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

use std::collections::HashMap;

use common_utils::{pii, request::Method, types::StringMajorUnit};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{BankDebitData, PaymentMethodData},
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{Authorize, PreProcessing},
    router_request_types::{PaymentsAuthorizeData, PaymentsPreProcessingData, ResponseId},
    router_response_types::{PaymentsResponseData, RedirectForm},
    types::{
        self, AccessTokenAuthenticationRouterData, PaymentsAuthorizeRouterData,
        PaymentsPreProcessingRouterData, PaymentsSyncRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use rand::distributions::DistString;
use serde::{Deserialize, Deserializer, Serialize};

use crate::{
    connectors::nordea::{
        requests::{
            AccessScope, AccountNumber, AccountType, CreditorAccount, CreditorBank, DebitorAccount,
            GrantType, NordeaOAuthExchangeRequest, NordeaOAuthRequest,
            NordeaPaymentsConfirmRequest, NordeaPaymentsRequest, NordeaRouterData, PaymentsUrgency,
        },
        responses::{
            NordeaErrorBody, NordeaFailures, NordeaOAuthExchangeResponse, NordeaPaymentStatus,
            NordeaPaymentsConfirmResponse, NordeaPaymentsInitiateResponse,
        },
    },
    types::{
        PaymentsPreprocessingResponseRouterData, PaymentsSyncResponseRouterData, ResponseRouterData,
    },
    utils::{self, get_unimplemented_payment_method_error_message, RouterData as _},
};

type Error = error_stack::Report<errors::ConnectorError>;

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

// Auth Struct
#[derive(Debug)]
pub struct NordeaAuthType {
    pub(super) client_id: Secret<String>,
    pub(super) client_secret: Secret<String>,
    /// PEM format private key for eIDAS signing
    /// Should be base64 encoded
    pub(super) eidas_private_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for NordeaAuthType {
    type Error = Error;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::SignatureKey {
                api_key,
                key1,
                api_secret,
            } => Ok(Self {
                client_id: key1.to_owned(),
                client_secret: api_key.to_owned(),
                eidas_private_key: api_secret.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct NordeaConnectorMetadataObject {
    /// Account number of the beneficiary (merchant)
    pub destination_account_number: Secret<String>,
    /// Account type (example: IBAN, BBAN_SE, BBAN_DK, BBAN_NO, BGNR, PGNR, GIRO_DK, BBAN_OTHER)
    pub account_type: String,
    /// Name of the beneficiary (merchant)
    pub merchant_name: Secret<String>,
}

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

impl TryFrom<&AccessTokenAuthenticationRouterData> for NordeaOAuthRequest {
    type Error = Error;
    fn try_from(item: &AccessTokenAuthenticationRouterData) -> Result<Self, Self::Error> {
        let country = item.get_billing_country()?;

        // Set refresh_token maximum expiry duration to 180 days (259200 / 60 = 180)
        // Minimum is 1 minute
        let duration = Some(259200);
        let maximum_transaction_history = Some(18);
        let redirect_uri = "https://hyperswitch.io".to_string();
        let scope = [
            AccessScope::AccountsBasic,
            AccessScope::AccountsDetails,
            AccessScope::AccountsBalances,
            AccessScope::AccountsTransactions,
            AccessScope::PaymentsMultiple,
        ]
        .to_vec();
        let state = rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 15);

        Ok(Self {
            country,
            duration,
            maximum_transaction_history,
            redirect_uri,
            scope,
            state: state.into(),
        })
    }
}

impl TryFrom<&types::RefreshTokenRouterData> for NordeaOAuthExchangeRequest {
    type Error = Error;
    fn try_from(item: &types::RefreshTokenRouterData) -> Result<Self, Self::Error> {
        let code = item
            .request
            .authentication_token
            .as_ref()
            .ok_or(errors::ConnectorError::MissingRequiredField {
                field_name: "authorization_code",
            })?
            .code
            .clone();
        let grant_type = GrantType::AuthorizationCode;
        let redirect_uri = Some("https://hyperswitch.io".to_string());

        Ok(Self {
            code: Some(code),
            grant_type,
            redirect_uri,
            refresh_token: None, // We're not using refresh_token to generate new access_token
        })
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, NordeaOAuthExchangeResponse, T, AccessToken>>
    for RouterData<F, T, AccessToken>
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<F, NordeaOAuthExchangeResponse, T, AccessToken>,
    ) -> Result<Self, Self::Error> {
        let access_token =
            item.response
                .access_token
                .ok_or(errors::ConnectorError::MissingRequiredField {
                    field_name: "access_token",
                })?;

        let expires_in = item.response.expires_in.unwrap_or(3600); // Default to 1 hour if not provided

        Ok(Self {
            status: common_enums::AttemptStatus::AuthenticationSuccessful,
            response: Ok(AccessToken {
                token: access_token.clone(),
                expires: expires_in,
            }),
            ..item.data
        })
    }
}

impl TryFrom<&str> for AccountType {
    type Error = Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value.to_uppercase().as_str() {
            "IBAN" => Ok(Self::Iban),
            "BBAN_SE" => Ok(Self::BbanSe),
            "BBAN_DK" => Ok(Self::BbanDk),
            "BBAN_NO" => Ok(Self::BbanNo),
            "BGNR" => Ok(Self::Bgnr),
            "PGNR" => Ok(Self::Pgnr),
            "GIRO_DK" => Ok(Self::GiroDk),
            "BBAN_OTHER" => Ok(Self::BbanOther),
            _ => Err(errors::ConnectorError::InvalidConnectorConfig {
                config: "account_type",
            }
            .into()),
        }
    }
}

impl<'de> Deserialize<'de> for PaymentsUrgency {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?.to_lowercase();
        match s.as_str() {
            "standard" => Ok(Self::Standard),
            "express" => Ok(Self::Express),
            "sameday" => Ok(Self::Sameday),
            _ => Err(serde::de::Error::unknown_variant(
                &s,
                &["standard", "express", "sameday"],
            )),
        }
    }
}

fn get_creditor_account_from_metadata(
    router_data: &PaymentsPreProcessingRouterData,
) -> Result<CreditorAccount, Error> {
    let metadata: NordeaConnectorMetadataObject =
        utils::to_connector_meta_from_secret(router_data.connector_meta_data.clone())
            .change_context(errors::ConnectorError::InvalidConnectorConfig {
                config: "merchant_connector_account.metadata",
            })?;
    let creditor_account = CreditorAccount {
        account: AccountNumber {
            account_type: AccountType::try_from(metadata.account_type.as_str())
                .unwrap_or(AccountType::Iban),
            currency: router_data.request.currency,
            value: metadata.destination_account_number,
        },
        country: router_data.get_optional_billing_country(),
        // Merchant is the beneficiary in this case
        name: Some(metadata.merchant_name),
        message: router_data
            .description
            .as_ref()
            .map(|desc| desc.chars().take(20).collect::<String>()),
        bank: Some(CreditorBank {
            address: None,
            bank_code: None,
            bank_name: None,
            business_identifier_code: None,
            country: router_data.get_billing_country()?,
        }),
        creditor_address: None,
        // Either Reference or Message must be supplied in the request
        reference: None,
    };
    Ok(creditor_account)
}

impl TryFrom<&NordeaRouterData<&PaymentsPreProcessingRouterData>> for NordeaPaymentsRequest {
    type Error = Error;
    fn try_from(
        item: &NordeaRouterData<&PaymentsPreProcessingRouterData>,
    ) -> Result<Self, Self::Error> {
        match item.router_data.request.payment_method_data.clone() {
            Some(PaymentMethodData::BankDebit(bank_debit_data)) => match bank_debit_data {
                BankDebitData::SepaBankDebit { iban, .. } => {
                    let creditor_account = get_creditor_account_from_metadata(item.router_data)?;
                    let debitor_account = DebitorAccount {
                        account: AccountNumber {
                            account_type: AccountType::Iban,
                            currency: item.router_data.request.currency,
                            value: iban,
                        },
                        message: item
                            .router_data
                            .description
                            .as_ref()
                            .map(|desc| desc.chars().take(20).collect::<String>()),
                    };

                    let instructed_amount = super::requests::InstructedAmount {
                        amount: item.amount.clone(),
                        currency: item.router_data.request.currency.ok_or(
                            errors::ConnectorError::MissingRequiredField {
                                field_name: "amount",
                            },
                        )?,
                    };

                    Ok(Self {
                        creditor_account,
                        debitor_account,
                        end_to_end_identification: None,
                        external_id: Some(item.router_data.connector_request_reference_id.clone()),
                        instructed_amount,
                        recurring: None,
                        request_availability_of_funds: None,
                        requested_execution_date: None,
                        tpp_messages: None,
                        urgency: None,
                    })
                }
                BankDebitData::AchBankDebit { .. }
                | BankDebitData::BacsBankDebit { .. }
                | BankDebitData::BecsBankDebit { .. }
                | BankDebitData::SepaGuarenteedBankDebit { .. } => {
                    Err(errors::ConnectorError::NotImplemented(
                        get_unimplemented_payment_method_error_message("Nordea"),
                    )
                    .into())
                }
            },
            Some(PaymentMethodData::CardRedirect(_))
            | Some(PaymentMethodData::CardDetailsForNetworkTransactionId(_))
            | Some(PaymentMethodData::Wallet(_))
            | Some(PaymentMethodData::PayLater(_))
            | Some(PaymentMethodData::BankRedirect(_))
            | Some(PaymentMethodData::BankTransfer(_))
            | Some(PaymentMethodData::Crypto(_))
            | Some(PaymentMethodData::MandatePayment)
            | Some(PaymentMethodData::Reward)
            | Some(PaymentMethodData::RealTimePayment(_))
            | Some(PaymentMethodData::MobilePayment(_))
            | Some(PaymentMethodData::Upi(_))
            | Some(PaymentMethodData::Voucher(_))
            | Some(PaymentMethodData::GiftCard(_))
            | Some(PaymentMethodData::OpenBanking(_))
            | Some(PaymentMethodData::CardToken(_))
            | Some(PaymentMethodData::NetworkToken(_))
            | Some(PaymentMethodData::Card(_))
            | None => {
                Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into())
            }
        }
    }
}

impl TryFrom<&NordeaRouterData<&PaymentsAuthorizeRouterData>> for NordeaPaymentsConfirmRequest {
    type Error = Error;
    fn try_from(
        item: &NordeaRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let payment_ids = match &item.router_data.response {
            Ok(response_data) => response_data
                .get_connector_transaction_id()
                .map_err(|_| errors::ConnectorError::MissingConnectorTransactionID)?,
            Err(_) => return Err(errors::ConnectorError::ResponseDeserializationFailed.into()),
        };

        Ok(Self {
            authentication_method: None,
            language: None,
            payments_ids: vec![payment_ids],
            redirect_url: None,
            state: None,
        })
    }
}

impl From<NordeaPaymentStatus> for common_enums::AttemptStatus {
    fn from(item: NordeaPaymentStatus) -> Self {
        match item {
            NordeaPaymentStatus::Confirmed | NordeaPaymentStatus::Paid => Self::Charged,

            NordeaPaymentStatus::PendingConfirmation
            | NordeaPaymentStatus::PendingSecondConfirmation => Self::ConfirmationAwaited,
            NordeaPaymentStatus::PendingUserApproval => Self::AuthenticationPending,

            NordeaPaymentStatus::OnHold | NordeaPaymentStatus::Unknown => Self::Pending,

            NordeaPaymentStatus::Rejected
            | NordeaPaymentStatus::InsufficientFunds
            | NordeaPaymentStatus::LimitExceeded
            | NordeaPaymentStatus::UserApprovalFailed
            | NordeaPaymentStatus::UserApprovalTimeout
            | NordeaPaymentStatus::UserApprovalCancelled => Self::Failure,
        }
    }
}

pub fn get_error_data(error_response: Option<&NordeaErrorBody>) -> Option<&NordeaFailures> {
    error_response
        .and_then(|error| error.nordea_failures.as_ref())
        .and_then(|failures| failures.first())
}

// Helper function to convert NordeaPaymentsInitiateResponse to common response data
fn convert_nordea_payment_response(
    response: &NordeaPaymentsInitiateResponse,
) -> Result<(PaymentsResponseData, common_enums::AttemptStatus), Error> {
    let payment_response = response
        .payments_response
        .as_ref()
        .ok_or(errors::ConnectorError::ResponseHandlingFailed)?;

    let resource_id = ResponseId::ConnectorTransactionId(payment_response.payment_id.clone());

    let response_data = PaymentsResponseData::TransactionResponse {
        resource_id,
        redirection_data: Box::new(None),
        mandate_reference: Box::new(None),
        connector_metadata: None,
        network_txn_id: None,
        connector_response_reference_id: payment_response.external_id.clone(),
        incremental_authorization_allowed: None,
        charges: None,
    };

    let status = common_enums::AttemptStatus::from(payment_response.payment_status.clone());

    Ok((response_data, status))
}

impl TryFrom<PaymentsPreprocessingResponseRouterData<NordeaPaymentsInitiateResponse>>
    for RouterData<PreProcessing, PaymentsPreProcessingData, PaymentsResponseData>
{
    type Error = Error;
    fn try_from(
        item: PaymentsPreprocessingResponseRouterData<NordeaPaymentsInitiateResponse>,
    ) -> Result<Self, Self::Error> {
        let (response, status) = convert_nordea_payment_response(&item.response)?;
        Ok(Self {
            status,
            response: Ok(response),
            ..item.data
        })
    }
}

impl
    TryFrom<
        ResponseRouterData<
            Authorize,
            NordeaPaymentsConfirmResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    > for RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<
            Authorize,
            NordeaPaymentsConfirmResponse,
            PaymentsAuthorizeData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        // First check if there are any errors in the response
        if let Some(errors) = &item.response.errors {
            if !errors.is_empty() {
                // Get the first error for the error response
                let first_error = errors
                    .first()
                    .ok_or(errors::ConnectorError::ResponseHandlingFailed)?;

                return Ok(Self {
                    status: common_enums::AttemptStatus::Failure,
                    response: Err(ErrorResponse {
                        code: first_error
                            .error
                            .clone()
                            .unwrap_or_else(|| "UNKNOWN_ERROR".to_string()),
                        message: first_error
                            .error_description
                            .clone()
                            .unwrap_or_else(|| "Payment confirmation failed".to_string()),
                        reason: first_error.error_description.clone(),
                        status_code: item.http_code,
                        attempt_status: Some(common_enums::AttemptStatus::Failure),
                        connector_transaction_id: first_error.payment_id.clone(),
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                    ..item.data
                });
            }
        }

        // If no errors, proceed with normal response handling
        // Check if there's a redirect link at the top level only
        let redirection_data = item
            .response
            .links
            .as_ref()
            .and_then(|links| {
                links.iter().find(|link| {
                    link.rel
                        .as_ref()
                        .map(|rel| rel == "signing")
                        .unwrap_or(false)
                })
            })
            .and_then(|link| link.href.clone())
            .map(|redirect_url| RedirectForm::Form {
                endpoint: redirect_url,
                method: Method::Get,
                form_fields: HashMap::new(),
            });

        let (response, status) = match &item.response.nordea_payments_response {
            Some(payment_response_wrapper) => {
                // Get the first payment from the payments array
                let payment = payment_response_wrapper
                    .payments
                    .first()
                    .ok_or(errors::ConnectorError::ResponseHandlingFailed)?;

                let resource_id = ResponseId::ConnectorTransactionId(payment.payment_id.clone());

                let response = Ok(PaymentsResponseData::TransactionResponse {
                    resource_id,
                    redirection_data: Box::new(redirection_data),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: payment.external_id.clone(),
                    incremental_authorization_allowed: None,
                    charges: None,
                });

                let status = common_enums::AttemptStatus::from(payment.payment_status.clone());

                (response, status)
            }
            None => {
                // No payment response, but we might still have a redirect link
                if let Some(redirect) = redirection_data {
                    let response = Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::NoResponseId,
                        redirection_data: Box::new(Some(redirect)),
                        mandate_reference: Box::new(None),
                        connector_metadata: None,
                        network_txn_id: None,
                        connector_response_reference_id: None,
                        incremental_authorization_allowed: None,
                        charges: None,
                    });
                    (response, common_enums::AttemptStatus::AuthenticationPending)
                } else {
                    return Err(errors::ConnectorError::ResponseHandlingFailed.into());
                }
            }
        };

        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}

impl TryFrom<PaymentsSyncResponseRouterData<NordeaPaymentsInitiateResponse>>
    for PaymentsSyncRouterData
{
    type Error = Error;
    fn try_from(
        item: PaymentsSyncResponseRouterData<NordeaPaymentsInitiateResponse>,
    ) -> Result<Self, Self::Error> {
        let (response, status) = convert_nordea_payment_response(&item.response)?;
        Ok(Self {
            status,
            response: Ok(response),
            ..item.data
        })
    }
}


// File: crates/hyperswitch_connectors/src/connectors/nordea/responses.rs

use common_enums::CountryAlpha2;
use common_utils::types::StringMajorUnit;
use masking::Secret;
use serde::{Deserialize, Serialize};

use super::requests::{
    CreditorAccount, DebitorAccount, InstructedAmount, PaymentsUrgency, RecurringInformation,
    ThirdPartyMessages,
};

// OAuth token response structure
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NordeaOAuthExchangeResponse {
    pub access_token: Option<Secret<String>>,
    pub expires_in: Option<i64>,
    pub refresh_token: Option<Secret<String>>,
    pub token_type: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub enum NordeaPaymentStatus {
    #[default]
    PendingConfirmation,
    PendingSecondConfirmation,
    PendingUserApproval,
    OnHold,
    Confirmed,
    Rejected,
    Paid,
    InsufficientFunds,
    LimitExceeded,
    UserApprovalFailed,
    UserApprovalTimeout,
    UserApprovalCancelled,
    Unknown,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaGroupHeader {
    /// Response creation time. Format: date-time.
    pub creation_date_time: Option<String>,
    /// HTTP code for response. Format: int32.
    pub http_code: Option<i32>,
    /// Original request id for correlation purposes
    pub message_identification: Option<String>,
    /// Details of paginated response
    pub message_pagination: Option<MessagePagination>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaResponseLinks {
    /// Describes the nature of the link, e.g. 'details' for a link to the detailed information of a listed resource.
    pub rel: Option<String>,
    /// Relative path to the linked resource
    pub href: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FeesType {
    Additional,
    Standard,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct TransactionFee {
    /// Monetary amount
    pub amount: InstructedAmount,
    pub description: Option<String>,
    pub excluded_from_total_fee: Option<bool>,
    pub percentage: Option<bool>,
    #[serde(rename = "type")]
    pub fees_type: Option<FeesType>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct BankFee {
    /// Example 'domestic_transaction' only for DK domestic payments
    #[serde(rename = "_type")]
    pub bank_fee_type: Option<String>,
    /// Country code according to ISO Alpha-2
    pub country_code: Option<CountryAlpha2>,
    /// Currency code according to ISO 4217
    pub currency_code: Option<api_models::enums::Currency>,
    /// Value of the fee.
    pub value: Option<StringMajorUnit>,
    pub fees: Option<Vec<TransactionFee>>,
    /// Monetary amount
    pub total_fee_amount: InstructedAmount,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ChargeBearer {
    Shar,
    Debt,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct ExchangeRate {
    pub base_currency: Option<api_models::enums::Currency>,
    pub exchange_currency: Option<api_models::enums::Currency>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct MessagePagination {
    /// Resource listing may return a continuationKey if there's more results available.
    /// Request may be retried with the continuationKey, but otherwise same parameters, in order to get more results.
    pub continuation_key: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaPaymentsInitiateResponseData {
    /// Unique payment identifier assigned for new payment
    #[serde(rename = "_id")]
    pub payment_id: String,
    /// HATEOAS inspired links: 'rel' and 'href'. Context specific link (only GET supported)
    #[serde(rename = "_links")]
    pub links: Option<Vec<NordeaResponseLinks>>,
    /// Marked as required field in the docs, but connector does not send amount in payment_response.amount
    pub amount: Option<StringMajorUnit>,
    /// Bearer of charges. shar = The Debtor (sender of the payment) will pay all fees charged by the sending bank.
    /// The Creditor (recipient of the payment) will pay all fees charged by the receiving bank.
    /// debt = The Debtor (sender of the payment) will bear all of the payment transaction fees.
    /// The creditor (beneficiary) will receive the full amount of the payment.
    pub charge_bearer: Option<ChargeBearer>,
    /// Creditor of the payment
    #[serde(rename = "creditor")]
    pub creditor_account: CreditorAccount,
    pub currency: Option<api_models::enums::Currency>,
    /// Debtor of the payment
    #[serde(rename = "debtor")]
    pub debitor_account: Option<DebitorAccount>,
    /// Timestamp of payment creation. ISO 8601 format yyyy-mm-ddThh:mm:ss.fffZ. Format:date-time.
    pub entry_date_time: Option<String>,
    /// Unique identification as assigned by a partner to identify the payment.
    pub external_id: Option<String>,
    /// An amount the bank will charge for executing the payment
    pub fee: Option<BankFee>,
    pub indicative_exchange_rate: Option<ExchangeRate>,
    /// It is mentioned as `number`. It can be an integer or a decimal number.
    pub rate: Option<f32>,
    /// Monetary amount
    pub instructed_amount: Option<InstructedAmount>,
    /// Indication of cross border payment to own account
    pub is_own_account_transfer: Option<bool>,
    /// OTP Challenge
    pub otp_challenge: Option<String>,
    /// Status of the payment
    pub payment_status: NordeaPaymentStatus,
    /// Planned execution date will indicate the day the payment will be finalized. If the payment has been pushed due to cut-off, it will be indicated in planned execution date. Format:date.
    pub planned_execution_date: Option<String>,
    /// Recurring information
    pub recurring: Option<RecurringInformation>,
    /// Choose a preferred execution date (or leave blank for today's date).
    /// This should be a valid bank day, and depending on the country the date will either be pushed to the next valid bank day,
    /// or return an error if a non-banking day date was supplied (all dates accepted in sandbox).
    /// SEPA: max +5 years from yesterday, Domestic: max. +1 year from yesterday. NB: Not supported for Own transfer Non-Recurring Norway.
    /// Format:date.
    pub requested_execution_date: Option<String>,
    /// Timestamp of payment creation. ISO 8601 format yyyy-mm-ddThh:mm:ss.fffZ Format:date-time.
    pub timestamp: Option<String>,
    /// Additional messages for third parties
    pub tpp_messages: Option<Vec<ThirdPartyMessages>>,
    pub transaction_fee: Option<Vec<BankFee>>,
    /// Currency that the cross border payment will be transferred in.
    /// This field is only supported for cross border payments for DK.
    /// If this field is not supplied then the payment will use the currency specified for the currency field of instructed_amount.
    pub transfer_currency: Option<api_models::enums::Currency>,
    /// Urgency of the payment. NB: This field is supported for DK Domestic ('standard' and 'express') and NO Domestic bank transfer payments ('standard' and 'express').
    /// Use 'express' for Straksbetaling (Instant payment).
    /// All other payment types ignore this input.
    /// For further details on urgencies and cut-offs, refer to the Nordea website.
    /// Value 'sameday' is marked as deprecated and will be removed in the future.
    pub urgency: Option<PaymentsUrgency>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaPaymentsInitiateResponse {
    /// Payment information
    #[serde(rename = "response")]
    pub payments_response: Option<NordeaPaymentsInitiateResponseData>,
    /// External response header
    pub group_header: Option<NordeaGroupHeader>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaPaymentsConfirmErrorObject {
    /// Error message
    pub error: Option<String>,
    /// Description of the error
    pub error_description: Option<String>,
    /// Payment id of the payment, the error is associated with
    pub payment_id: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaPaymentsResponseWrapper {
    pub payments: Vec<NordeaPaymentsInitiateResponseData>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaPaymentsConfirmResponse {
    /// HATEOAS inspired links: 'rel' and 'href'
    #[serde(rename = "_links")]
    pub links: Option<Vec<NordeaResponseLinks>>,
    /// Error description
    pub errors: Option<Vec<NordeaPaymentsConfirmErrorObject>>,
    /// External response header
    pub group_header: Option<NordeaGroupHeader>,
    /// OTP Challenge
    pub otp_challenge: Option<String>,
    #[serde(rename = "response")]
    pub nordea_payments_response: Option<NordeaPaymentsResponseWrapper>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaOriginalRequest {
    /// Original request url
    #[serde(rename = "url")]
    pub nordea_url: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaFailures {
    /// Failure code
    pub code: Option<String>,
    /// Failure description
    pub description: Option<String>,
    /// JSON path of the failing element if applicable
    pub path: Option<String>,
    /// Type of the validation error, e.g. NotNull
    #[serde(rename = "type")]
    pub failure_type: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaErrorBody {
    // Serde JSON because connector returns an `(item)` object in failures array object
    /// More details on the occurred error: Validation error
    #[serde(rename = "failures")]
    pub nordea_failures: Option<Vec<NordeaFailures>>,
    /// Original request information
    #[serde(rename = "request")]
    pub nordea_request: Option<NordeaOriginalRequest>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaErrorResponse {
    /// Error response body
    pub error: Option<NordeaErrorBody>,
    /// External response header
    pub group_header: Option<NordeaGroupHeader>,
    #[serde(rename = "httpCode")]
    pub http_code: Option<String>,
    #[serde(rename = "moreInformation")]
    pub more_information: Option<String>,
}

// Nordea does not support refunds in Private APIs. Only Corporate APIs support Refunds