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

use common_enums::enums;
use common_utils::{
    pii,
    types::{MinorUnit, StringMajorUnit},
};
use hyperswitch_domain_models::{
    payment_method_data::PaymentMethodData,
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm},
    types,
};
use hyperswitch_interfaces::{consts, errors};
use masking::Secret;
use reqwest::Url;
use serde::{Deserialize, Serialize};

use crate::{
    types::ResponseRouterData,
    utils::{self, CryptoData, ForeignTryFrom, PaymentsAuthorizeRequestData},
};

#[derive(Debug, Serialize)]
pub struct CryptopayRouterData<T> {
    pub amount: StringMajorUnit,
    pub router_data: T,
}

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

#[derive(Default, Debug, Serialize)]
pub struct CryptopayPaymentsRequest {
    price_amount: StringMajorUnit,
    price_currency: enums::Currency,
    pay_currency: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    network: Option<String>,
    success_redirect_url: Option<String>,
    unsuccess_redirect_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    metadata: Option<pii::SecretSerdeValue>,
    custom_id: String,
}

impl TryFrom<&CryptopayRouterData<&types::PaymentsAuthorizeRouterData>>
    for CryptopayPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &CryptopayRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let cryptopay_request = match item.router_data.request.payment_method_data {
            PaymentMethodData::Crypto(ref cryptodata) => {
                let pay_currency = cryptodata.get_pay_currency()?;
                Ok(Self {
                    price_amount: item.amount.clone(),
                    price_currency: item.router_data.request.currency,
                    pay_currency,
                    network: cryptodata.network.to_owned(),
                    success_redirect_url: item.router_data.request.router_return_url.clone(),
                    unsuccess_redirect_url: item.router_data.request.router_return_url.clone(),
                    //Cryptopay only accepts metadata as Object. If any other type, payment will fail with error.
                    metadata: item.router_data.request.get_metadata_as_object(),
                    custom_id: item.router_data.connector_request_reference_id.clone(),
                })
            }
            PaymentMethodData::Card(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankDebit(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("CryptoPay"),
                ))
            }
        }?;
        Ok(cryptopay_request)
    }
}

// Auth Struct
pub struct CryptopayAuthType {
    pub(super) api_key: Secret<String>,
    pub(super) api_secret: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for CryptopayAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        if let ConnectorAuthType::BodyKey { api_key, key1 } = auth_type {
            Ok(Self {
                api_key: api_key.to_owned(),
                api_secret: key1.to_owned(),
            })
        } else {
            Err(errors::ConnectorError::FailedToObtainAuthType.into())
        }
    }
}
// PaymentsResponse
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CryptopayPaymentStatus {
    New,
    Completed,
    Unresolved,
    Refunded,
    Cancelled,
}

impl From<CryptopayPaymentStatus> for enums::AttemptStatus {
    fn from(item: CryptopayPaymentStatus) -> Self {
        match item {
            CryptopayPaymentStatus::New => Self::AuthenticationPending,
            CryptopayPaymentStatus::Completed => Self::Charged,
            CryptopayPaymentStatus::Cancelled => Self::Failure,
            CryptopayPaymentStatus::Unresolved | CryptopayPaymentStatus::Refunded => {
                Self::Unresolved
            } //mapped refunded to Unresolved because refund api is not available, also merchant has done the action on the connector dashboard.
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CryptopayPaymentsResponse {
    pub data: CryptopayPaymentResponseData,
}

impl<F, T>
    ForeignTryFrom<(
        ResponseRouterData<F, CryptopayPaymentsResponse, T, PaymentsResponseData>,
        Option<MinorUnit>,
    )> for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn foreign_try_from(
        (item, amount_captured_in_minor_units): (
            ResponseRouterData<F, CryptopayPaymentsResponse, T, PaymentsResponseData>,
            Option<MinorUnit>,
        ),
    ) -> Result<Self, Self::Error> {
        let status = enums::AttemptStatus::from(item.response.data.status.clone());
        let response = if utils::is_payment_failure(status) {
            let payment_response = &item.response.data;
            Err(ErrorResponse {
                code: payment_response
                    .name
                    .clone()
                    .unwrap_or(consts::NO_ERROR_CODE.to_string()),
                message: payment_response
                    .status_context
                    .clone()
                    .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
                reason: payment_response.status_context.clone(),
                status_code: item.http_code,
                attempt_status: None,
                connector_transaction_id: Some(payment_response.id.clone()),
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            })
        } else {
            let redirection_data = item
                .response
                .data
                .hosted_page_url
                .map(|x| RedirectForm::from((x, common_utils::request::Method::Get)));
            Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.data.id.clone()),
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item
                    .response
                    .data
                    .custom_id
                    .or(Some(item.response.data.id)),
                incremental_authorization_allowed: None,
                charges: None,
            })
        };
        match (amount_captured_in_minor_units, status) {
            (Some(minor_amount), enums::AttemptStatus::Charged) => {
                let amount_captured = Some(minor_amount.get_amount_as_i64());
                Ok(Self {
                    status,
                    response,
                    amount_captured,
                    minor_amount_captured: amount_captured_in_minor_units,
                    ..item.data
                })
            }
            _ => Ok(Self {
                status,
                response,
                ..item.data
            }),
        }
    }
}

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct CryptopayErrorData {
    pub code: String,
    pub message: String,
    pub reason: Option<String>,
}

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct CryptopayErrorResponse {
    pub error: CryptopayErrorData,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CryptopayPaymentResponseData {
    pub id: String,
    pub custom_id: Option<String>,
    pub customer_id: Option<String>,
    pub status: CryptopayPaymentStatus,
    pub status_context: Option<String>,
    pub address: Option<Secret<String>>,
    pub network: Option<String>,
    pub uri: Option<String>,
    pub price_amount: Option<StringMajorUnit>,
    pub price_currency: Option<String>,
    pub pay_amount: Option<StringMajorUnit>,
    pub pay_currency: Option<String>,
    pub fee: Option<String>,
    pub fee_currency: Option<String>,
    pub paid_amount: Option<String>,
    pub name: Option<String>,
    pub description: Option<String>,
    pub success_redirect_url: Option<String>,
    pub unsuccess_redirect_url: Option<String>,
    pub hosted_page_url: Option<Url>,
    pub created_at: Option<String>,
    pub expires_at: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CryptopayWebhookDetails {
    #[serde(rename = "type")]
    pub service_type: String,
    pub event: WebhookEvent,
    pub data: CryptopayPaymentResponseData,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WebhookEvent {
    TransactionCreated,
    TransactionConfirmed,
    StatusChanged,
}


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

use common_enums::{enums, Currency};
use common_utils::{pii::Email, request::Method, types::MinorUnit};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{BankRedirectData, PaymentMethodData},
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{PaymentsAuthorizeRouterData, RefundsRouterData},
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::PaymentsAuthorizeRequestData,
};

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

impl<T> From<(MinorUnit, T)> for PaystackRouterData<T> {
    fn from((amount, item): (MinorUnit, T)) -> Self {
        //Todo :  use utils to convert the amount to the type of amount that a connector accepts
        Self {
            amount,
            router_data: item,
        }
    }
}

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct PaystackEftProvider {
    provider: String,
}

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct PaystackPaymentsRequest {
    amount: MinorUnit,
    currency: Currency,
    email: Email,
    eft: PaystackEftProvider,
}

impl TryFrom<&PaystackRouterData<&PaymentsAuthorizeRouterData>> for PaystackPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &PaystackRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::BankRedirect(BankRedirectData::Eft { provider }) => {
                let email = item.router_data.request.get_email()?;
                let eft = PaystackEftProvider { provider };
                Ok(Self {
                    amount: item.amount,
                    currency: item.router_data.request.currency,
                    email,
                    eft,
                })
            }
            _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
        }
    }
}

pub struct PaystackAuthType {
    pub(super) api_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for PaystackAuthType {
    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 {
                api_key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PaystackEftRedirect {
    reference: String,
    status: String,
    url: String,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PaystackPaymentsResponseData {
    status: bool,
    message: String,
    data: PaystackEftRedirect,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum PaystackPaymentsResponse {
    PaystackPaymentsData(PaystackPaymentsResponseData),
    PaystackPaymentsError(PaystackErrorResponse),
}

impl<F, T> TryFrom<ResponseRouterData<F, PaystackPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PaystackPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let (status, response) = match item.response {
            PaystackPaymentsResponse::PaystackPaymentsData(resp) => {
                let redirection_url = Url::parse(resp.data.url.as_str())
                    .change_context(errors::ConnectorError::ParsingFailed)?;
                let redirection_data = RedirectForm::from((redirection_url, Method::Get));
                (
                    common_enums::AttemptStatus::AuthenticationPending,
                    Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::ConnectorTransactionId(
                            resp.data.reference.clone(),
                        ),
                        redirection_data: Box::new(Some(redirection_data)),
                        mandate_reference: Box::new(None),
                        connector_metadata: None,
                        network_txn_id: None,
                        connector_response_reference_id: None,
                        incremental_authorization_allowed: None,
                        charges: None,
                    }),
                )
            }
            PaystackPaymentsResponse::PaystackPaymentsError(err) => {
                let err_msg = get_error_message(err.clone());
                (
                    common_enums::AttemptStatus::Failure,
                    Err(ErrorResponse {
                        code: err.code,
                        message: err_msg.clone(),
                        reason: Some(err_msg.clone()),
                        attempt_status: None,
                        connector_transaction_id: None,
                        status_code: item.http_code,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                )
            }
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PaystackPSyncStatus {
    Abandoned,
    Failed,
    Ongoing,
    Pending,
    Processing,
    Queued,
    Reversed,
    Success,
}

impl From<PaystackPSyncStatus> for common_enums::AttemptStatus {
    fn from(item: PaystackPSyncStatus) -> Self {
        match item {
            PaystackPSyncStatus::Success => Self::Charged,
            PaystackPSyncStatus::Abandoned => Self::AuthenticationPending,
            PaystackPSyncStatus::Ongoing
            | PaystackPSyncStatus::Pending
            | PaystackPSyncStatus::Processing
            | PaystackPSyncStatus::Queued => Self::Pending,
            PaystackPSyncStatus::Failed => Self::Failure,
            PaystackPSyncStatus::Reversed => Self::Voided,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PaystackPSyncData {
    status: PaystackPSyncStatus,
    reference: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PaystackPSyncResponseData {
    status: bool,
    message: String,
    data: PaystackPSyncData,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum PaystackPSyncResponse {
    PaystackPSyncData(PaystackPSyncResponseData),
    PaystackPSyncWebhook(PaystackPaymentWebhookData),
    PaystackPSyncError(PaystackErrorResponse),
}

impl<F, T> TryFrom<ResponseRouterData<F, PaystackPSyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PaystackPSyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            PaystackPSyncResponse::PaystackPSyncData(resp) => Ok(Self {
                status: common_enums::AttemptStatus::from(resp.data.status),
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(resp.data.reference.clone()),
                    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
            }),
            PaystackPSyncResponse::PaystackPSyncWebhook(resp) => Ok(Self {
                status: common_enums::AttemptStatus::from(resp.status),
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(resp.reference.clone()),
                    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
            }),
            PaystackPSyncResponse::PaystackPSyncError(err) => {
                let err_msg = get_error_message(err.clone());
                Ok(Self {
                    response: Err(ErrorResponse {
                        code: err.code,
                        message: err_msg.clone(),
                        reason: Some(err_msg.clone()),
                        attempt_status: None,
                        connector_transaction_id: None,
                        status_code: item.http_code,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                    ..item.data
                })
            }
        }
    }
}

#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct PaystackRefundRequest {
    pub transaction: String,
    pub amount: MinorUnit,
}

impl<F> TryFrom<&PaystackRouterData<&RefundsRouterData<F>>> for PaystackRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaystackRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        Ok(Self {
            transaction: item.router_data.request.connector_transaction_id.clone(),
            amount: item.amount.to_owned(),
        })
    }
}

#[derive(Debug, Serialize, Default, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PaystackRefundStatus {
    Processed,
    Failed,
    #[default]
    Processing,
    Pending,
}

impl From<PaystackRefundStatus> for enums::RefundStatus {
    fn from(item: PaystackRefundStatus) -> Self {
        match item {
            PaystackRefundStatus::Processed => Self::Success,
            PaystackRefundStatus::Failed => Self::Failure,
            PaystackRefundStatus::Processing | PaystackRefundStatus::Pending => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PaystackRefundsData {
    status: PaystackRefundStatus,
    id: i64,
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PaystackRefundsResponseData {
    status: bool,
    message: String,
    data: PaystackRefundsData,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum PaystackRefundsResponse {
    PaystackRefundsData(PaystackRefundsResponseData),
    PaystackRSyncWebhook(PaystackRefundWebhookData),
    PaystackRefundsError(PaystackErrorResponse),
}

impl TryFrom<RefundsResponseRouterData<Execute, PaystackRefundsResponse>>
    for RefundsRouterData<Execute>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, PaystackRefundsResponse>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            PaystackRefundsResponse::PaystackRefundsData(resp) => Ok(Self {
                response: Ok(RefundsResponseData {
                    connector_refund_id: resp.data.id.to_string(),
                    refund_status: enums::RefundStatus::from(resp.data.status),
                }),
                ..item.data
            }),
            PaystackRefundsResponse::PaystackRSyncWebhook(resp) => Ok(Self {
                response: Ok(RefundsResponseData {
                    connector_refund_id: resp.id,
                    refund_status: enums::RefundStatus::from(resp.status),
                }),
                ..item.data
            }),
            PaystackRefundsResponse::PaystackRefundsError(err) => {
                let err_msg = get_error_message(err.clone());
                Ok(Self {
                    response: Err(ErrorResponse {
                        code: err.code,
                        message: err_msg.clone(),
                        reason: Some(err_msg.clone()),
                        attempt_status: None,
                        connector_transaction_id: None,
                        status_code: item.http_code,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                    ..item.data
                })
            }
        }
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, PaystackRefundsResponse>>
    for RefundsRouterData<RSync>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, PaystackRefundsResponse>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            PaystackRefundsResponse::PaystackRefundsData(resp) => Ok(Self {
                response: Ok(RefundsResponseData {
                    connector_refund_id: resp.data.id.to_string(),
                    refund_status: enums::RefundStatus::from(resp.data.status),
                }),
                ..item.data
            }),
            PaystackRefundsResponse::PaystackRSyncWebhook(resp) => Ok(Self {
                response: Ok(RefundsResponseData {
                    connector_refund_id: resp.id,
                    refund_status: enums::RefundStatus::from(resp.status),
                }),
                ..item.data
            }),
            PaystackRefundsResponse::PaystackRefundsError(err) => {
                let err_msg = get_error_message(err.clone());
                Ok(Self {
                    response: Err(ErrorResponse {
                        code: err.code,
                        message: err_msg.clone(),
                        reason: Some(err_msg.clone()),
                        attempt_status: None,
                        connector_transaction_id: None,
                        status_code: item.http_code,
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    }),
                    ..item.data
                })
            }
        }
    }
}

#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PaystackErrorResponse {
    pub status: bool,
    pub message: String,
    pub data: Option<serde_json::Value>,
    pub meta: serde_json::Value,
    pub code: String,
}

pub fn get_error_message(response: PaystackErrorResponse) -> String {
    if let Some(serde_json::Value::Object(err_map)) = response.data {
        err_map.get("message").map(|msg| msg.clone().to_string())
    } else {
        None
    }
    .unwrap_or(response.message)
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PaystackPaymentWebhookData {
    pub status: PaystackPSyncStatus,
    pub reference: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PaystackRefundWebhookData {
    pub status: PaystackRefundStatus,
    pub id: String,
    pub transaction_reference: String,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum PaystackWebhookEventData {
    Payment(PaystackPaymentWebhookData),
    Refund(PaystackRefundWebhookData),
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct PaystackWebhookData {
    pub event: String,
    pub data: PaystackWebhookEventData,
}

impl From<PaystackWebhookEventData> for api_models::webhooks::IncomingWebhookEvent {
    fn from(item: PaystackWebhookEventData) -> Self {
        match item {
            PaystackWebhookEventData::Payment(payment_data) => match payment_data.status {
                PaystackPSyncStatus::Success => Self::PaymentIntentSuccess,
                PaystackPSyncStatus::Failed => Self::PaymentIntentFailure,
                PaystackPSyncStatus::Abandoned
                | PaystackPSyncStatus::Ongoing
                | PaystackPSyncStatus::Pending
                | PaystackPSyncStatus::Processing
                | PaystackPSyncStatus::Queued => Self::PaymentIntentProcessing,
                PaystackPSyncStatus::Reversed => Self::EventNotSupported,
            },
            PaystackWebhookEventData::Refund(refund_data) => match refund_data.status {
                PaystackRefundStatus::Processed => Self::RefundSuccess,
                PaystackRefundStatus::Failed => Self::RefundFailure,
                PaystackRefundStatus::Processing | PaystackRefundStatus::Pending => {
                    Self::EventNotSupported
                }
            },
        }
    }
}


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

use std::collections::HashMap;

use common_enums::enums;
use common_utils::{
    errors::CustomResult,
    ext_traits::ByteSliceExt,
    pii::{Email, IpAddress},
    request::Method,
    types::FloatMajorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{PaymentMethodData, WalletData},
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{PaymentsAuthorizeRouterData, RefundsRouterData},
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};

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

pub struct CalidaRouterData<T> {
    pub amount: FloatMajorUnit,
    pub router_data: T,
}

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

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct CalidaMetadataObject {
    pub shop_name: String,
}

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

#[derive(Debug, Serialize, PartialEq)]
pub struct CalidaPaymentsRequest {
    pub amount: FloatMajorUnit,
    pub currency: enums::Currency,
    pub payment_provider: String,
    pub shop_name: String,
    pub reference: String,
    pub ip_address: Option<Secret<String, IpAddress>>,
    pub first_name: Secret<String>,
    pub last_name: Secret<String>,
    pub billing_address_country_code_iso: enums::CountryAlpha2,
    pub billing_address_city: String,
    pub billing_address_line1: Secret<String>,
    pub billing_address_postal_code: Secret<String>,
    pub webhook_url: String,
    pub success_url: String,
    pub failure_url: String,
}

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

impl TryFrom<&CalidaRouterData<&PaymentsAuthorizeRouterData>> for CalidaPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &CalidaRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        match item.router_data.request.capture_method {
            Some(enums::CaptureMethod::Manual)
            | Some(enums::CaptureMethod::ManualMultiple)
            | Some(enums::CaptureMethod::Scheduled)
            | Some(enums::CaptureMethod::SequentialAutomatic) => {
                Err(errors::ConnectorError::FlowNotSupported {
                    flow: format!("{:?}", item.router_data.request.capture_method),
                    connector: "Calida".to_string(),
                }
                .into())
            }
            Some(enums::CaptureMethod::Automatic) | None => {
                match item.router_data.request.payment_method_data.clone() {
                    PaymentMethodData::Wallet(WalletData::BluecodeRedirect {}) => {
                        let calida_mca_metadata =
                            CalidaMetadataObject::try_from(&item.router_data.connector_meta_data)?;
                        Self::try_from((item, &calida_mca_metadata))
                    }
                    _ => Err(
                        errors::ConnectorError::NotImplemented("Payment method".to_string()).into(),
                    ),
                }
            }
        }
    }
}

impl
    TryFrom<(
        &CalidaRouterData<&PaymentsAuthorizeRouterData>,
        &CalidaMetadataObject,
    )> for CalidaPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        value: (
            &CalidaRouterData<&PaymentsAuthorizeRouterData>,
            &CalidaMetadataObject,
        ),
    ) -> Result<Self, Self::Error> {
        let item = value.0;

        Ok(Self {
            amount: item.amount,
            currency: item.router_data.request.currency,
            payment_provider: "bluecode_payment".to_string(),
            shop_name: value.1.shop_name.clone(),
            reference: item.router_data.payment_id.clone(),
            ip_address: item.router_data.request.get_ip_address_as_optional(),
            first_name: item.router_data.get_billing_first_name()?,
            last_name: item.router_data.get_billing_last_name()?,
            billing_address_country_code_iso: item.router_data.get_billing_country()?,
            billing_address_city: item.router_data.get_billing_city()?,
            billing_address_line1: item.router_data.get_billing_line1()?,
            billing_address_postal_code: item.router_data.get_billing_zip()?,
            webhook_url: item.router_data.request.get_webhook_url()?,
            success_url: item.router_data.request.get_router_return_url()?,
            failure_url: item.router_data.request.get_router_return_url()?,
        })
    }
}

pub struct CalidaAuthType {
    pub(super) api_key: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for CalidaAuthType {
    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 {
                api_key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

impl From<CalidaPaymentStatus> for common_enums::AttemptStatus {
    fn from(item: CalidaPaymentStatus) -> Self {
        match item {
            CalidaPaymentStatus::ManualProcessing => Self::Pending,
            CalidaPaymentStatus::Pending | CalidaPaymentStatus::PaymentInitiated => {
                Self::AuthenticationPending
            }
            CalidaPaymentStatus::Failed => Self::Failure,
            CalidaPaymentStatus::Completed => Self::Charged,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CalidaPaymentsResponse {
    pub id: i64,
    pub order_id: String,
    pub amount: FloatMajorUnit,
    pub currency: enums::Currency,
    pub charged_amount: FloatMajorUnit,
    pub charged_currency: enums::Currency,
    pub status: CalidaPaymentStatus,
    pub payment_link: url::Url,
    pub etoken: Secret<String>,
    pub payment_request_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CalidaSyncResponse {
    pub id: Option<i64>,
    pub order_id: String,
    pub user_id: Option<i64>,
    pub customer_id: Option<String>,
    pub customer_email: Option<Email>,
    pub customer_phone: Option<String>,
    pub status: CalidaPaymentStatus,
    pub payment_provider: Option<String>,
    pub payment_connector: Option<String>,
    pub payment_method: Option<String>,
    pub payment_method_type: Option<String>,
    pub shop_name: Option<String>,
    pub sender_name: Option<String>,
    pub sender_email: Option<String>,
    pub description: Option<String>,
    pub amount: FloatMajorUnit,
    pub currency: enums::Currency,
    pub charged_amount: Option<FloatMajorUnit>,
    pub charged_amount_currency: Option<String>,
    pub charged_fx_amount: Option<FloatMajorUnit>,
    pub charged_fx_amount_currency: Option<enums::Currency>,
    pub is_underpaid: Option<bool>,
    pub billing_amount: Option<FloatMajorUnit>,
    pub billing_currency: Option<String>,
    pub language: Option<String>,
    pub ip_address: Option<Secret<String, IpAddress>>,
    pub first_name: Option<Secret<String>>,
    pub last_name: Option<Secret<String>>,
    pub billing_address_line1: Option<Secret<String>>,
    pub billing_address_city: Option<Secret<String>>,
    pub billing_address_postal_code: Option<Secret<String>>,
    pub billing_address_country: Option<String>,
    pub billing_address_country_code_iso: Option<enums::CountryAlpha2>,
    pub shipping_address_country_code_iso: Option<enums::CountryAlpha2>,
    pub success_url: Option<String>,
    pub failure_url: Option<String>,
    pub source: Option<String>,
    pub bonus_code: Option<String>,
    pub dob: Option<String>,
    pub fees_amount: Option<f64>,
    pub fx_margin_amount: Option<f64>,
    pub fx_margin_percent: Option<f64>,
    pub fees_percent: Option<f64>,
    pub reseller_id: Option<String>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CalidaPaymentStatus {
    Pending,
    PaymentInitiated,
    ManualProcessing,
    Failed,
    Completed,
}

impl<F, T> TryFrom<ResponseRouterData<F, CalidaPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, CalidaPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let url = item.response.payment_link.clone();
        let redirection_data = Some(RedirectForm::from((url, Method::Get)));
        Ok(Self {
            status: common_enums::AttemptStatus::from(item.response.status),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.order_id),
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.payment_request_id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, CalidaSyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, CalidaSyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: common_enums::AttemptStatus::from(item.response.status),
            response: item.data.response,
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize)]
pub struct CalidaRefundRequest {
    pub amount: FloatMajorUnit,
}

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

// Type definition for Refund Response

#[allow(dead_code)]
#[derive(Debug, Copy, 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 CalidaErrorResponse {
    pub message: String,
    pub context_data: HashMap<String, Value>,
}

pub(crate) fn get_calida_webhook_event(
    status: CalidaPaymentStatus,
) -> api_models::webhooks::IncomingWebhookEvent {
    match status {
        CalidaPaymentStatus::Completed => {
            api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess
        }
        CalidaPaymentStatus::PaymentInitiated
        | CalidaPaymentStatus::ManualProcessing
        | CalidaPaymentStatus::Pending => {
            api_models::webhooks::IncomingWebhookEvent::PaymentIntentProcessing
        }
        CalidaPaymentStatus::Failed => {
            api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure
        }
    }
}

pub(crate) fn get_webhook_object_from_body(
    body: &[u8],
) -> CustomResult<CalidaSyncResponse, common_utils::errors::ParsingError> {
    let webhook: CalidaSyncResponse = body.parse_struct("CalidaIncomingWebhook")?;

    Ok(webhook)
}

pub fn sort_and_minify_json(value: &Value) -> Result<String, errors::ConnectorError> {
    fn sort_value(val: &Value) -> Value {
        match val {
            Value::Object(map) => {
                let mut entries: Vec<_> = map.iter().collect();
                entries.sort_by_key(|(k, _)| k.to_owned());

                let sorted_map: Map<String, Value> = entries
                    .into_iter()
                    .map(|(k, v)| (k.clone(), sort_value(v)))
                    .collect();

                Value::Object(sorted_map)
            }
            Value::Array(arr) => Value::Array(arr.iter().map(sort_value).collect()),
            _ => val.clone(),
        }
    }

    let sorted_value = sort_value(value);
    serde_json::to_string(&sorted_value)
        .map_err(|_| errors::ConnectorError::WebhookBodyDecodingFailed)
}


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

use std::collections::HashMap;

use api_models::admin::{AdditionalMerchantData, MerchantAccountData, MerchantRecipientData};
use common_enums::enums;
use common_utils::{id_type::MerchantId, request::Method, types::StringMajorUnit};
use hyperswitch_domain_models::{
    payment_method_data::PaymentMethodData,
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{PaymentsAuthorizeRouterData, RefundsRouterData},
};
use hyperswitch_interfaces::{
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    errors,
};
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{self},
};

//TODO: Fill the struct with respective fields
pub struct TokenioRouterData<T> {
    pub amount: StringMajorUnit,
    pub router_data: T,
}

impl<T> From<(StringMajorUnit, T)> for TokenioRouterData<T> {
    fn from((amount, item): (StringMajorUnit, T)) -> Self {
        Self {
            amount,
            router_data: item,
        }
    }
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenioPaymentsRequest {
    pub initiation: PaymentInitiation,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PaymentInitiation {
    pub ref_id: String,
    pub remittance_information_primary: MerchantId,
    pub amount: Amount,
    pub local_instrument: LocalInstrument,
    pub creditor: Creditor,
    pub callback_url: Option<String>,
    pub flow_type: FlowType,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Amount {
    pub value: StringMajorUnit,
    pub currency: enums::Currency,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LocalInstrument {
    Sepa,
    SepaInstant,
    FasterPayments,
    Elixir,
    Bankgiro,
    Plusgiro,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum Creditor {
    FasterPayments {
        #[serde(rename = "sortCode")]
        sort_code: Secret<String>,
        #[serde(rename = "accountNumber")]
        account_number: Secret<String>,
        name: Secret<String>,
    },
    Sepa {
        iban: Secret<String>,
        name: Secret<String>,
    },
    SepaInstant {
        iban: Secret<String>,
        name: Secret<String>,
    },
    ElixirIban {
        iban: Secret<String>,
        name: Secret<String>,
    },
    ElixirAccount {
        #[serde(rename = "accountNumber")]
        account_number: Secret<String>,
        name: Secret<String>,
    },
    Bankgiro {
        #[serde(rename = "bankgiroNumber")]
        bankgiro_number: Secret<String>,
        name: Secret<String>,
    },
    Plusgiro {
        #[serde(rename = "plusgiroNumber")]
        plusgiro_number: Secret<String>,
        name: Secret<String>,
    },
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FlowType {
    ApiOnly,
    FullHostedPages,
    EmbeddedHostedPages,
}

impl TryFrom<&TokenioRouterData<&PaymentsAuthorizeRouterData>> for TokenioPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &TokenioRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::OpenBanking(_) => {
                let (local_instrument, creditor) = match item
                    .router_data
                    .additional_merchant_data
                    .as_ref()
                    .and_then(|data| match data {
                        AdditionalMerchantData::OpenBankingRecipientData(recipient_data) => {
                            match recipient_data {
                                MerchantRecipientData::AccountData(account_data) => {
                                    Some(account_data)
                                }
                                _ => None,
                            }
                        }
                    }) {
                    Some(MerchantAccountData::FasterPayments {
                        account_number,
                        sort_code,
                        name,
                        ..
                    }) => (
                        LocalInstrument::FasterPayments,
                        Creditor::FasterPayments {
                            sort_code: sort_code.clone(),
                            account_number: account_number.clone(),
                            name: name.clone().into(),
                        },
                    ),

                    Some(MerchantAccountData::SepaInstant { iban, name, .. }) => (
                        LocalInstrument::SepaInstant,
                        Creditor::SepaInstant {
                            iban: iban.clone(),
                            name: name.clone().into(),
                        },
                    ),

                    Some(MerchantAccountData::Sepa { iban, name, .. }) => (
                        LocalInstrument::Sepa,
                        Creditor::Sepa {
                            iban: iban.clone(),
                            name: name.clone().into(),
                        },
                    ),

                    Some(MerchantAccountData::Iban { iban, name, .. }) => (
                        LocalInstrument::Sepa, // Assuming IBAN defaults to SEPA
                        Creditor::Sepa {
                            iban: iban.clone(),
                            name: name.clone().into(),
                        },
                    ),

                    Some(MerchantAccountData::Elixir {
                        account_number,
                        iban,
                        name,
                        ..
                    }) => {
                        if !iban.peek().is_empty() {
                            (
                                LocalInstrument::Elixir,
                                Creditor::ElixirIban {
                                    iban: iban.clone(),
                                    name: name.clone().into(),
                                },
                            )
                        } else {
                            (
                                LocalInstrument::Elixir,
                                Creditor::ElixirAccount {
                                    account_number: account_number.clone(),
                                    name: name.clone().into(),
                                },
                            )
                        }
                    }
                    Some(MerchantAccountData::Bacs {
                        account_number,
                        sort_code,
                        name,
                        ..
                    }) => (
                        LocalInstrument::FasterPayments,
                        Creditor::FasterPayments {
                            sort_code: sort_code.clone(),
                            account_number: account_number.clone(),
                            name: name.clone().into(),
                        },
                    ),

                    Some(MerchantAccountData::Bankgiro { number, name, .. }) => (
                        LocalInstrument::Bankgiro,
                        Creditor::Bankgiro {
                            bankgiro_number: number.clone(),
                            name: name.clone().into(),
                        },
                    ),

                    Some(MerchantAccountData::Plusgiro { number, name, .. }) => (
                        LocalInstrument::Plusgiro,
                        Creditor::Plusgiro {
                            plusgiro_number: number.clone(),
                            name: name.clone().into(),
                        },
                    ),

                    None => {
                        return Err(errors::ConnectorError::InvalidConnectorConfig {
                            config: "No valid payment method found in additional merchant data",
                        }
                        .into())
                    }
                };
                Ok(Self {
                    initiation: PaymentInitiation {
                        ref_id: utils::generate_12_digit_number().to_string(),
                        remittance_information_primary: item.router_data.merchant_id.clone(),
                        amount: Amount {
                            value: item.amount.clone(),
                            currency: item.router_data.request.currency,
                        },
                        local_instrument,
                        creditor,
                        callback_url: item.router_data.request.router_return_url.clone(),
                        flow_type: FlowType::FullHostedPages,
                    },
                })
            }
            _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
        }
    }
}

pub struct TokenioAuthType {
    pub(super) merchant_id: Secret<String>,
    pub(super) private_key: Secret<String>,
    pub(super) key_id: Secret<String>,
    pub(super) key_algorithm: CryptoAlgorithm,
}

#[derive(Debug, Deserialize, PartialEq)]
pub enum CryptoAlgorithm {
    RS256,
    ES256,
    #[serde(rename = "EdDSA")]
    EDDSA,
}

impl TryFrom<&str> for CryptoAlgorithm {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(s: &str) -> Result<Self, Self::Error> {
        match s.to_uppercase().as_str() {
            "RS256" | "rs256" => Ok(Self::RS256),
            "ES256" | "es256" => Ok(Self::ES256),
            "EDDSA" | "eddsa" | "EdDSA" => Ok(Self::EDDSA),
            _ => Err(errors::ConnectorError::InvalidConnectorConfig {
                config: "Unsupported key algorithm. Select from RS256, ES256, EdDSA",
            }
            .into()),
        }
    }
}
impl TryFrom<&ConnectorAuthType> for TokenioAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::MultiAuthKey {
                api_key,
                key1,
                api_secret,
                key2,
            } => Ok(Self {
                merchant_id: key1.to_owned(),
                private_key: api_secret.to_owned(),
                key_id: api_key.to_owned(),
                key_algorithm: CryptoAlgorithm::try_from(key2.clone().expose().as_str())?,
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Serialize, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenioApiWrapper {
    pub payment: PaymentResponse,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
#[serde(untagged)]
pub enum TokenioPaymentsResponse {
    Success(TokenioApiWrapper),
    Error(TokenioErrorResponse),
}

#[derive(Debug, Serialize, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentResponse {
    pub id: String,
    pub status: PaymentStatus,
    pub status_reason_information: Option<String>,
    pub authentication: Option<Authentication>,
    pub error_info: Option<ErrorInfo>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PaymentStatus {
    InitiationPending,
    InitiationPendingRedirectAuth,
    InitiationPendingRedirectAuthVerification,
    InitiationPendingRedirectHp,
    InitiationPendingRedemption,
    InitiationPendingRedemptionVerification,
    InitiationProcessing,
    InitiationCompleted,
    InitiationRejected,
    InitiationRejectedInsufficientFunds,
    InitiationFailed,
    InitiationDeclined,
    InitiationExpired,
    InitiationNoFinalStatusAvailable,
    SettlementInProgress,
    SettlementCompleted,
    SettlementIncomplete,
}

#[derive(Debug, Serialize, Clone, Deserialize)]
#[serde(untagged)]
pub enum Authentication {
    RedirectUrl {
        #[serde(rename = "redirectUrl")]
        redirect_url: String,
    },
}

#[derive(Debug, Serialize, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorInfo {
    pub http_error_code: i32,
    pub message: Option<String>,
    pub token_external_error: Option<bool>,
    pub token_trace_id: Option<String>,
}

impl From<TokenioPaymentsResponse> for common_enums::AttemptStatus {
    fn from(response: TokenioPaymentsResponse) -> Self {
        match response {
            TokenioPaymentsResponse::Success(wrapper) => match wrapper.payment.status {
                // Pending statuses - payment is still in progress
                PaymentStatus::InitiationPending
                | PaymentStatus::InitiationPendingRedirectAuth
                | PaymentStatus::InitiationPendingRedirectAuthVerification
                | PaymentStatus::InitiationPendingRedirectHp
                | PaymentStatus::InitiationPendingRedemption
                | PaymentStatus::InitiationPendingRedemptionVerification => {
                    Self::AuthenticationPending
                }

                // Success statuses
                PaymentStatus::SettlementCompleted => Self::Charged,

                // Settlement in progress - could map to different status based on business logic
                PaymentStatus::SettlementInProgress => Self::Pending,

                // Failure statuses
                PaymentStatus::InitiationRejected
                | PaymentStatus::InitiationFailed
                | PaymentStatus::InitiationExpired
                | PaymentStatus::InitiationRejectedInsufficientFunds
                | PaymentStatus::InitiationDeclined => Self::Failure,

                // Uncertain status
                PaymentStatus::InitiationCompleted
                | PaymentStatus::InitiationProcessing
                | PaymentStatus::InitiationNoFinalStatusAvailable
                | PaymentStatus::SettlementIncomplete => Self::Pending,
            },
            TokenioPaymentsResponse::Error(_) => Self::Failure,
        }
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, TokenioPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, TokenioPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let status = common_enums::AttemptStatus::from(item.response.clone());
        let response = match item.response {
            TokenioPaymentsResponse::Success(wrapper) => {
                let payment = wrapper.payment;
                if let common_enums::AttemptStatus::Failure = status {
                    Err(ErrorResponse {
                        code: payment
                            .error_info
                            .as_ref()
                            .map(|ei| ei.http_error_code.to_string())
                            .unwrap_or_else(|| NO_ERROR_CODE.to_string()),
                        message: payment
                            .error_info
                            .as_ref()
                            .and_then(|ei| ei.message.clone())
                            .or_else(|| payment.status_reason_information.clone())
                            .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
                        reason: Some(
                            payment
                                .error_info
                                .as_ref()
                                .and_then(|ei| ei.message.clone())
                                .or_else(|| payment.status_reason_information.clone())
                                .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
                        ),
                        status_code: item.http_code,
                        attempt_status: None,
                        connector_transaction_id: Some(payment.id.clone()),
                        network_advice_code: None,
                        network_decline_code: None,
                        network_error_message: None,
                        connector_metadata: None,
                    })
                } else {
                    Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::ConnectorTransactionId(payment.id.clone()),
                        redirection_data: Box::new(payment.authentication.as_ref().map(|auth| {
                            match auth {
                                Authentication::RedirectUrl { redirect_url } => {
                                    RedirectForm::Form {
                                        endpoint: redirect_url.to_string(),
                                        method: Method::Get,
                                        form_fields: HashMap::new(),
                                    }
                                }
                            }
                        })),
                        mandate_reference: Box::new(None),
                        connector_metadata: None,
                        network_txn_id: None,
                        connector_response_reference_id: None,
                        incremental_authorization_allowed: None,
                        charges: None,
                    })
                }
            }
            TokenioPaymentsResponse::Error(error_response) => Err(ErrorResponse {
                code: error_response.get_error_code(),
                message: error_response.get_message(),
                reason: Some(error_response.get_message()),
                status_code: item.http_code,
                attempt_status: None,
                connector_transaction_id: None,
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            }),
        };
        Ok(Self {
            status,
            response,
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize)]
pub struct TokenioRefundRequest {
    pub amount: StringMajorUnit,
}

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

#[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(Debug, Serialize, Clone, Deserialize)]
#[serde(untagged)]
pub enum TokenioErrorResponse {
    Json {
        #[serde(rename = "errorCode")]
        error_code: Option<String>,
        message: Option<String>,
    },
    Text(String),
}
impl TokenioErrorResponse {
    pub fn from_bytes(bytes: &[u8]) -> Self {
        // First try to parse as JSON
        if let Ok(json_response) = serde_json::from_slice::<Self>(bytes) {
            json_response
        } else {
            // If JSON parsing fails, treat as plain text
            let text = String::from_utf8_lossy(bytes).to_string();
            Self::Text(text)
        }
    }
    pub fn get_message(&self) -> String {
        match self {
            Self::Json {
                message,
                error_code,
            } => message
                .as_deref()
                .or(error_code.as_deref())
                .unwrap_or(NO_ERROR_MESSAGE)
                .to_string(),
            Self::Text(text) => text.clone(),
        }
    }

    pub fn get_error_code(&self) -> String {
        match self {
            Self::Json { error_code, .. } => {
                error_code.as_deref().unwrap_or(NO_ERROR_CODE).to_string()
            }
            Self::Text(_) => NO_ERROR_CODE.to_string(),
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TokenioWebhookEventType {
    PaymentStatusChanged,
    #[serde(other)]
    Unknown,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TokenioWebhookPaymentStatus {
    InitiationCompleted,
    PaymentCompleted,
    PaymentFailed,
    PaymentCancelled,
    InitiationRejected,
    InitiationProcessing,
    #[serde(other)]
    Unknown,
}

// Base webhook payload structure
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenioWebhookPayload {
    #[serde(rename = "eventType", skip_serializing_if = "Option::is_none")]
    pub event_type: Option<String>,
    pub id: String,
    #[serde(flatten)]
    pub event_data: TokenioWebhookEventData,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum TokenioWebhookEventData {
    PaymentV2 { payment: TokenioPaymentObjectV2 },
}

// Payment v2 structures
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenioPaymentObjectV2 {
    pub id: String,
    pub status: TokenioPaymentStatus,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TokenioPaymentStatus {
    InitiationCompleted,
    PaymentCompleted,
    PaymentFailed,
    PaymentCancelled,
    InitiationRejected,
    InitiationProcessing,
    InitiationPendingRedirectHp,
    #[serde(other)]
    Other,
}


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

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

use common_enums::enums;
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
use common_utils::types::{ConnectorTransactionId, FloatMajorUnitForConnector};
use common_utils::{
    errors::CustomResult,
    ext_traits::ByteSliceExt,
    id_type,
    types::{FloatMajorUnit, StringMinorUnit},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::router_data::ConnectorAuthType;
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
use hyperswitch_domain_models::{
    router_data_v2::flow_common_types as recovery_flow_common_types,
    router_flow_types::revenue_recovery as recovery_router_flows,
    router_request_types::revenue_recovery as recovery_request_types,
    router_response_types::revenue_recovery as recovery_response_types,
    types as recovery_router_data_types,
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
use crate::{types::ResponseRouterDataV2, utils};

pub struct RecurlyRouterData<T> {
    pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
    pub router_data: T,
}

impl<T> From<(StringMinorUnit, T)> for RecurlyRouterData<T> {
    fn from((amount, item): (StringMinorUnit, T)) -> Self {
        //Todo :  use utils to convert the amount to the type of amount that a connector accepts
        Self {
            amount,
            router_data: item,
        }
    }
}

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

impl TryFrom<&ConnectorAuthType> for RecurlyAuthType {
    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 {
                api_key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

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

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RecurlyWebhookBody {
    // Transaction uuid
    pub uuid: String,
    pub event_type: RecurlyPaymentEventType,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum RecurlyPaymentEventType {
    #[serde(rename = "succeeded")]
    PaymentSucceeded,
    #[serde(rename = "failed")]
    PaymentFailed,
}

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

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum RecurlyChargeStatus {
    #[serde(rename = "success")]
    Succeeded,
    #[serde(rename = "declined")]
    Failed,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum RecurlyFundingTypes {
    Credit,
    Debit,
}

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

#[derive(Debug, Serialize, Deserialize)]
pub struct RecurlyRecoveryDetailsData {
    pub amount: FloatMajorUnit,
    pub currency: common_enums::Currency,
    pub id: String,
    pub status_code: Option<String>,
    pub status_message: Option<String>,
    pub account: Account,
    pub invoice: Invoice,
    pub payment_method: PaymentMethod,
    pub payment_gateway: PaymentGateway,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
    pub status: RecurlyChargeStatus,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PaymentMethod {
    pub gateway_token: String,
    pub funding_source: RecurlyFundingTypes,
    pub object: RecurlyPaymentObject,
    pub card_type: common_enums::CardNetwork,
    pub first_six: String,
}

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

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

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

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl
    TryFrom<
        ResponseRouterDataV2<
            recovery_router_flows::BillingConnectorPaymentsSync,
            RecurlyRecoveryDetailsData,
            recovery_flow_common_types::BillingConnectorPaymentsSyncFlowData,
            recovery_request_types::BillingConnectorPaymentsSyncRequest,
            recovery_response_types::BillingConnectorPaymentsSyncResponse,
        >,
    > for recovery_router_data_types::BillingConnectorPaymentsSyncRouterDataV2
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterDataV2<
            recovery_router_flows::BillingConnectorPaymentsSync,
            RecurlyRecoveryDetailsData,
            recovery_flow_common_types::BillingConnectorPaymentsSyncFlowData,
            recovery_request_types::BillingConnectorPaymentsSyncRequest,
            recovery_response_types::BillingConnectorPaymentsSyncResponse,
        >,
    ) -> Result<Self, Self::Error> {
        let merchant_reference_id =
            id_type::PaymentReferenceId::from_str(&item.response.invoice.id)
                .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        let connector_transaction_id = Some(ConnectorTransactionId::from(item.response.id));

        Ok(Self {
            response: Ok(
                recovery_response_types::BillingConnectorPaymentsSyncResponse {
                    status: item.response.status.into(),
                    amount: utils::convert_back_amount_to_minor_units(
                        &FloatMajorUnitForConnector,
                        item.response.amount,
                        item.response.currency,
                    )?,
                    currency: item.response.currency,
                    merchant_reference_id,
                    connector_account_reference_id: item.response.payment_gateway.id,
                    connector_transaction_id,
                    error_code: item.response.status_code,
                    error_message: item.response.status_message,
                    processor_payment_method_token: item.response.payment_method.gateway_token,
                    connector_customer_id: item.response.account.id,
                    transaction_created_at: Some(item.response.created_at),
                    payment_method_sub_type: common_enums::PaymentMethodType::from(
                        item.response.payment_method.funding_source,
                    ),
                    payment_method_type: common_enums::PaymentMethod::from(
                        item.response.payment_method.object,
                    ),
                    // This none because this field is specific to stripebilling.
                    charge_id: None,
                    // Need to populate these card info field
                    card_info: api_models::payments::AdditionalCardInfo {
                        card_network: Some(item.response.payment_method.card_type),
                        card_isin: Some(item.response.payment_method.first_six),
                        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,
                    },
                },
            ),
            ..item.data
        })
    }
}

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl From<RecurlyChargeStatus> for enums::AttemptStatus {
    fn from(status: RecurlyChargeStatus) -> Self {
        match status {
            RecurlyChargeStatus::Succeeded => Self::Charged,
            RecurlyChargeStatus::Failed => Self::Failure,
        }
    }
}
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl From<RecurlyFundingTypes> for common_enums::PaymentMethodType {
    fn from(funding: RecurlyFundingTypes) -> Self {
        match funding {
            RecurlyFundingTypes::Credit => Self::Credit,
            RecurlyFundingTypes::Debit => Self::Debit,
        }
    }
}
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl From<RecurlyPaymentObject> for common_enums::PaymentMethod {
    fn from(funding: RecurlyPaymentObject) -> Self {
        match funding {
            RecurlyPaymentObject::CreditCard => Self::Card,
        }
    }
}

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

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl TryFrom<enums::AttemptStatus> for RecurlyRecordStatus {
    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: "recurly",
            }
            .into()),
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RecurlyRecordBackResponse {
    // Invoice id
    pub id: id_type::PaymentReferenceId,
}

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl
    TryFrom<
        ResponseRouterDataV2<
            recovery_router_flows::InvoiceRecordBack,
            RecurlyRecordBackResponse,
            recovery_flow_common_types::InvoiceRecordBackData,
            recovery_request_types::InvoiceRecordBackRequest,
            recovery_response_types::InvoiceRecordBackResponse,
        >,
    > for recovery_router_data_types::InvoiceRecordBackRouterDataV2
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterDataV2<
            recovery_router_flows::InvoiceRecordBack,
            RecurlyRecordBackResponse,
            recovery_flow_common_types::InvoiceRecordBackData,
            recovery_request_types::InvoiceRecordBackRequest,
            recovery_response_types::InvoiceRecordBackResponse,
        >,
    ) -> Result<Self, Self::Error> {
        let merchant_reference_id = item.response.id;
        Ok(Self {
            response: Ok(recovery_response_types::InvoiceRecordBackResponse {
                merchant_reference_id,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RecurlyInvoiceSyncResponse {
    pub id: String,
    pub total: FloatMajorUnit,
    pub currency: common_enums::Currency,
    pub address: Option<RecurlyInvoiceBillingAddress>,
    pub line_items: Vec<RecurlyLineItems>,
    pub transactions: Vec<RecurlyInvoiceTransactionsStatus>,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RecurlyInvoiceBillingAddress {
    pub street1: Option<Secret<String>>,
    pub street2: Option<Secret<String>>,
    pub region: Option<Secret<String>>,
    pub country: Option<enums::CountryAlpha2>,
    pub postal_code: Option<Secret<String>>,
    pub city: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RecurlyLineItems {
    #[serde(rename = "type")]
    pub invoice_type: RecurlyInvoiceLineItemType,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub start_date: PrimitiveDateTime,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub end_date: PrimitiveDateTime,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum RecurlyInvoiceLineItemType {
    Credit,
    Charge,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct RecurlyInvoiceTransactionsStatus {
    pub status: String,
}

#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl
    TryFrom<
        ResponseRouterDataV2<
            recovery_router_flows::BillingConnectorInvoiceSync,
            RecurlyInvoiceSyncResponse,
            recovery_flow_common_types::BillingConnectorInvoiceSyncFlowData,
            recovery_request_types::BillingConnectorInvoiceSyncRequest,
            recovery_response_types::BillingConnectorInvoiceSyncResponse,
        >,
    > for recovery_router_data_types::BillingConnectorInvoiceSyncRouterDataV2
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterDataV2<
            recovery_router_flows::BillingConnectorInvoiceSync,
            RecurlyInvoiceSyncResponse,
            recovery_flow_common_types::BillingConnectorInvoiceSyncFlowData,
            recovery_request_types::BillingConnectorInvoiceSyncRequest,
            recovery_response_types::BillingConnectorInvoiceSyncResponse,
        >,
    ) -> Result<Self, Self::Error> {
        #[allow(clippy::as_conversions)]
        // No of retries never exceeds u16 in recurly. So its better to suppress the clippy warning
        let retry_count = item.response.transactions.len() as u16;
        let merchant_reference_id = id_type::PaymentReferenceId::from_str(&item.response.id)
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        Ok(Self {
            response: Ok(
                recovery_response_types::BillingConnectorInvoiceSyncResponse {
                    amount: utils::convert_back_amount_to_minor_units(
                        &FloatMajorUnitForConnector,
                        item.response.total,
                        item.response.currency,
                    )?,
                    currency: item.response.currency,
                    merchant_reference_id,
                    retry_count: Some(retry_count),
                    billing_address: Some(api_models::payments::Address {
                        address: Some(api_models::payments::AddressDetails {
                            city: item
                                .response
                                .address
                                .clone()
                                .and_then(|address| address.city),
                            state: item
                                .response
                                .address
                                .clone()
                                .and_then(|address| address.region),
                            country: item
                                .response
                                .address
                                .clone()
                                .and_then(|address| address.country),
                            line1: item
                                .response
                                .address
                                .clone()
                                .and_then(|address| address.street1),
                            line2: item
                                .response
                                .address
                                .clone()
                                .and_then(|address| address.street2),
                            line3: None,
                            zip: item
                                .response
                                .address
                                .clone()
                                .and_then(|address| address.postal_code),
                            first_name: None,
                            last_name: None,
                            origin_zip: None,
                        }),
                        phone: None,
                        email: None,
                    }),
                    created_at: item.response.line_items.first().map(|line| line.start_date),
                    ends_at: item.response.line_items.first().map(|line| line.end_date),
                },
            ),
            ..item.data
        })
    }
}