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

use common_enums::{enums, Currency};
use common_utils::types::{MinorUnit, StringMajorUnit};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{GiftCardData, PaymentMethodData},
    router_data::{
        AccessToken, ConnectorAuthType, ErrorResponse, PaymentMethodBalance, RouterData,
    },
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, PreprocessingResponseId, RefundsResponseData},
    types::{PaymentsAuthorizeRouterData, PaymentsPreProcessingRouterData, RefundsRouterData},
};
use hyperswitch_interfaces::{consts::NO_ERROR_MESSAGE, errors};
use masking::Secret;
use serde::{Deserialize, Serialize};

use crate::types::{
    PaymentsPreprocessingResponseRouterData, RefundsResponseRouterData, ResponseRouterData,
};

pub struct BlackhawknetworkRouterData<T> {
    pub amount: StringMajorUnit,
    pub router_data: T,
}

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

#[derive(Debug, Clone)]
pub struct BlackhawknetworkAuthType {
    pub(super) client_id: Secret<String>,
    pub(super) client_secret: Secret<String>,
    pub(super) product_line_id: Secret<String>,
}

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

    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::SignatureKey {
                api_key,
                key1,
                api_secret,
            } => Ok(Self {
                client_id: api_key.clone(),
                client_secret: api_secret.clone(),
                product_line_id: key1.clone(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType)
                .attach_printable("Unsupported authentication type for Blackhawk Network"),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BlackhawknetworkAccessTokenRequest {
    pub grant_type: String,
    pub client_id: Secret<String>,
    pub client_secret: Secret<String>,
    pub scope: String,
}
impl<F, T> TryFrom<ResponseRouterData<F, BlackhawknetworkTokenResponse, T, AccessToken>>
    for RouterData<F, T, AccessToken>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, BlackhawknetworkTokenResponse, T, AccessToken>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(AccessToken {
                token: item.response.access_token,
                expires: item.response.expires_in,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BlackhawknetworkVerifyAccountRequest {
    pub account_number: Secret<String>,
    pub product_line_id: Secret<String>,
    pub account_type: AccountType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pin: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cvv2: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expiration_date: Option<Secret<String>>,
}
impl TryFrom<&PaymentsPreProcessingRouterData> for BlackhawknetworkVerifyAccountRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsPreProcessingRouterData) -> Result<Self, Self::Error> {
        let auth = BlackhawknetworkAuthType::try_from(&item.connector_auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;

        let gift_card_data = match &item.request.payment_method_data {
            Some(PaymentMethodData::GiftCard(gc)) => match gc.as_ref() {
                GiftCardData::BhnCardNetwork(data) => data,
                _ => {
                    return Err(errors::ConnectorError::FlowNotSupported {
                        flow: "Balance".to_string(),
                        connector: "BlackHawkNetwork".to_string(),
                    }
                    .into())
                }
            },
            _ => {
                return Err(errors::ConnectorError::FlowNotSupported {
                    flow: "Balance".to_string(),
                    connector: "BlackHawkNetwork".to_string(),
                }
                .into())
            }
        };

        Ok(Self {
            account_number: gift_card_data.account_number.clone(),
            product_line_id: auth.product_line_id,
            account_type: AccountType::GiftCard,
            pin: gift_card_data.pin.clone(),
            cvv2: gift_card_data.cvv2.clone(),
            expiration_date: gift_card_data.expiration_date.clone().map(Secret::new),
        })
    }
}

impl TryFrom<PaymentsPreprocessingResponseRouterData<BlackhawknetworkVerifyAccountResponse>>
    for PaymentsPreProcessingRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsPreprocessingResponseRouterData<BlackhawknetworkVerifyAccountResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::PreProcessingResponse {
                pre_processing_id: PreprocessingResponseId::PreProcessingId(
                    item.response.account.entity_id,
                ),
                connector_metadata: None,
                session_token: None,
                connector_response_reference_id: None,
            }),
            payment_method_balance: Some(PaymentMethodBalance {
                currency: item.response.account.currency,
                amount: item.response.account.balance,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BlackhawknetworkTokenResponse {
    pub access_token: Secret<String>,
    pub expires_in: i64,
}
#[derive(Serialize, Debug)]
pub struct BlackhawknetworkPaymentsRequest {
    pub account_id: String,
    pub amount: StringMajorUnit,
    pub currency: Currency,
}

impl TryFrom<&BlackhawknetworkRouterData<&PaymentsAuthorizeRouterData>>
    for BlackhawknetworkPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: &BlackhawknetworkRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        match &item.router_data.request.payment_method_data {
            PaymentMethodData::GiftCard(_gift_card) => {
                let account_id = item
                    .router_data
                    .preprocessing_id
                    .to_owned()
                    .ok_or_else(|| {
                        errors::ConnectorError::MissingConnectorRelatedTransactionID {
                            id: "entity_id".to_string(),
                        }
                    })?;

                Ok(Self {
                    account_id,
                    amount: item.amount.clone(),
                    currency: item.router_data.request.currency,
                })
            }
            _ => Err(error_stack::Report::new(
                errors::ConnectorError::NotImplemented("Non-gift card payment method".to_string()),
            )),
        }
    }
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum BlackhawknetworkRedeemResponse {
    Success(BlackhawknetworkPaymentsResponse),
    Error(BlackhawknetworkErrorResponse),
}

#[derive(Debug, Deserialize, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BlackhawknetworkPaymentsResponse {
    pub id: String,
    #[serde(rename = "transactionStatus")]
    pub status: BlackhawknetworkAttemptStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum BlackhawknetworkAttemptStatus {
    Approved,
    Declined,
    Pending,
}

impl<F, T> TryFrom<ResponseRouterData<F, BlackhawknetworkRedeemResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: ResponseRouterData<F, BlackhawknetworkRedeemResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            BlackhawknetworkRedeemResponse::Success(response) => Ok(Self {
                status: match response.status {
                    BlackhawknetworkAttemptStatus::Approved => enums::AttemptStatus::Charged,
                    BlackhawknetworkAttemptStatus::Declined => enums::AttemptStatus::Failure,
                    BlackhawknetworkAttemptStatus::Pending => enums::AttemptStatus::Pending,
                },
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(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
            }),
            BlackhawknetworkRedeemResponse::Error(error_response) => Ok(Self {
                response: Err(ErrorResponse {
                    status_code: item.http_code,
                    code: error_response.error.clone(),
                    message: error_response
                        .error_description
                        .clone()
                        .unwrap_or(NO_ERROR_MESSAGE.to_string()),
                    reason: error_response.error_description,
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                }),
                ..item.data
            }),
        }
    }
}

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

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

#[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(Debug, Serialize, Deserialize, Clone, Copy)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AccountType {
    CreditCard,
    GiftCard,
    LoyaltyCard,
    PhoneCard,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AccountStatus {
    New,
    Activated,
    Closed,
}

impl From<AccountStatus> for common_enums::AttemptStatus {
    fn from(item: AccountStatus) -> Self {
        match item {
            AccountStatus::New | AccountStatus::Activated => Self::Pending,
            AccountStatus::Closed => Self::Failure,
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct AccountInformation {
    pub entity_id: String,
    pub balance: MinorUnit,
    pub currency: Currency,
    pub status: AccountStatus,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct BlackhawknetworkVerifyAccountResponse {
    account: AccountInformation,
}

#[derive(Debug, Deserialize, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BlackhawknetworkErrorResponse {
    pub error: String,
    pub error_description: Option<String>,
}


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

use base64::Engine;
use cards::CardNumber;
use common_enums::{enums, AttemptStatus};
use common_utils::{consts, errors::CustomResult, request::Method};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{
        ApplePayWalletData, BankRedirectData, Card, PaymentMethodData, WalletData,
    },
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{
        MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        RefundsRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use url::Url;

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{
        self, CardData, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, WalletData as _,
    },
};

pub mod nexinets_constants {
    pub const MAX_PAYMENT_REFERENCE_ID_LENGTH: usize = 30;
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsPaymentsRequest {
    initial_amount: i64,
    currency: enums::Currency,
    channel: NexinetsChannel,
    product: NexinetsProduct,
    payment: Option<NexinetsPaymentDetails>,
    #[serde(rename = "async")]
    nexinets_async: NexinetsAsyncDetails,
    merchant_order_id: Option<String>,
}

#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexinetsChannel {
    #[default]
    Ecom,
}

#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum NexinetsProduct {
    #[default]
    Creditcard,
    Paypal,
    Giropay,
    Sofort,
    Eps,
    Ideal,
    Applepay,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum NexinetsPaymentDetails {
    Card(Box<NexiCardDetails>),
    Wallet(Box<NexinetsWalletDetails>),
    BankRedirects(Box<NexinetsBankRedirects>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexiCardDetails {
    #[serde(flatten)]
    card_data: CardDataDetails,
    cof_contract: Option<CofContract>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum CardDataDetails {
    CardDetails(Box<CardDetails>),
    PaymentInstrument(Box<PaymentInstrument>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardDetails {
    card_number: CardNumber,
    expiry_month: Secret<String>,
    expiry_year: Secret<String>,
    verification: Secret<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentInstrument {
    payment_instrument_id: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
pub struct CofContract {
    #[serde(rename = "type")]
    recurring_type: RecurringType,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RecurringType {
    Unscheduled,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsBankRedirects {
    bic: Option<NexinetsBIC>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsAsyncDetails {
    pub success_url: Option<String>,
    pub cancel_url: Option<String>,
    pub failure_url: Option<String>,
}

#[derive(Debug, Serialize)]
pub enum NexinetsBIC {
    #[serde(rename = "ABNANL2A")]
    AbnAmro,
    #[serde(rename = "ASNBNL21")]
    AsnBank,
    #[serde(rename = "BUNQNL2A")]
    Bunq,
    #[serde(rename = "INGBNL2A")]
    Ing,
    #[serde(rename = "KNABNL2H")]
    Knab,
    #[serde(rename = "RABONL2U")]
    Rabobank,
    #[serde(rename = "RBRBNL21")]
    Regiobank,
    #[serde(rename = "SNSBNL2A")]
    SnsBank,
    #[serde(rename = "TRIONL2U")]
    TriodosBank,
    #[serde(rename = "FVLBNL22")]
    VanLanschot,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum NexinetsWalletDetails {
    ApplePayToken(Box<ApplePayDetails>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplePayDetails {
    payment_data: serde_json::Value,
    payment_method: ApplepayPaymentMethod,
    transaction_identifier: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApplepayPaymentMethod {
    display_name: String,
    network: String,
    #[serde(rename = "type")]
    token_type: String,
}

impl TryFrom<&PaymentsAuthorizeRouterData> for NexinetsPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
        let return_url = item.request.router_return_url.clone();
        let nexinets_async = NexinetsAsyncDetails {
            success_url: return_url.clone(),
            cancel_url: return_url.clone(),
            failure_url: return_url,
        };
        let (payment, product) = get_payment_details_and_product(item)?;
        let merchant_order_id = match item.payment_method {
            // Merchant order id is sent only in case of card payment
            enums::PaymentMethod::Card => {
                if item.connector_request_reference_id.len()
                    <= nexinets_constants::MAX_PAYMENT_REFERENCE_ID_LENGTH
                {
                    Ok(Some(item.connector_request_reference_id.clone()))
                } else {
                    Err(errors::ConnectorError::MaxFieldLengthViolated {
                        connector: "Nexinets".to_string(),
                        field_name: "merchant_order_id".to_string(),
                        max_length: nexinets_constants::MAX_PAYMENT_REFERENCE_ID_LENGTH,
                        received_length: item.connector_request_reference_id.len(),
                    })
                }
            }?,
            _ => None,
        };
        Ok(Self {
            initial_amount: item.request.amount,
            currency: item.request.currency,
            channel: NexinetsChannel::Ecom,
            product,
            payment,
            nexinets_async,
            merchant_order_id,
        })
    }
}

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

impl TryFrom<&ConnectorAuthType> for NexinetsAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::BodyKey { api_key, key1 } => {
                let auth_key = format!("{}:{}", key1.peek(), api_key.peek());
                let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key));
                Ok(Self {
                    api_key: Secret::new(auth_header),
                })
            }
            _ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
        }
    }
}
// PaymentsResponse
#[derive(Debug, Deserialize, Clone, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexinetsPaymentStatus {
    Success,
    Pending,
    Ok,
    Failure,
    Declined,
    InProgress,
    Expired,
    Aborted,
}

fn get_status(status: NexinetsPaymentStatus, method: NexinetsTransactionType) -> AttemptStatus {
    match status {
        NexinetsPaymentStatus::Success => match method {
            NexinetsTransactionType::Preauth => AttemptStatus::Authorized,
            NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => {
                AttemptStatus::Charged
            }
            NexinetsTransactionType::Cancel => AttemptStatus::Voided,
        },
        NexinetsPaymentStatus::Declined
        | NexinetsPaymentStatus::Failure
        | NexinetsPaymentStatus::Expired
        | NexinetsPaymentStatus::Aborted => match method {
            NexinetsTransactionType::Preauth => AttemptStatus::AuthorizationFailed,
            NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => {
                AttemptStatus::CaptureFailed
            }
            NexinetsTransactionType::Cancel => AttemptStatus::VoidFailed,
        },
        NexinetsPaymentStatus::Ok => match method {
            NexinetsTransactionType::Preauth => AttemptStatus::Authorized,
            _ => AttemptStatus::Pending,
        },
        NexinetsPaymentStatus::Pending => AttemptStatus::AuthenticationPending,
        NexinetsPaymentStatus::InProgress => AttemptStatus::Pending,
    }
}

impl TryFrom<&enums::BankNames> for NexinetsBIC {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(bank: &enums::BankNames) -> Result<Self, Self::Error> {
        match bank {
            enums::BankNames::AbnAmro => Ok(Self::AbnAmro),
            enums::BankNames::AsnBank => Ok(Self::AsnBank),
            enums::BankNames::Bunq => Ok(Self::Bunq),
            enums::BankNames::Ing => Ok(Self::Ing),
            enums::BankNames::Knab => Ok(Self::Knab),
            enums::BankNames::Rabobank => Ok(Self::Rabobank),
            enums::BankNames::Regiobank => Ok(Self::Regiobank),
            enums::BankNames::SnsBank => Ok(Self::SnsBank),
            enums::BankNames::TriodosBank => Ok(Self::TriodosBank),
            enums::BankNames::VanLanschot => Ok(Self::VanLanschot),
            _ => Err(errors::ConnectorError::FlowNotSupported {
                flow: bank.to_string(),
                connector: "Nexinets".to_string(),
            }
            .into()),
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsPreAuthOrDebitResponse {
    order_id: String,
    transaction_type: NexinetsTransactionType,
    transactions: Vec<NexinetsTransaction>,
    payment_instrument: PaymentInstrument,
    redirect_url: Option<Url>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsTransaction {
    pub transaction_id: String,
    #[serde(rename = "type")]
    pub transaction_type: NexinetsTransactionType,
    pub currency: enums::Currency,
    pub status: NexinetsPaymentStatus,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NexinetsTransactionType {
    Preauth,
    Debit,
    Capture,
    Cancel,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NexinetsPaymentsMetadata {
    pub transaction_id: Option<String>,
    pub order_id: Option<String>,
    pub psync_flow: NexinetsTransactionType,
}

impl<F, T> TryFrom<ResponseRouterData<F, NexinetsPreAuthOrDebitResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, NexinetsPreAuthOrDebitResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let transaction = match item.response.transactions.first() {
            Some(order) => order,
            _ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
        };
        let connector_metadata = serde_json::to_value(NexinetsPaymentsMetadata {
            transaction_id: Some(transaction.transaction_id.clone()),
            order_id: Some(item.response.order_id.clone()),
            psync_flow: item.response.transaction_type.clone(),
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)?;
        let redirection_data = item
            .response
            .redirect_url
            .map(|url| RedirectForm::from((url, Method::Get)));
        let resource_id = match item.response.transaction_type.clone() {
            NexinetsTransactionType::Preauth => ResponseId::NoResponseId,
            NexinetsTransactionType::Debit => {
                ResponseId::ConnectorTransactionId(transaction.transaction_id.clone())
            }
            _ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
        };
        let mandate_reference = item
            .response
            .payment_instrument
            .payment_instrument_id
            .map(|id| MandateReference {
                connector_mandate_id: Some(id.expose()),
                payment_method_id: None,
                mandate_metadata: None,
                connector_mandate_request_reference_id: None,
            });
        Ok(Self {
            status: get_status(transaction.status.clone(), item.response.transaction_type),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id,
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(mandate_reference),
                connector_metadata: Some(connector_metadata),
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.order_id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsCaptureOrVoidRequest {
    pub initial_amount: i64,
    pub currency: enums::Currency,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsOrder {
    pub order_id: String,
}

impl TryFrom<&PaymentsCaptureRouterData> for NexinetsCaptureOrVoidRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            initial_amount: item.request.amount_to_capture,
            currency: item.request.currency,
        })
    }
}

impl TryFrom<&PaymentsCancelRouterData> for NexinetsCaptureOrVoidRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            initial_amount: item.request.get_amount()?,
            currency: item.request.get_currency()?,
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsPaymentResponse {
    pub transaction_id: String,
    pub status: NexinetsPaymentStatus,
    pub order: NexinetsOrder,
    #[serde(rename = "type")]
    pub transaction_type: NexinetsTransactionType,
}

impl<F, T> TryFrom<ResponseRouterData<F, NexinetsPaymentResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, NexinetsPaymentResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let transaction_id = Some(item.response.transaction_id.clone());
        let connector_metadata = serde_json::to_value(NexinetsPaymentsMetadata {
            transaction_id,
            order_id: Some(item.response.order.order_id.clone()),
            psync_flow: item.response.transaction_type.clone(),
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)?;
        let resource_id = match item.response.transaction_type.clone() {
            NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => {
                ResponseId::ConnectorTransactionId(item.response.transaction_id)
            }
            _ => ResponseId::NoResponseId,
        };
        Ok(Self {
            status: get_status(item.response.status, item.response.transaction_type),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id,
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: Some(connector_metadata),
                network_txn_id: None,
                connector_response_reference_id: Some(item.response.order.order_id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

// REFUND :
// Type definition for RefundRequest
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsRefundRequest {
    pub initial_amount: i64,
    pub currency: enums::Currency,
}

impl<F> TryFrom<&RefundsRouterData<F>> for NexinetsRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &RefundsRouterData<F>) -> Result<Self, Self::Error> {
        Ok(Self {
            initial_amount: item.request.refund_amount,
            currency: item.request.currency,
        })
    }
}

// Type definition for Refund Response
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NexinetsRefundResponse {
    pub transaction_id: String,
    pub status: RefundStatus,
    pub order: NexinetsOrder,
    #[serde(rename = "type")]
    pub transaction_type: RefundType,
}

#[allow(dead_code)]
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatus {
    Success,
    Ok,
    Failure,
    Declined,
    InProgress,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundType {
    Refund,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Success => Self::Success,
            RefundStatus::Failure | RefundStatus::Declined => Self::Failure,
            RefundStatus::InProgress | RefundStatus::Ok => Self::Pending,
        }
    }
}

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

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

#[derive(Debug, Deserialize, Serialize)]
pub struct NexinetsErrorResponse {
    pub status: u16,
    pub code: u16,
    pub message: String,
    pub errors: Vec<OrderErrorDetails>,
}

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

fn get_payment_details_and_product(
    item: &PaymentsAuthorizeRouterData,
) -> Result<
    (Option<NexinetsPaymentDetails>, NexinetsProduct),
    error_stack::Report<errors::ConnectorError>,
> {
    match &item.request.payment_method_data {
        PaymentMethodData::Card(card) => Ok((
            Some(get_card_data(item, card)?),
            NexinetsProduct::Creditcard,
        )),
        PaymentMethodData::Wallet(wallet) => Ok(get_wallet_details(wallet)?),
        PaymentMethodData::BankRedirect(bank_redirect) => match bank_redirect {
            BankRedirectData::Eps { .. } => Ok((None, NexinetsProduct::Eps)),
            BankRedirectData::Giropay { .. } => Ok((None, NexinetsProduct::Giropay)),
            BankRedirectData::Ideal { bank_name, .. } => Ok((
                Some(NexinetsPaymentDetails::BankRedirects(Box::new(
                    NexinetsBankRedirects {
                        bic: bank_name
                            .map(|bank_name| NexinetsBIC::try_from(&bank_name))
                            .transpose()?,
                    },
                ))),
                NexinetsProduct::Ideal,
            )),
            BankRedirectData::Sofort { .. } => Ok((None, NexinetsProduct::Sofort)),
            BankRedirectData::BancontactCard { .. }
            | BankRedirectData::Blik { .. }
            | BankRedirectData::Bizum { .. }
            | BankRedirectData::Eft { .. }
            | BankRedirectData::Interac { .. }
            | BankRedirectData::OnlineBankingCzechRepublic { .. }
            | BankRedirectData::OnlineBankingFinland { .. }
            | BankRedirectData::OnlineBankingPoland { .. }
            | BankRedirectData::OnlineBankingSlovakia { .. }
            | BankRedirectData::OpenBankingUk { .. }
            | BankRedirectData::Przelewy24 { .. }
            | BankRedirectData::Trustly { .. }
            | BankRedirectData::OnlineBankingFpx { .. }
            | BankRedirectData::OnlineBankingThailand { .. }
            | BankRedirectData::LocalBankRedirect {} => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("nexinets"),
                ))?
            }
        },
        PaymentMethodData::CardRedirect(_)
        | PaymentMethodData::PayLater(_)
        | PaymentMethodData::BankDebit(_)
        | PaymentMethodData::BankTransfer(_)
        | PaymentMethodData::Crypto(_)
        | PaymentMethodData::MandatePayment
        | PaymentMethodData::Reward
        | PaymentMethodData::RealTimePayment(_)
        | PaymentMethodData::MobilePayment(_)
        | PaymentMethodData::Upi(_)
        | PaymentMethodData::Voucher(_)
        | PaymentMethodData::GiftCard(_)
        | PaymentMethodData::OpenBanking(_)
        | PaymentMethodData::CardToken(_)
        | PaymentMethodData::NetworkToken(_)
        | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
            Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("nexinets"),
            ))?
        }
    }
}

fn get_card_data(
    item: &PaymentsAuthorizeRouterData,
    card: &Card,
) -> Result<NexinetsPaymentDetails, errors::ConnectorError> {
    let (card_data, cof_contract) = match item.request.is_mandate_payment() {
        true => {
            let card_data = match item.request.off_session {
                Some(true) => CardDataDetails::PaymentInstrument(Box::new(PaymentInstrument {
                    payment_instrument_id: item.request.connector_mandate_id().map(Secret::new),
                })),
                _ => CardDataDetails::CardDetails(Box::new(get_card_details(card)?)),
            };
            let cof_contract = Some(CofContract {
                recurring_type: RecurringType::Unscheduled,
            });
            (card_data, cof_contract)
        }
        false => (
            CardDataDetails::CardDetails(Box::new(get_card_details(card)?)),
            None,
        ),
    };
    Ok(NexinetsPaymentDetails::Card(Box::new(NexiCardDetails {
        card_data,
        cof_contract,
    })))
}

fn get_applepay_details(
    wallet_data: &WalletData,
    applepay_data: &ApplePayWalletData,
) -> CustomResult<ApplePayDetails, errors::ConnectorError> {
    let payment_data = WalletData::get_wallet_token_as_json(wallet_data, "Apple Pay".to_string())?;
    Ok(ApplePayDetails {
        payment_data,
        payment_method: ApplepayPaymentMethod {
            display_name: applepay_data.payment_method.display_name.to_owned(),
            network: applepay_data.payment_method.network.to_owned(),
            token_type: applepay_data.payment_method.pm_type.to_owned(),
        },
        transaction_identifier: applepay_data.transaction_identifier.to_owned(),
    })
}

fn get_card_details(req_card: &Card) -> Result<CardDetails, errors::ConnectorError> {
    Ok(CardDetails {
        card_number: req_card.card_number.clone(),
        expiry_month: req_card.card_exp_month.clone(),
        expiry_year: req_card.get_card_expiry_year_2_digit()?,
        verification: req_card.card_cvc.clone(),
    })
}

fn get_wallet_details(
    wallet: &WalletData,
) -> Result<
    (Option<NexinetsPaymentDetails>, NexinetsProduct),
    error_stack::Report<errors::ConnectorError>,
> {
    match wallet {
        WalletData::PaypalRedirect(_) => Ok((None, NexinetsProduct::Paypal)),
        WalletData::ApplePay(applepay_data) => Ok((
            Some(NexinetsPaymentDetails::Wallet(Box::new(
                NexinetsWalletDetails::ApplePayToken(Box::new(get_applepay_details(
                    wallet,
                    applepay_data,
                )?)),
            ))),
            NexinetsProduct::Applepay,
        )),
        WalletData::AliPayQr(_)
        | WalletData::AliPayRedirect(_)
        | WalletData::AliPayHkRedirect(_)
        | WalletData::AmazonPay(_)
        | WalletData::AmazonPayRedirect(_)
        | WalletData::Paysera(_)
        | WalletData::Skrill(_)
        | WalletData::BluecodeRedirect {}
        | WalletData::MomoRedirect(_)
        | WalletData::KakaoPayRedirect(_)
        | WalletData::GoPayRedirect(_)
        | WalletData::GcashRedirect(_)
        | WalletData::ApplePayRedirect(_)
        | WalletData::ApplePayThirdPartySdk(_)
        | WalletData::DanaRedirect { .. }
        | WalletData::GooglePay(_)
        | WalletData::GooglePayRedirect(_)
        | WalletData::GooglePayThirdPartySdk(_)
        | WalletData::MbWayRedirect(_)
        | WalletData::MobilePayRedirect(_)
        | WalletData::PaypalSdk(_)
        | WalletData::Paze(_)
        | WalletData::SamsungPay(_)
        | WalletData::TwintRedirect { .. }
        | WalletData::VippsRedirect { .. }
        | WalletData::TouchNGoRedirect(_)
        | WalletData::WeChatPayRedirect(_)
        | WalletData::WeChatPayQr(_)
        | WalletData::CashappQr(_)
        | WalletData::SwishQr(_)
        | WalletData::Mifinity(_)
        | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented(
            utils::get_unimplemented_payment_method_error_message("nexinets"),
        ))?,
    }
}

pub fn get_order_id(
    meta: &NexinetsPaymentsMetadata,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
    let order_id = meta.order_id.clone().ok_or(
        errors::ConnectorError::MissingConnectorRelatedTransactionID {
            id: "order_id".to_string(),
        },
    )?;
    Ok(order_id)
}

pub fn get_transaction_id(
    meta: &NexinetsPaymentsMetadata,
) -> Result<String, error_stack::Report<errors::ConnectorError>> {
    let transaction_id = meta.transaction_id.clone().ok_or(
        errors::ConnectorError::MissingConnectorRelatedTransactionID {
            id: "transaction_id".to_string(),
        },
    )?;
    Ok(transaction_id)
}


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

use common_utils::pii::Email;
use hyperswitch_domain_models::{
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::vault::ExternalVaultCreateFlow,
    router_response_types::{
        ConnectorCustomerResponseData, PaymentsResponseData, VaultResponseData,
    },
    types::{ConnectorCustomerRouterData, VaultRouterData},
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};

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

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

impl TryFrom<&VaultRouterData<ExternalVaultCreateFlow>> for HyperswitchVaultCreateRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &VaultRouterData<ExternalVaultCreateFlow>) -> Result<Self, Self::Error> {
        let customer_id = item
            .request
            .connector_customer_id
            .clone()
            .ok_or_else(utils::missing_field_err("connector_customer"))?;
        Ok(Self { customer_id })
    }
}

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

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

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

impl<F, T> TryFrom<ResponseRouterData<F, HyperswitchVaultCreateResponse, T, VaultResponseData>>
    for RouterData<F, T, VaultResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, HyperswitchVaultCreateResponse, T, VaultResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(VaultResponseData::ExternalVaultCreateResponse {
                session_id: item.response.id,
                client_secret: item.response.client_secret,
            }),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize)]
pub struct HyperswitchVaultCustomerCreateRequest {
    name: Option<Secret<String>>,
    email: Option<Email>,
}

impl TryFrom<&ConnectorCustomerRouterData> for HyperswitchVaultCustomerCreateRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &ConnectorCustomerRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            name: item.request.name.clone(),
            email: item.request.email.clone(),
        })
    }
}

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

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

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

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct HyperswitchVaultErrorDetails {
    #[serde(alias = "type")]
    pub error_type: String,
    pub message: Option<String>,
    pub code: String,
}


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

use api_models::payments::OpenBankingSessionToken;
use common_enums::{AttemptStatus, Currency};
use common_utils::types::FloatMajorUnit;
use hyperswitch_domain_models::{
    payment_method_data::{OpenBankingData, PaymentMethodData},
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_request_types::ResponseId,
    router_response_types::PaymentsResponseData,
    types::{PaymentsAuthorizeRouterData, PaymentsSyncRouterData},
};
use hyperswitch_interfaces::errors::ConnectorError;
use masking::{PeekInterface, Secret};
use serde::{Deserialize, Serialize};

use crate::{
    types::{PaymentsPostProcessingRouterData, ResponseRouterData},
    utils::is_payment_failure,
};

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

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

#[derive(Default, Debug, Serialize)]
pub struct PlaidPaymentsRequest {
    amount: PlaidAmount,
    recipient_id: String,
    reference: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    schedule: Option<PlaidSchedule>,
    #[serde(skip_serializing_if = "Option::is_none")]
    options: Option<PlaidOptions>,
}

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct PlaidAmount {
    currency: Currency,
    value: FloatMajorUnit,
}

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct PlaidSchedule {
    interval: String,
    interval_execution_day: String,
    start_date: String,
    end_date: Option<String>,
    adjusted_start_date: Option<String>,
}

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct PlaidOptions {
    request_refund_details: bool,
    iban: Option<Secret<String>>,
    bacs: Option<PlaidBacs>,
    scheme: String,
}

#[derive(Default, Debug, Serialize, Deserialize)]
pub struct PlaidBacs {
    account: Secret<String>,
    sort_code: Secret<String>,
}

#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct PlaidLinkTokenRequest {
    client_name: String,
    country_codes: Vec<String>,
    language: String,
    products: Vec<String>,
    user: User,
    payment_initiation: PlaidPaymentInitiation,
    redirect_uri: Option<String>,
    android_package_name: Option<String>,
}

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

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

impl TryFrom<&PlaidRouterData<&PaymentsAuthorizeRouterData>> for PlaidPaymentsRequest {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(item: &PlaidRouterData<&PaymentsAuthorizeRouterData>) -> Result<Self, Self::Error> {
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::OpenBanking(ref data) => match data {
                OpenBankingData::OpenBankingPIS { .. } => {
                    let amount = item.amount;
                    let currency = item.router_data.request.currency;
                    let payment_id = item.router_data.payment_id.clone();
                    let id_len = payment_id.len();
                    let reference = if id_len > 18 {
                        payment_id.get(id_len - 18..id_len).map(|id| id.to_string())
                    } else {
                        Some(payment_id)
                    }
                    .ok_or(ConnectorError::MissingRequiredField {
                        field_name: "payment_id",
                    })?;
                    let recipient_type = item
                        .router_data
                        .additional_merchant_data
                        .as_ref()
                        .map(|merchant_data| match merchant_data {
                            api_models::admin::AdditionalMerchantData::OpenBankingRecipientData(
                                data,
                            ) => data.clone(),
                        })
                        .ok_or(ConnectorError::MissingRequiredField {
                            field_name: "additional_merchant_data",
                        })?;

                    let recipient_id = match recipient_type {
                        api_models::admin::MerchantRecipientData::ConnectorRecipientId(id) => {
                            Ok(id.peek().to_string())
                        }
                        _ => Err(ConnectorError::MissingRequiredField {
                            field_name: "ConnectorRecipientId",
                        }),
                    }?;

                    Ok(Self {
                        amount: PlaidAmount {
                            currency,
                            value: amount,
                        },
                        reference,
                        recipient_id,
                        schedule: None,
                        options: None,
                    })
                }
            },
            _ => Err(ConnectorError::NotImplemented("Payment methods".to_string()).into()),
        }
    }
}

impl TryFrom<&PaymentsSyncRouterData> for PlaidSyncRequest {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(item: &PaymentsSyncRouterData) -> Result<Self, Self::Error> {
        match item.request.connector_transaction_id {
            ResponseId::ConnectorTransactionId(ref id) => Ok(Self {
                payment_id: id.clone(),
            }),
            _ => Err((ConnectorError::MissingConnectorTransactionID).into()),
        }
    }
}

impl TryFrom<&PaymentsPostProcessingRouterData> for PlaidLinkTokenRequest {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(item: &PaymentsPostProcessingRouterData) -> Result<Self, Self::Error> {
        match item.request.payment_method_data {
            PaymentMethodData::OpenBanking(ref data) => match data {
                OpenBankingData::OpenBankingPIS { .. } => {
                    let headers = item.header_payload.clone();

                    let platform = headers
                        .as_ref()
                        .and_then(|headers| headers.x_client_platform.clone());

                    let (is_android, is_ios) = match platform {
                        Some(common_enums::ClientPlatform::Android) => (true, false),
                        Some(common_enums::ClientPlatform::Ios) => (false, true),
                        _ => (false, false),
                    };

                    Ok(Self {
                        client_name: "Hyperswitch".to_string(),
                        country_codes: item
                            .request
                            .country
                            .map(|code| vec![code.to_string()])
                            .ok_or(ConnectorError::MissingRequiredField {
                                field_name: "billing.address.country",
                            })?,
                        language: "en".to_string(),
                        products: vec!["payment_initiation".to_string()],
                        user: User {
                            client_user_id: item
                                .request
                                .customer_id
                                .clone()
                                .map(|id| id.get_string_repr().to_string())
                                .unwrap_or("default cust".to_string()),
                        },
                        payment_initiation: PlaidPaymentInitiation {
                            payment_id: item
                                .request
                                .connector_transaction_id
                                .clone()
                                .ok_or(ConnectorError::MissingConnectorTransactionID)?,
                        },
                        android_package_name: if is_android {
                            headers
                                .as_ref()
                                .and_then(|headers| headers.x_app_id.clone())
                        } else {
                            None
                        },
                        redirect_uri: if is_ios {
                            headers
                                .as_ref()
                                .and_then(|headers| headers.x_redirect_uri.clone())
                        } else {
                            None
                        },
                    })
                }
            },
            _ => Err(ConnectorError::NotImplemented("Payment methods".to_string()).into()),
        }
    }
}

pub struct PlaidAuthType {
    pub client_id: Secret<String>,
    pub secret: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for PlaidAuthType {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
                client_id: api_key.to_owned(),
                secret: key1.to_owned(),
            }),
            _ => Err(ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[derive(strum::Display)]
pub enum PlaidPaymentStatus {
    PaymentStatusInputNeeded,
    PaymentStatusInitiated,
    PaymentStatusInsufficientFunds,
    PaymentStatusFailed,
    PaymentStatusBlocked,
    PaymentStatusCancelled,
    PaymentStatusExecuted,
    PaymentStatusSettled,
    PaymentStatusEstablished,
    PaymentStatusRejected,
    PaymentStatusAuthorising,
}

impl From<PlaidPaymentStatus> for AttemptStatus {
    fn from(item: PlaidPaymentStatus) -> Self {
        match item {
            PlaidPaymentStatus::PaymentStatusAuthorising => Self::Authorizing,
            PlaidPaymentStatus::PaymentStatusBlocked
            | PlaidPaymentStatus::PaymentStatusInsufficientFunds
            | PlaidPaymentStatus::PaymentStatusRejected => Self::AuthorizationFailed,
            PlaidPaymentStatus::PaymentStatusCancelled => Self::Voided,
            PlaidPaymentStatus::PaymentStatusEstablished => Self::Authorized,
            PlaidPaymentStatus::PaymentStatusExecuted
            | PlaidPaymentStatus::PaymentStatusSettled
            | PlaidPaymentStatus::PaymentStatusInitiated => Self::Charged,
            PlaidPaymentStatus::PaymentStatusFailed => Self::Failure,
            PlaidPaymentStatus::PaymentStatusInputNeeded => Self::AuthenticationPending,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PlaidPaymentsResponse {
    status: PlaidPaymentStatus,
    payment_id: String,
}

impl<F, T> TryFrom<ResponseRouterData<F, PlaidPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PlaidPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let status = AttemptStatus::from(item.response.status.clone());
        Ok(Self {
            status,
            response: if is_payment_failure(status) {
                Err(ErrorResponse {
                    // populating status everywhere as plaid only sends back a status
                    code: item.response.status.clone().to_string(),
                    message: item.response.status.clone().to_string(),
                    reason: Some(item.response.status.to_string()),
                    status_code: item.http_code,
                    attempt_status: None,
                    connector_transaction_id: Some(item.response.payment_id),
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                })
            } else {
                Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(
                        item.response.payment_id.clone(),
                    ),
                    redirection_data: Box::new(None),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: Some(item.response.payment_id),
                    incremental_authorization_allowed: None,
                    charges: None,
                })
            },
            ..item.data
        })
    }
}

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

impl<F, T> TryFrom<ResponseRouterData<F, PlaidLinkTokenResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PlaidLinkTokenResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let session_token = Some(OpenBankingSessionToken {
            open_banking_session_token: item.response.link_token,
        });

        Ok(Self {
            status: AttemptStatus::AuthenticationPending,
            response: Ok(PaymentsResponseData::PostProcessingResponse { session_token }),
            ..item.data
        })
    }
}

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

#[derive(Debug, Serialize, Deserialize)]
pub struct PlaidSyncResponse {
    payment_id: String,
    amount: PlaidAmount,
    status: PlaidPaymentStatus,
    recipient_id: String,
    reference: String,
    last_status_update: String,
    adjusted_reference: Option<String>,
    schedule: Option<PlaidSchedule>,
    iban: Option<Secret<String>>,
    bacs: Option<PlaidBacs>,
    scheme: Option<String>,
    adjusted_scheme: Option<String>,
    request_id: String,
}

impl<F, T> TryFrom<ResponseRouterData<F, PlaidSyncResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, PlaidSyncResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let status = AttemptStatus::from(item.response.status.clone());
        Ok(Self {
            status,
            response: if is_payment_failure(status) {
                Err(ErrorResponse {
                    // populating status everywhere as plaid only sends back a status
                    code: item.response.status.clone().to_string(),
                    message: item.response.status.clone().to_string(),
                    reason: Some(item.response.status.to_string()),
                    status_code: item.http_code,
                    attempt_status: None,
                    connector_transaction_id: Some(item.response.payment_id),
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                })
            } else {
                Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(
                        item.response.payment_id.clone(),
                    ),
                    redirection_data: Box::new(None),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: Some(item.response.payment_id),
                    incremental_authorization_allowed: None,
                    charges: None,
                })
            },
            ..item.data
        })
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct PlaidErrorResponse {
    pub display_message: Option<String>,
    pub error_code: Option<String>,
    pub error_message: String,
    pub error_type: Option<String>,
}


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

use common_enums::{enums, CountryAlpha2, Currency};
use common_utils::{pii, request::Method, types::MinorUnit};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    payment_method_data::{PayLaterData, PaymentMethodData},
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsCompleteAuthorizeRouterData, RefundsRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{
    types::{
        PaymentsCancelResponseRouterData, PaymentsCaptureResponseRouterData,
        RefundsResponseRouterData, ResponseRouterData,
    },
    utils::{PaymentsAuthorizeRequestData, RouterData as OtherRouterData},
};
pub struct AffirmRouterData<T> {
    pub amount: MinorUnit,
    pub router_data: T,
}

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

#[derive(Debug, Serialize)]
pub struct AffirmPaymentsRequest {
    pub merchant: Merchant,
    pub items: Vec<Item>,
    pub shipping: Option<Shipping>,
    pub billing: Option<Billing>,
    pub total: MinorUnit,
    pub currency: Currency,
    pub order_id: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct AffirmCompleteAuthorizeRequest {
    pub order_id: Option<String>,
    pub reference_id: Option<String>,
    pub transaction_id: String,
}

impl TryFrom<&PaymentsCompleteAuthorizeRouterData> for AffirmCompleteAuthorizeRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCompleteAuthorizeRouterData) -> Result<Self, Self::Error> {
        let transaction_id = item.request.connector_transaction_id.clone().ok_or(
            errors::ConnectorError::MissingRequiredField {
                field_name: "connector_transaction_id",
            },
        )?;

        let reference_id = item.reference_id.clone();
        let order_id = item.connector_request_reference_id.clone();
        Ok(Self {
            transaction_id,
            order_id: Some(order_id),
            reference_id,
        })
    }
}

#[derive(Debug, Serialize)]
pub struct Merchant {
    pub public_api_key: Secret<String>,
    pub user_confirmation_url: String,
    pub user_cancel_url: String,
    pub user_confirmation_url_action: Option<String>,
    pub use_vcn: Option<String>,
    pub name: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct Item {
    pub display_name: String,
    pub sku: String,
    pub unit_price: MinorUnit,
    pub qty: i64,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Shipping {
    pub name: Name,
    pub address: Address,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone_number: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<pii::Email>,
}
#[derive(Debug, Serialize)]
pub struct Billing {
    pub name: Name,
    pub address: Address,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone_number: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<pii::Email>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Name {
    pub first: Option<Secret<String>>,
    pub last: Option<Secret<String>>,
    pub full: Option<Secret<String>>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Address {
    pub line1: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub line2: Option<Secret<String>>,
    pub city: Option<String>,
    pub state: Option<Secret<String>>,
    pub zipcode: Option<Secret<String>>,
    pub country: Option<CountryAlpha2>,
}

#[derive(Debug, Serialize)]
pub struct Metadata {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub shipping_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub entity_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub platform_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub platform_version: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub platform_affirm: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub webhook_session_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mode: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub customer: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub itinerary: Option<Vec<Value>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub checkout_channel_type: Option<String>,
    #[serde(rename = "BOPIS", skip_serializing_if = "Option::is_none")]
    pub bopis: Option<bool>,
}

#[derive(Debug, Serialize)]
pub struct Discount {
    pub discount_amount: MinorUnit,
    pub discount_display_name: String,
    pub discount_code: Option<String>,
}

impl TryFrom<&AffirmRouterData<&PaymentsAuthorizeRouterData>> for AffirmPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: &AffirmRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let router_data = &item.router_data;
        let request = &router_data.request;

        let billing = Some(Billing {
            name: Name {
                first: item.router_data.get_optional_billing_first_name(),
                last: item.router_data.get_optional_billing_last_name(),
                full: item.router_data.get_optional_billing_full_name(),
            },
            address: Address {
                line1: item.router_data.get_optional_billing_line1(),
                line2: item.router_data.get_optional_billing_line2(),
                city: item.router_data.get_optional_billing_city(),
                state: item.router_data.get_optional_billing_state(),
                zipcode: item.router_data.get_optional_billing_zip(),
                country: item.router_data.get_optional_billing_country(),
            },
            phone_number: item.router_data.get_optional_billing_phone_number(),
            email: item.router_data.get_optional_billing_email(),
        });

        let shipping = Some(Shipping {
            name: Name {
                first: item.router_data.get_optional_shipping_first_name(),
                last: item.router_data.get_optional_shipping_last_name(),
                full: item.router_data.get_optional_shipping_full_name(),
            },
            address: Address {
                line1: item.router_data.get_optional_shipping_line1(),
                line2: item.router_data.get_optional_shipping_line2(),
                city: item.router_data.get_optional_shipping_city(),
                state: item.router_data.get_optional_shipping_state(),
                zipcode: item.router_data.get_optional_shipping_zip(),
                country: item.router_data.get_optional_shipping_country(),
            },
            phone_number: item.router_data.get_optional_shipping_phone_number(),
            email: item.router_data.get_optional_shipping_email(),
        });

        match request.payment_method_data.clone() {
            PaymentMethodData::PayLater(PayLaterData::AffirmRedirect {}) => {
                let items = match request.order_details.clone() {
                    Some(order_details) => order_details
                        .iter()
                        .map(|data| {
                            Ok(Item {
                                display_name: data.product_name.clone(),
                                sku: data.product_id.clone().unwrap_or_default(),
                                unit_price: data.amount,
                                qty: data.quantity.into(),
                            })
                        })
                        .collect::<Result<Vec<_>, _>>(),
                    None => Err(report!(errors::ConnectorError::MissingRequiredField {
                        field_name: "order_details",
                    })),
                }?;

                let auth_type = AffirmAuthType::try_from(&item.router_data.connector_auth_type)
                    .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
                let public_api_key = auth_type.public_key;
                let merchant = Merchant {
                    public_api_key,
                    user_confirmation_url: request.get_complete_authorize_url()?,
                    user_cancel_url: request.get_router_return_url()?,
                    user_confirmation_url_action: None,
                    use_vcn: None,
                    name: None,
                };

                Ok(Self {
                    merchant,
                    items,
                    shipping,
                    billing,
                    total: item.amount,
                    currency: request.currency,
                    order_id: Some(item.router_data.connector_request_reference_id.clone()),
                })
            }
            _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
        }
    }
}
pub struct AffirmAuthType {
    pub public_key: Secret<String>,
    pub private_key: Secret<String>,
}

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

impl From<AffirmTransactionStatus> for common_enums::AttemptStatus {
    fn from(item: AffirmTransactionStatus) -> Self {
        match item {
            AffirmTransactionStatus::Authorized => Self::Authorized,
            AffirmTransactionStatus::AuthExpired => Self::Failure,
            AffirmTransactionStatus::Canceled => Self::Voided,
            AffirmTransactionStatus::Captured => Self::Charged,
            AffirmTransactionStatus::ConfirmationExpired => Self::Failure,
            AffirmTransactionStatus::Confirmed => Self::Authorized,
            AffirmTransactionStatus::Created => Self::Pending,
            AffirmTransactionStatus::Declined => Self::Failure,
            AffirmTransactionStatus::Disputed => Self::Unresolved,
            AffirmTransactionStatus::DisputeRefunded => Self::Unresolved,
            AffirmTransactionStatus::ExpiredAuthorization => Self::Failure,
            AffirmTransactionStatus::ExpiredConfirmation => Self::Failure,
            AffirmTransactionStatus::PartiallyCaptured => Self::Charged,
            AffirmTransactionStatus::Voided => Self::Voided,
            AffirmTransactionStatus::PartiallyVoided => Self::Voided,
        }
    }
}

impl From<AffirmRefundStatus> for common_enums::RefundStatus {
    fn from(item: AffirmRefundStatus) -> Self {
        match item {
            AffirmRefundStatus::PartiallyRefunded => Self::Success,
            AffirmRefundStatus::Refunded => Self::Success,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AffirmPaymentsResponse {
    checkout_id: String,
    redirect_url: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct AffirmCompleteAuthorizeResponse {
    pub id: String,
    pub status: AffirmTransactionStatus,
    pub amount: MinorUnit,
    pub amount_refunded: MinorUnit,
    pub authorization_expiration: String,
    pub checkout_id: String,
    pub created: String,
    pub currency: Currency,
    pub events: Vec<TransactionEvent>,
    pub settlement_transaction_id: Option<String>,
    pub transaction_id: String,
    pub order_id: String,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
    pub shipping: Option<Shipping>,
    pub agent_alias: Option<String>,
    pub merchant_transaction_id: Option<String>,
    pub provider_id: Option<i64>,
    pub remove_tax: Option<bool>,
    pub checkout: Option<Value>,
    pub refund_expires: Option<String>,
    pub remaining_capturable_amount: Option<i64>,
    pub loan_information: Option<LoanInformation>,
    pub user_id: Option<String>,
    pub platform: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct TransactionEvent {
    pub id: String,
    pub amount: MinorUnit,
    pub created: String,
    pub currency: Currency,
    pub fee: Option<i64>,
    pub fee_refunded: Option<MinorUnit>,
    pub reference_id: Option<String>,
    #[serde(rename = "type")]
    pub event_type: AffirmEventType,
    pub settlement_transaction_id: Option<String>,
    pub transaction_id: String,
    pub order_id: String,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
    pub shipping: Option<Shipping>,
    pub agent_alias: Option<String>,
    pub merchant_transaction_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct LoanInformation {
    pub fees: Option<LoanFees>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct LoanFees {
    pub amount: Option<MinorUnit>,
    pub description: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AffirmTransactionStatus {
    Authorized,
    AuthExpired,
    Canceled,
    Captured,
    ConfirmationExpired,
    Confirmed,
    Created,
    Declined,
    Disputed,
    DisputeRefunded,
    ExpiredAuthorization,
    ExpiredConfirmation,
    PartiallyCaptured,
    Voided,
    PartiallyVoided,
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AffirmRefundStatus {
    PartiallyRefunded,
    Refunded,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct AffirmPSyncResponse {
    pub amount: MinorUnit,
    pub amount_refunded: MinorUnit,
    pub authorization_expiration: Option<String>,
    pub checkout_id: String,
    pub created: String,
    pub currency: Currency,
    pub events: Vec<TransactionEvent>,
    pub id: String,
    pub order_id: String,
    pub provider_id: Option<i64>,
    pub remove_tax: Option<bool>,
    pub status: AffirmTransactionStatus,
    pub checkout: Option<Value>,
    pub refund_expires: Option<String>,
    pub remaining_capturable_amount: Option<MinorUnit>,
    pub loan_information: Option<LoanInformation>,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
    pub shipping: Option<Shipping>,
    pub merchant_transaction_id: Option<String>,
    pub settlement_transaction_id: Option<String>,
    pub transaction_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct AffirmRsyncResponse {
    pub amount: MinorUnit,
    pub amount_refunded: MinorUnit,
    pub authorization_expiration: String,
    pub checkout_id: String,
    pub created: String,
    pub currency: Currency,
    pub events: Vec<TransactionEvent>,
    pub id: String,
    pub order_id: String,
    pub status: AffirmRefundStatus,
    pub refund_expires: Option<String>,
    pub remaining_capturable_amount: Option<MinorUnit>,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
    pub shipping: Option<Shipping>,
    pub merchant_transaction_id: Option<String>,
    pub settlement_transaction_id: Option<String>,
    pub transaction_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AffirmResponseWrapper {
    Authorize(AffirmPaymentsResponse),
    Psync(Box<AffirmPSyncResponse>),
}

impl<F, T> TryFrom<ResponseRouterData<F, AffirmResponseWrapper, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: ResponseRouterData<F, AffirmResponseWrapper, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        match &item.response {
            AffirmResponseWrapper::Authorize(resp) => {
                let redirection_data = url::Url::parse(&resp.redirect_url)
                    .ok()
                    .map(|url| RedirectForm::from((url, Method::Get)));

                Ok(Self {
                    status: enums::AttemptStatus::AuthenticationPending,
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::ConnectorTransactionId(resp.checkout_id.clone()),
                        redirection_data: Box::new(redirection_data),
                        mandate_reference: Box::new(None),
                        connector_metadata: None,
                        network_txn_id: None,
                        connector_response_reference_id: None,
                        charges: None,
                        incremental_authorization_allowed: None,
                    }),
                    ..item.data
                })
            }
            AffirmResponseWrapper::Psync(resp) => {
                let status = enums::AttemptStatus::from(resp.status);
                Ok(Self {
                    status,
                    response: Ok(PaymentsResponseData::TransactionResponse {
                        resource_id: ResponseId::ConnectorTransactionId(resp.id.clone()),
                        redirection_data: Box::new(None),
                        mandate_reference: Box::new(None),
                        connector_metadata: None,
                        network_txn_id: None,
                        connector_response_reference_id: None,
                        charges: None,
                        incremental_authorization_allowed: None,
                    }),
                    ..item.data
                })
            }
        }
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, AffirmCompleteAuthorizeResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, AffirmCompleteAuthorizeResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        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
        })
    }
}
#[derive(Default, Debug, Serialize)]
pub struct AffirmRefundRequest {
    pub amount: MinorUnit,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reference_id: Option<String>,
}

impl<F> TryFrom<&AffirmRouterData<&RefundsRouterData<F>>> for AffirmRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(item: &AffirmRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        let reference_id = item.router_data.request.connector_transaction_id.clone();

        Ok(Self {
            amount: item.amount.to_owned(),
            reference_id: Some(reference_id),
        })
    }
}

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

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct AffirmRefundResponse {
    pub id: String,
    pub amount: MinorUnit,
    pub created: String,
    pub currency: Currency,
    pub fee: Option<MinorUnit>,
    pub fee_refunded: Option<MinorUnit>,
    pub reference_id: Option<String>,
    #[serde(rename = "type")]
    pub event_type: AffirmEventType,
    pub settlement_transaction_id: Option<String>,
    pub transaction_id: String,
    pub order_id: String,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
    pub shipping: Option<Shipping>,
    pub agent_alias: Option<String>,
    pub merchant_transaction_id: Option<String>,
}

impl From<AffirmEventType> for enums::RefundStatus {
    fn from(event_type: AffirmEventType) -> Self {
        match event_type {
            AffirmEventType::Refund => Self::Success,
            _ => Self::Pending,
        }
    }
}

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

impl TryFrom<RefundsResponseRouterData<RSync, AffirmRsyncResponse>> for RefundsRouterData<RSync> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: RefundsResponseRouterData<RSync, AffirmRsyncResponse>,
    ) -> 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 AffirmErrorResponse {
    pub status_code: u16,
    pub code: String,
    pub message: String,
    #[serde(rename = "type")]
    pub error_type: String,
}

#[derive(Debug, Serialize)]
pub struct AffirmCaptureRequest {
    pub order_id: Option<String>,
    pub reference_id: Option<String>,
    pub amount: MinorUnit,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
}

impl TryFrom<&AffirmRouterData<&PaymentsCaptureRouterData>> for AffirmCaptureRequest {
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(item: &AffirmRouterData<&PaymentsCaptureRouterData>) -> Result<Self, Self::Error> {
        let reference_id = match item.router_data.connector_request_reference_id.clone() {
            ref_id if ref_id.is_empty() => None,
            ref_id => Some(ref_id),
        };

        let amount = item.amount;

        Ok(Self {
            reference_id,
            amount,
            order_id: None,
            shipping_carrier: None,
            shipping_confirmation: None,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct AffirmCaptureResponse {
    pub id: String,
    pub amount: MinorUnit,
    pub created: String,
    pub currency: Currency,
    pub fee: Option<MinorUnit>,
    pub fee_refunded: Option<MinorUnit>,
    pub reference_id: Option<String>,
    #[serde(rename = "type")]
    pub event_type: AffirmEventType,
    pub settlement_transaction_id: Option<String>,
    pub transaction_id: String,
    pub order_id: String,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
    pub shipping: Option<Shipping>,
    pub agent_alias: Option<String>,
    pub merchant_transaction_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AffirmEventType {
    Auth,
    AuthExpired,
    Capture,
    ChargeOff,
    Confirm,
    ConfirmationExpired,
    ExpireAuthorization,
    ExpireConfirmation,
    Refund,
    SplitCapture,
    Update,
    Void,
    PartialVoid,
    RefundVoided,
}

impl From<AffirmEventType> for enums::AttemptStatus {
    fn from(event_type: AffirmEventType) -> Self {
        match event_type {
            AffirmEventType::Auth => Self::Authorized,
            AffirmEventType::Capture | AffirmEventType::SplitCapture | AffirmEventType::Confirm => {
                Self::Charged
            }
            AffirmEventType::AuthExpired
            | AffirmEventType::ChargeOff
            | AffirmEventType::ConfirmationExpired
            | AffirmEventType::ExpireAuthorization
            | AffirmEventType::ExpireConfirmation => Self::Failure,
            AffirmEventType::Refund | AffirmEventType::RefundVoided => Self::AutoRefunded,
            AffirmEventType::Update => Self::Pending,
            AffirmEventType::Void | AffirmEventType::PartialVoid => Self::Voided,
        }
    }
}

impl TryFrom<PaymentsCaptureResponseRouterData<AffirmCaptureResponse>>
    for PaymentsCaptureRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsCaptureResponseRouterData<AffirmCaptureResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.event_type.clone()),
            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
        })
    }
}

impl TryFrom<&PaymentsCancelRouterData> for AffirmCancelRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        let request = &item.request;

        let reference_id = request.connector_transaction_id.clone();
        let amount = item
            .request
            .amount
            .ok_or(errors::ConnectorError::MissingRequiredField {
                field_name: "amount",
            })?;
        Ok(Self {
            reference_id: Some(reference_id),
            amount,
            merchant_transaction_id: None,
        })
    }
}

#[derive(Debug, Serialize)]
pub struct AffirmCancelRequest {
    pub reference_id: Option<String>,
    pub amount: i64,
    pub merchant_transaction_id: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct AffirmCancelResponse {
    pub id: String,
    pub amount: MinorUnit,
    pub created: String,
    pub currency: Currency,
    pub fee: Option<MinorUnit>,
    pub fee_refunded: Option<MinorUnit>,
    pub reference_id: Option<String>,
    #[serde(rename = "type")]
    pub event_type: AffirmEventType,
    pub settlement_transaction_id: Option<String>,
    pub transaction_id: String,
    pub order_id: String,
    pub shipping_carrier: Option<String>,
    pub shipping_confirmation: Option<String>,
    pub shipping: Option<Shipping>,
    pub agent_alias: Option<String>,
    pub merchant_transaction_id: Option<String>,
}

impl TryFrom<PaymentsCancelResponseRouterData<AffirmCancelResponse>> for PaymentsCancelRouterData {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsCancelResponseRouterData<AffirmCancelResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::from(item.response.event_type.clone()),
            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
        })
    }
}