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

use std::collections::HashMap;

use api_models::webhooks::IncomingWebhookEvent;
use common_enums::enums;
use common_utils::{
    errors::CustomResult, ext_traits::Encode, request::Method, types::FloatMajorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{BankRedirectData, PaymentMethodData, RealTimePaymentData, UpiData},
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        refunds::{Execute, RSync},
        Authorize,
    },
    router_request_types::{PaymentsAuthorizeData, ResponseId},
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types::{self, RefundsRouterData},
};
use hyperswitch_interfaces::{
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    errors,
};
use masking::{Secret, SwitchStrategy};
use serde::{Deserialize, Serialize};

use crate::{
    types::{RefundsResponseRouterData, ResponseRouterData},
    utils::{
        get_unimplemented_payment_method_error_message, is_payment_failure, is_refund_failure,
        PaymentsAuthorizeRequestData, RefundsRequestData,
    },
};

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

// Every access token will be valid for 5 minutes. It contains grant_type and scope for different type of access, but for our usecases it should be only 'client_credentials' and 'payment' resp(as per doc) for all type of api call.
#[derive(Debug, Serialize)]
pub struct IatapayAuthUpdateRequest {
    grant_type: String,
    scope: String,
}
impl TryFrom<&types::RefreshTokenRouterData> for IatapayAuthUpdateRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(_item: &types::RefreshTokenRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            grant_type: "client_credentials".to_string(),
            scope: "payment".to_string(),
        })
    }
}
#[derive(Debug, Serialize)]
pub struct IatapayRouterData<T> {
    amount: FloatMajorUnit,
    router_data: T,
}
impl<T> TryFrom<(FloatMajorUnit, T)> for IatapayRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from((amount, item): (FloatMajorUnit, T)) -> Result<Self, Self::Error> {
        Ok(Self {
            amount,
            router_data: item,
        })
    }
}
#[derive(Debug, Deserialize, Serialize)]
pub struct IatapayAuthUpdateResponse {
    pub access_token: Secret<String>,
    pub expires_in: i64,
}

impl<F, T> TryFrom<ResponseRouterData<F, IatapayAuthUpdateResponse, T, AccessToken>>
    for RouterData<F, T, AccessToken>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, IatapayAuthUpdateResponse, 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)]
#[serde(rename_all = "camelCase")]
pub struct RedirectUrls {
    success_url: String,
    failure_url: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayerInfo {
    token_id: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum PreferredCheckoutMethod {
    Vpa, //Passing this in UPI_COLLECT will trigger an S2S payment call which is not required.
    Qr,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IatapayPaymentsRequest {
    merchant_id: Secret<String>,
    merchant_payment_id: Option<String>,
    amount: FloatMajorUnit,
    currency: common_enums::Currency,
    country: common_enums::CountryAlpha2,
    locale: String,
    redirect_urls: RedirectUrls,
    notification_url: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    payer_info: Option<PayerInfo>,
    #[serde(skip_serializing_if = "Option::is_none")]
    preferred_checkout_method: Option<PreferredCheckoutMethod>,
}

impl
    TryFrom<&IatapayRouterData<&RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>>>
    for IatapayPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;

    fn try_from(
        item: &IatapayRouterData<
            &RouterData<Authorize, PaymentsAuthorizeData, PaymentsResponseData>,
        >,
    ) -> Result<Self, Self::Error> {
        let return_url = item.router_data.request.get_router_return_url()?;
        // Iatapay processes transactions through the payment method selected based on the country
        let (country, payer_info, preferred_checkout_method) = match item
            .router_data
            .request
            .payment_method_data
            .clone()
        {
            PaymentMethodData::Upi(upi_type) => match upi_type {
                UpiData::UpiCollect(upi_data) => (
                    common_enums::CountryAlpha2::IN,
                    upi_data.vpa_id.map(|id| PayerInfo {
                        token_id: id.switch_strategy(),
                    }),
                    None,
                ),
                UpiData::UpiIntent(_) | UpiData::UpiQr(_) => (
                    common_enums::CountryAlpha2::IN,
                    None,
                    Some(PreferredCheckoutMethod::Qr),
                ),
            },
            PaymentMethodData::BankRedirect(bank_redirect_data) => match bank_redirect_data {
                BankRedirectData::Ideal { .. } => (common_enums::CountryAlpha2::NL, None, None),
                BankRedirectData::LocalBankRedirect {} => {
                    (common_enums::CountryAlpha2::AT, None, None)
                }
                BankRedirectData::BancontactCard { .. }
                | BankRedirectData::Bizum {}
                | BankRedirectData::Blik { .. }
                | BankRedirectData::Eft { .. }
                | BankRedirectData::Eps { .. }
                | BankRedirectData::Giropay { .. }
                | BankRedirectData::Interac { .. }
                | BankRedirectData::OnlineBankingCzechRepublic { .. }
                | BankRedirectData::OnlineBankingFinland { .. }
                | BankRedirectData::OnlineBankingPoland { .. }
                | BankRedirectData::OnlineBankingSlovakia { .. }
                | BankRedirectData::OpenBankingUk { .. }
                | BankRedirectData::Przelewy24 { .. }
                | BankRedirectData::Sofort { .. }
                | BankRedirectData::Trustly { .. }
                | BankRedirectData::OnlineBankingFpx { .. }
                | BankRedirectData::OnlineBankingThailand { .. } => {
                    Err(errors::ConnectorError::NotImplemented(
                        get_unimplemented_payment_method_error_message("iatapay"),
                    ))?
                }
            },
            PaymentMethodData::RealTimePayment(real_time_payment_data) => {
                match *real_time_payment_data {
                    RealTimePaymentData::DuitNow {} => {
                        (common_enums::CountryAlpha2::MY, None, None)
                    }
                    RealTimePaymentData::Fps {} => (common_enums::CountryAlpha2::HK, None, None),
                    RealTimePaymentData::PromptPay {} => {
                        (common_enums::CountryAlpha2::TH, None, None)
                    }
                    RealTimePaymentData::VietQr {} => (common_enums::CountryAlpha2::VN, None, None),
                }
            }
            PaymentMethodData::Card(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankDebit(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    get_unimplemented_payment_method_error_message("iatapay"),
                ))?
            }
        };
        let payload = Self {
            merchant_id: IatapayAuthType::try_from(&item.router_data.connector_auth_type)?
                .merchant_id,
            merchant_payment_id: Some(item.router_data.connector_request_reference_id.clone()),
            amount: item.amount,
            currency: item.router_data.request.currency,
            country,
            locale: format!("en-{country}"),
            redirect_urls: get_redirect_url(return_url),
            payer_info,
            notification_url: item.router_data.request.get_webhook_url()?,
            preferred_checkout_method,
        };
        Ok(payload)
    }
}

fn get_redirect_url(return_url: String) -> RedirectUrls {
    RedirectUrls {
        success_url: return_url.clone(),
        failure_url: return_url,
    }
}

// Auth Struct
pub struct IatapayAuthType {
    pub(super) client_id: Secret<String>,
    pub(super) merchant_id: Secret<String>,
    pub(super) client_secret: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for IatapayAuthType {
    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.to_owned(),
                merchant_id: key1.to_owned(),
                client_secret: api_secret.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
        }
    }
}
// PaymentsResponse
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum IatapayPaymentStatus {
    #[default]
    Created,
    Initiated,
    Authorized,
    Settled,
    Cleared,
    Failed,
    #[serde(rename = "UNEXPECTED SETTLED")]
    UnexpectedSettled,
}

impl From<IatapayPaymentStatus> for enums::AttemptStatus {
    fn from(item: IatapayPaymentStatus) -> Self {
        match item {
            IatapayPaymentStatus::Authorized
            | IatapayPaymentStatus::Settled
            | IatapayPaymentStatus::Cleared => Self::Charged,
            IatapayPaymentStatus::Failed | IatapayPaymentStatus::UnexpectedSettled => Self::Failure,
            IatapayPaymentStatus::Created => Self::AuthenticationPending,
            IatapayPaymentStatus::Initiated => Self::Pending,
        }
    }
}

#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RedirectUrl {
    pub redirect_url: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CheckoutMethod {
    pub redirect: RedirectUrl,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IatapayPaymentsResponse {
    pub status: IatapayPaymentStatus,
    pub iata_payment_id: Option<String>,
    pub iata_refund_id: Option<String>,
    pub merchant_id: Option<Secret<String>>,
    pub merchant_payment_id: Option<String>,
    pub amount: FloatMajorUnit,
    pub currency: String,
    pub checkout_methods: Option<CheckoutMethod>,
    pub failure_code: Option<String>,
    pub failure_details: Option<String>,
}

fn get_iatpay_response(
    response: IatapayPaymentsResponse,
    status_code: u16,
) -> CustomResult<
    (
        enums::AttemptStatus,
        Option<ErrorResponse>,
        PaymentsResponseData,
    ),
    errors::ConnectorError,
> {
    let status = enums::AttemptStatus::from(response.status);
    let error = if is_payment_failure(status) {
        Some(ErrorResponse {
            code: response
                .failure_code
                .unwrap_or_else(|| NO_ERROR_CODE.to_string()),
            message: response
                .failure_details
                .clone()
                .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
            reason: response.failure_details,
            status_code,
            attempt_status: Some(status),
            connector_transaction_id: response.iata_payment_id.clone(),
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    } else {
        None
    };
    let form_fields = HashMap::new();
    let id = match response.iata_payment_id.clone() {
        Some(s) => ResponseId::ConnectorTransactionId(s),
        None => ResponseId::NoResponseId,
    };
    let connector_response_reference_id = response.merchant_payment_id.or(response.iata_payment_id);

    let payment_response_data = match response.checkout_methods {
        Some(checkout_methods) => {
            let (connector_metadata, redirection_data) =
                match checkout_methods.redirect.redirect_url.ends_with("qr") {
                    true => {
                        let qr_code_info = api_models::payments::FetchQrCodeInformation {
                            qr_code_fetch_url: url::Url::parse(
                                &checkout_methods.redirect.redirect_url,
                            )
                            .change_context(errors::ConnectorError::ResponseHandlingFailed)?,
                        };
                        (
                            Some(qr_code_info.encode_to_value())
                                .transpose()
                                .change_context(errors::ConnectorError::ResponseHandlingFailed)?,
                            None,
                        )
                    }
                    false => (
                        None,
                        Some(RedirectForm::Form {
                            endpoint: checkout_methods.redirect.redirect_url,
                            method: Method::Get,
                            form_fields,
                        }),
                    ),
                };

            PaymentsResponseData::TransactionResponse {
                resource_id: id,
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata,
                network_txn_id: None,
                connector_response_reference_id: connector_response_reference_id.clone(),
                incremental_authorization_allowed: None,
                charges: None,
            }
        }
        None => PaymentsResponseData::TransactionResponse {
            resource_id: id.clone(),
            redirection_data: Box::new(None),
            mandate_reference: Box::new(None),
            connector_metadata: None,
            network_txn_id: None,
            connector_response_reference_id: connector_response_reference_id.clone(),
            incremental_authorization_allowed: None,
            charges: None,
        },
    };

    Ok((status, error, payment_response_data))
}

impl<F, T> TryFrom<ResponseRouterData<F, IatapayPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = Error;
    fn try_from(
        item: ResponseRouterData<F, IatapayPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let (status, error, payment_response_data) =
            get_iatpay_response(item.response, item.http_code)?;
        Ok(Self {
            status,
            response: error.map_or_else(|| Ok(payment_response_data), Err),
            ..item.data
        })
    }
}

// REFUND :
// Type definition for RefundRequest
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IatapayRefundRequest {
    pub merchant_id: Secret<String>,
    pub merchant_refund_id: Option<String>,
    pub amount: FloatMajorUnit,
    pub currency: String,
    pub bank_transfer_description: Option<String>,
    pub notification_url: String,
}

impl<F> TryFrom<&IatapayRouterData<&RefundsRouterData<F>>> for IatapayRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &IatapayRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: item.amount,
            merchant_id: IatapayAuthType::try_from(&item.router_data.connector_auth_type)?
                .merchant_id,
            merchant_refund_id: Some(item.router_data.request.refund_id.clone()),
            currency: item.router_data.request.currency.to_string(),
            bank_transfer_description: item.router_data.request.reason.clone(),
            notification_url: item.router_data.request.get_webhook_url()?,
        })
    }
}

// Type definition for Refund Response

#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum RefundStatus {
    #[default]
    Created,
    Locked,
    Initiated,
    Authorized,
    Settled,
    Cleared,
    Failed,
}

impl From<RefundStatus> for enums::RefundStatus {
    fn from(item: RefundStatus) -> Self {
        match item {
            RefundStatus::Created => Self::Pending,
            RefundStatus::Failed => Self::Failure,
            RefundStatus::Locked => Self::Pending,
            RefundStatus::Initiated => Self::Pending,
            RefundStatus::Authorized => Self::Pending,
            RefundStatus::Settled => Self::Success,
            RefundStatus::Cleared => Self::Success,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RefundResponse {
    iata_refund_id: String,
    status: RefundStatus,
    merchant_refund_id: String,
    amount: FloatMajorUnit,
    currency: String,
    bank_transfer_description: Option<String>,
    failure_code: Option<String>,
    failure_details: Option<String>,
    lock_reason: Option<String>,
    creation_date_time: Option<String>,
    finish_date_time: Option<String>,
    update_date_time: Option<String>,
    clearance_date_time: Option<String>,
    iata_payment_id: Option<String>,
    merchant_payment_id: Option<String>,
    payment_amount: Option<FloatMajorUnit>,
    merchant_id: Option<Secret<String>>,
    account_country: Option<String>,
}

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> {
        let refund_status = enums::RefundStatus::from(item.response.status);
        let response = if is_refund_failure(refund_status) {
            Err(ErrorResponse {
                code: item
                    .response
                    .failure_code
                    .unwrap_or_else(|| NO_ERROR_CODE.to_string()),
                message: item
                    .response
                    .failure_details
                    .clone()
                    .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
                reason: item.response.failure_details,
                status_code: item.http_code,
                attempt_status: None,
                connector_transaction_id: Some(item.response.iata_refund_id.clone()),
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            })
        } else {
            Ok(RefundsResponseData {
                connector_refund_id: item.response.iata_refund_id.to_string(),
                refund_status,
            })
        };

        Ok(Self {
            response,
            ..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> {
        let refund_status = enums::RefundStatus::from(item.response.status);
        let response = if is_refund_failure(refund_status) {
            Err(ErrorResponse {
                code: item
                    .response
                    .failure_code
                    .unwrap_or_else(|| NO_ERROR_CODE.to_string()),
                message: item
                    .response
                    .failure_details
                    .clone()
                    .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
                reason: item.response.failure_details,
                status_code: item.http_code,
                attempt_status: None,
                connector_transaction_id: Some(item.response.iata_refund_id.clone()),
                network_advice_code: None,
                network_decline_code: None,
                network_error_message: None,
                connector_metadata: None,
            })
        } else {
            Ok(RefundsResponseData {
                connector_refund_id: item.response.iata_refund_id.to_string(),
                refund_status,
            })
        };
        Ok(Self {
            response,
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct IatapayErrorResponse {
    pub status: Option<u16>,
    pub error: String,
    pub message: Option<String>,
    pub reason: Option<String>,
}

#[derive(Deserialize, Debug, Serialize)]
pub struct IatapayAccessTokenErrorResponse {
    pub error: String,
    pub path: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IatapayPaymentWebhookBody {
    pub status: IatapayWebhookStatus,
    pub iata_payment_id: String,
    pub merchant_payment_id: Option<String>,
    pub failure_code: Option<String>,
    pub failure_details: Option<String>,
    pub amount: FloatMajorUnit,
    pub currency: String,
    pub checkout_methods: Option<CheckoutMethod>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IatapayRefundWebhookBody {
    pub status: IatapayRefundWebhookStatus,
    pub iata_refund_id: String,
    pub merchant_refund_id: Option<String>,
    pub failure_code: Option<String>,
    pub failure_details: Option<String>,
    pub amount: FloatMajorUnit,
    pub currency: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum IatapayWebhookResponse {
    IatapayPaymentWebhookBody(IatapayPaymentWebhookBody),
    IatapayRefundWebhookBody(IatapayRefundWebhookBody),
}

impl TryFrom<IatapayWebhookResponse> for IncomingWebhookEvent {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(payload: IatapayWebhookResponse) -> CustomResult<Self, errors::ConnectorError> {
        match payload {
            IatapayWebhookResponse::IatapayPaymentWebhookBody(wh_body) => match wh_body.status {
                IatapayWebhookStatus::Authorized | IatapayWebhookStatus::Settled => {
                    Ok(Self::PaymentIntentSuccess)
                }
                IatapayWebhookStatus::Initiated => Ok(Self::PaymentIntentProcessing),
                IatapayWebhookStatus::Failed => Ok(Self::PaymentIntentFailure),
                IatapayWebhookStatus::Created
                | IatapayWebhookStatus::Cleared
                | IatapayWebhookStatus::Tobeinvestigated
                | IatapayWebhookStatus::Blocked
                | IatapayWebhookStatus::UnexpectedSettled
                | IatapayWebhookStatus::Unknown => Ok(Self::EventNotSupported),
            },
            IatapayWebhookResponse::IatapayRefundWebhookBody(wh_body) => match wh_body.status {
                IatapayRefundWebhookStatus::Cleared
                | IatapayRefundWebhookStatus::Authorized
                | IatapayRefundWebhookStatus::Settled => Ok(Self::RefundSuccess),
                IatapayRefundWebhookStatus::Failed => Ok(Self::RefundFailure),
                IatapayRefundWebhookStatus::Created
                | IatapayRefundWebhookStatus::Locked
                | IatapayRefundWebhookStatus::Initiated
                | IatapayRefundWebhookStatus::Unknown => Ok(Self::EventNotSupported),
            },
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum IatapayWebhookStatus {
    Created,
    Initiated,
    Authorized,
    Settled,
    Cleared,
    Failed,
    Tobeinvestigated,
    Blocked,
    #[serde(rename = "UNEXPECTED SETTLED")]
    UnexpectedSettled,
    #[serde(other)]
    Unknown,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum IatapayRefundWebhookStatus {
    Created,
    Initiated,
    Authorized,
    Settled,
    Failed,
    Cleared,
    Locked,
    #[serde(other)]
    Unknown,
}


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

use api_models::webhooks::IncomingWebhookEvent;
use common_enums::enums;
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{BankDebitData, Card, PayLaterData, PaymentMethodData, WalletData},
    router_data::{ConnectorAuthType, PaymentMethodToken, RouterData},
    router_flow_types::{refunds::Execute, RSync},
    router_request_types::ResponseId,
    router_response_types::{PaymentsResponseData, RefundsResponseData},
    types,
};
use hyperswitch_interfaces::errors;
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};

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

impl TryFrom<(&types::TokenizationRouterData, BankDebitData)> for SquareTokenRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: (&types::TokenizationRouterData, BankDebitData),
    ) -> Result<Self, Self::Error> {
        let (_item, bank_debit_data) = value;
        match bank_debit_data {
            BankDebitData::AchBankDebit { .. }
            | BankDebitData::SepaBankDebit { .. }
            | BankDebitData::BecsBankDebit { .. }
            | BankDebitData::BacsBankDebit { .. }
            | BankDebitData::SepaGuarenteedBankDebit { .. } => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Square"),
                ))?
            }
        }
    }
}

impl TryFrom<(&types::TokenizationRouterData, Card)> for SquareTokenRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: (&types::TokenizationRouterData, Card)) -> Result<Self, Self::Error> {
        let (item, card_data) = value;
        let auth = SquareAuthType::try_from(&item.connector_auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        let exp_year = Secret::new(
            card_data
                .get_expiry_year_4_digit()
                .peek()
                .parse::<u16>()
                .change_context(errors::ConnectorError::DateFormattingFailed)?,
        );
        let exp_month = Secret::new(
            card_data
                .card_exp_month
                .peek()
                .parse::<u16>()
                .change_context(errors::ConnectorError::DateFormattingFailed)?,
        );
        //The below error will never happen because if session-id is not generated it would give error in execute_pretasks itself.
        let session_id = Secret::new(
            item.session_token
                .clone()
                .ok_or(errors::ConnectorError::RequestEncodingFailed)?,
        );
        Ok(Self::Card(SquareTokenizeData {
            client_id: auth.key1,
            session_id,
            card_data: SquareCardData {
                exp_year,
                exp_month,
                number: card_data.card_number,
                cvv: card_data.card_cvc,
            },
        }))
    }
}

impl TryFrom<(&types::TokenizationRouterData, PayLaterData)> for SquareTokenRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: (&types::TokenizationRouterData, PayLaterData),
    ) -> Result<Self, Self::Error> {
        let (_item, pay_later_data) = value;
        match pay_later_data {
            PayLaterData::AfterpayClearpayRedirect { .. }
            | PayLaterData::KlarnaRedirect { .. }
            | PayLaterData::KlarnaSdk { .. }
            | PayLaterData::AffirmRedirect { .. }
            | PayLaterData::PayBrightRedirect { .. }
            | PayLaterData::WalleyRedirect { .. }
            | PayLaterData::AlmaRedirect { .. }
            | PayLaterData::FlexitiRedirect { .. }
            | PayLaterData::AtomeRedirect { .. }
            | PayLaterData::BreadpayRedirect { .. }
            | PayLaterData::PayjustnowRedirect { .. } => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Square"),
                ))?
            }
        }
    }
}

impl TryFrom<(&types::TokenizationRouterData, WalletData)> for SquareTokenRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: (&types::TokenizationRouterData, WalletData)) -> Result<Self, Self::Error> {
        let (_item, wallet_data) = value;
        match wallet_data {
            WalletData::AmazonPay(_)
            | WalletData::ApplePay(_)
            | WalletData::GooglePay(_)
            | WalletData::BluecodeRedirect {}
            | WalletData::AliPayQr(_)
            | WalletData::AliPayRedirect(_)
            | WalletData::AliPayHkRedirect(_)
            | WalletData::AmazonPayRedirect(_)
            | WalletData::Paysera(_)
            | WalletData::Skrill(_)
            | WalletData::MomoRedirect(_)
            | WalletData::KakaoPayRedirect(_)
            | WalletData::GoPayRedirect(_)
            | WalletData::GcashRedirect(_)
            | WalletData::ApplePayRedirect(_)
            | WalletData::ApplePayThirdPartySdk(_)
            | WalletData::DanaRedirect {}
            | WalletData::GooglePayRedirect(_)
            | WalletData::GooglePayThirdPartySdk(_)
            | WalletData::MbWayRedirect(_)
            | WalletData::MobilePayRedirect(_)
            | WalletData::PaypalRedirect(_)
            | 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("Square"),
            ))?,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct SquareCardData {
    cvv: Secret<String>,
    exp_month: Secret<u16>,
    exp_year: Secret<u16>,
    number: cards::CardNumber,
}
#[derive(Debug, Serialize)]
pub struct SquareTokenizeData {
    client_id: Secret<String>,
    session_id: Secret<String>,
    card_data: SquareCardData,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum SquareTokenRequest {
    Card(SquareTokenizeData),
}

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

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

impl<F, T> TryFrom<ResponseRouterData<F, SquareSessionResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, SquareSessionResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            status: enums::AttemptStatus::Pending,
            session_token: Some(item.response.session_id.clone().expose()),
            response: Ok(PaymentsResponseData::SessionTokenResponse {
                session_token: item.response.session_id.expose(),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SquareTokenResponse {
    card_nonce: Secret<String>,
}

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

#[derive(Debug, Deserialize, Serialize)]
pub struct SquarePaymentsAmountData {
    amount: i64,
    currency: enums::Currency,
}
#[derive(Debug, Serialize)]
pub struct SquarePaymentsRequestExternalDetails {
    source: String,
    #[serde(rename = "type")]
    source_type: String,
}

#[derive(Debug, Serialize)]
pub struct SquarePaymentsRequest {
    amount_money: SquarePaymentsAmountData,
    idempotency_key: Secret<String>,
    source_id: Secret<String>,
    autocomplete: bool,
    external_details: SquarePaymentsRequestExternalDetails,
}

impl TryFrom<&types::PaymentsAuthorizeRouterData> for SquarePaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
        let autocomplete = item.request.is_auto_capture()?;
        match item.request.payment_method_data.clone() {
            PaymentMethodData::Card(_) => {
                let pm_token = item.get_payment_method_token()?;
                Ok(Self {
                    idempotency_key: Secret::new(item.attempt_id.clone()),
                    source_id: match pm_token {
                        PaymentMethodToken::Token(token) => token,
                        PaymentMethodToken::ApplePayDecrypt(_) => Err(
                            unimplemented_payment_method!("Apple Pay", "Simplified", "Square"),
                        )?,
                        PaymentMethodToken::PazeDecrypt(_) => {
                            Err(unimplemented_payment_method!("Paze", "Square"))?
                        }
                        PaymentMethodToken::GooglePayDecrypt(_) => {
                            Err(unimplemented_payment_method!("Google Pay", "Square"))?
                        }
                    },
                    amount_money: SquarePaymentsAmountData {
                        amount: item.request.amount,
                        currency: item.request.currency,
                    },
                    autocomplete,
                    external_details: SquarePaymentsRequestExternalDetails {
                        source: "Hyperswitch".to_string(),
                        source_type: "Card".to_string(),
                    },
                })
            }
            PaymentMethodData::BankDebit(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankTransfer(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Square"),
                ))?
            }
        }
    }
}

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

impl TryFrom<&ConnectorAuthType> for SquareAuthType {
    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 {
                api_key: api_key.to_owned(),
                key1: key1.to_owned(),
            }),
            ConnectorAuthType::HeaderKey { .. }
            | ConnectorAuthType::SignatureKey { .. }
            | ConnectorAuthType::MultiAuthKey { .. }
            | ConnectorAuthType::CurrencyAuthKey { .. }
            | ConnectorAuthType::TemporaryAuth
            | ConnectorAuthType::NoKey
            | ConnectorAuthType::CertificateAuth { .. } => {
                Err(errors::ConnectorError::FailedToObtainAuthType.into())
            }
        }
    }
}
// PaymentsResponse
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum SquarePaymentStatus {
    Completed,
    Failed,
    Approved,
    Canceled,
    Pending,
}

impl From<SquarePaymentStatus> for enums::AttemptStatus {
    fn from(item: SquarePaymentStatus) -> Self {
        match item {
            SquarePaymentStatus::Completed => Self::Charged,
            SquarePaymentStatus::Approved => Self::Authorized,
            SquarePaymentStatus::Failed => Self::Failure,
            SquarePaymentStatus::Canceled => Self::Voided,
            SquarePaymentStatus::Pending => Self::Pending,
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct SquarePaymentsResponseDetails {
    status: SquarePaymentStatus,
    id: String,
    amount_money: SquarePaymentsAmountData,
    reference_id: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct SquarePaymentsResponse {
    payment: SquarePaymentsResponseDetails,
}

impl<F, T> TryFrom<ResponseRouterData<F, SquarePaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, SquarePaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        //Since this try_from is being used in Authorize, Sync, Capture & Void flow. Field amount_captured should only be updated in case of Charged status.
        let status = enums::AttemptStatus::from(item.response.payment.status);
        let mut amount_captured = None;
        if status == enums::AttemptStatus::Charged {
            amount_captured = Some(item.response.payment.amount_money.amount)
        };
        Ok(Self {
            status,
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.payment.id),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item.response.payment.reference_id,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            amount_captured,
            ..item.data
        })
    }
}

// REFUND :
// Type definition for RefundRequest
#[derive(Debug, Serialize)]
pub struct SquareRefundRequest {
    amount_money: SquarePaymentsAmountData,
    idempotency_key: Secret<String>,
    payment_id: Secret<String>,
}

impl<F> TryFrom<&types::RefundsRouterData<F>> for SquareRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
        Ok(Self {
            amount_money: SquarePaymentsAmountData {
                amount: item.request.refund_amount,
                currency: item.request.currency,
            },
            idempotency_key: Secret::new(item.request.refund_id.clone()),
            payment_id: Secret::new(item.request.connector_transaction_id.clone()),
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum RefundStatus {
    Completed,
    Failed,
    Pending,
    Rejected,
}

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

#[derive(Debug, Deserialize, Serialize)]
pub struct SquareRefundResponseDetails {
    status: RefundStatus,
    id: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct RefundResponse {
    refund: SquareRefundResponseDetails,
}

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

impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for types::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.refund.id,
                refund_status: enums::RefundStatus::from(item.response.refund.status),
            }),
            ..item.data
        })
    }
}

#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct SquareErrorDetails {
    pub category: Option<String>,
    pub code: Option<String>,
    pub detail: Option<String>,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct SquareErrorResponse {
    pub errors: Vec<SquareErrorDetails>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SquareWebhookObject {
    Payment(SquarePaymentsResponseDetails),
    Refund(SquareRefundResponseDetails),
}

#[derive(Debug, Deserialize)]
pub struct SquareWebhookData {
    pub id: String,
    pub object: SquareWebhookObject,
}

#[derive(Debug, Deserialize)]
pub struct SquareWebhookBody {
    #[serde(rename = "type")]
    pub webhook_type: String,
    pub data: SquareWebhookData,
}

impl From<SquareWebhookObject> for IncomingWebhookEvent {
    fn from(item: SquareWebhookObject) -> Self {
        match item {
            SquareWebhookObject::Payment(payment_data) => match payment_data.status {
                SquarePaymentStatus::Completed => Self::PaymentIntentSuccess,
                SquarePaymentStatus::Failed => Self::PaymentIntentFailure,
                SquarePaymentStatus::Pending => Self::PaymentIntentProcessing,
                SquarePaymentStatus::Approved | SquarePaymentStatus::Canceled => {
                    Self::EventNotSupported
                }
            },
            SquareWebhookObject::Refund(refund_data) => match refund_data.status {
                RefundStatus::Completed => Self::RefundSuccess,
                RefundStatus::Failed | RefundStatus::Rejected => Self::RefundFailure,
                RefundStatus::Pending => Self::EventNotSupported,
            },
        }
    }
}


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

#[cfg(feature = "payouts")]
use api_models::payouts::PayoutMethodData;
#[cfg(feature = "payouts")]
use cards::CardNumber;
#[cfg(feature = "payouts")]
use common_enums::{PayoutStatus, PayoutType};
use common_utils::types::MinorUnit;
#[cfg(feature = "payouts")]
use common_utils::{ext_traits::OptionExt, transformers::ForeignFrom};
#[cfg(feature = "payouts")]
use error_stack::ResultExt;
use hyperswitch_domain_models::router_data::ConnectorAuthType;
#[cfg(feature = "payouts")]
use hyperswitch_domain_models::{
    router_flow_types::PoFulfill,
    types::{PayoutsResponseData, PayoutsRouterData},
};
use hyperswitch_interfaces::errors::ConnectorError;
use masking::Secret;
use serde::{Deserialize, Serialize};

#[cfg(feature = "payouts")]
use crate::utils::CardData as _;
use crate::utils::{
    get_unimplemented_payment_method_error_message, CardIssuer, ErrorCodeAndMessage,
};
#[cfg(feature = "payouts")]
use crate::{
    types::PayoutsResponseRouterData,
    utils::{PayoutsData, RouterData},
};
#[cfg(feature = "payouts")]
type Error = error_stack::Report<ConnectorError>;
use serde_repr::{Deserialize_repr, Serialize_repr};
pub struct PayoneRouterData<T> {
    pub amount: MinorUnit,
    pub router_data: T,
}

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

#[derive(Debug, Deserialize, Serialize)]
pub struct ErrorResponse {
    pub errors: Option<Vec<SubError>>,
    pub error_id: Option<i32>,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct SubError {
    pub code: String,
    pub message: String,
    pub http_status_code: u16,
}

impl From<SubError> for ErrorCodeAndMessage {
    fn from(error: SubError) -> Self {
        Self {
            error_code: error.code.to_string(),
            error_message: error.code.to_string(),
        }
    }
}
// Auth Struct
pub struct PayoneAuthType {
    pub(super) api_key: Secret<String>,
    pub merchant_account: Secret<String>,
    pub api_secret: Secret<String>,
}

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

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayonePayoutFulfillRequest {
    amount_of_money: AmountOfMoney,
    card_payout_method_specific_input: CardPayoutMethodSpecificInput,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AmountOfMoney {
    amount: MinorUnit,
    currency_code: String,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CardPayoutMethodSpecificInput {
    card: Card,
    payment_product_id: PaymentProductId,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Card {
    card_holder_name: Secret<String>,
    card_number: CardNumber,
    expiry_date: Secret<String>,
}

#[cfg(feature = "payouts")]
impl TryFrom<PayoneRouterData<&PayoutsRouterData<PoFulfill>>> for PayonePayoutFulfillRequest {
    type Error = Error;
    fn try_from(
        item: PayoneRouterData<&PayoutsRouterData<PoFulfill>>,
    ) -> Result<Self, Self::Error> {
        let request = item.router_data.request.to_owned();
        let payout_type = request.get_payout_type()?;
        match payout_type {
            PayoutType::Card => {
                let amount_of_money: AmountOfMoney = AmountOfMoney {
                    amount: item.amount,
                    currency_code: item.router_data.request.destination_currency.to_string(),
                };
                let card_payout_method_specific_input =
                    match item.router_data.get_payout_method_data()? {
                        PayoutMethodData::Card(card_data) => CardPayoutMethodSpecificInput {
                            card: Card {
                                card_number: card_data.card_number.clone(),
                                card_holder_name: card_data
                                    .card_holder_name
                                    .clone()
                                    .get_required_value("card_holder_name")
                                    .change_context(ConnectorError::MissingRequiredField {
                                        field_name: "payout_method_data.card.holder_name",
                                    })?,
                                expiry_date: card_data
                                    .get_card_expiry_month_year_2_digit_with_delimiter(
                                        "".to_string(),
                                    )?,
                            },
                            payment_product_id: PaymentProductId::try_from(
                                card_data.get_card_issuer()?,
                            )?,
                        },
                        PayoutMethodData::Bank(_)
                        | PayoutMethodData::Wallet(_)
                        | PayoutMethodData::BankRedirect(_)
                        | PayoutMethodData::Passthrough(_) => Err(ConnectorError::NotImplemented(
                            get_unimplemented_payment_method_error_message("Payone"),
                        ))?,
                    };
                Ok(Self {
                    amount_of_money,
                    card_payout_method_specific_input,
                })
            }
            PayoutType::Wallet | PayoutType::Bank | PayoutType::BankRedirect => {
                Err(ConnectorError::NotImplemented(
                    get_unimplemented_payment_method_error_message("Payone"),
                ))?
            }
        }
    }
}

#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum PaymentProductId {
    Visa = 1,
    MasterCard = 3,
}

impl TryFrom<CardIssuer> for PaymentProductId {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(issuer: CardIssuer) -> Result<Self, Self::Error> {
        match issuer {
            CardIssuer::Master => Ok(Self::MasterCard),
            CardIssuer::Visa => Ok(Self::Visa),
            CardIssuer::AmericanExpress
            | CardIssuer::Maestro
            | CardIssuer::Discover
            | CardIssuer::DinersClub
            | CardIssuer::JCB
            | CardIssuer::CarteBlanche
            | CardIssuer::UnionPay
            | CardIssuer::CartesBancaires => Err(ConnectorError::NotImplemented(
                get_unimplemented_payment_method_error_message("payone"),
            )
            .into()),
        }
    }
}

#[cfg(feature = "payouts")]
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PayoneStatus {
    Created,
    #[default]
    PendingApproval,
    Rejected,
    PayoutRequested,
    AccountCredited,
    RejectedCredit,
    Cancelled,
    Reversed,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayonePayoutFulfillResponse {
    id: String,
    payout_output: PayoutOutput,
    status: PayoneStatus,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PayoutOutput {
    amount_of_money: AmountOfMoney,
}

#[cfg(feature = "payouts")]
impl ForeignFrom<PayoneStatus> for PayoutStatus {
    fn foreign_from(payone_status: PayoneStatus) -> Self {
        match payone_status {
            PayoneStatus::AccountCredited => Self::Success,
            PayoneStatus::RejectedCredit | PayoneStatus::Rejected => Self::Failed,
            PayoneStatus::Cancelled | PayoneStatus::Reversed => Self::Cancelled,
            PayoneStatus::Created
            | PayoneStatus::PendingApproval
            | PayoneStatus::PayoutRequested => Self::Pending,
        }
    }
}

#[cfg(feature = "payouts")]
impl<F> TryFrom<PayoutsResponseRouterData<F, PayonePayoutFulfillResponse>>
    for PayoutsRouterData<F>
{
    type Error = Error;
    fn try_from(
        item: PayoutsResponseRouterData<F, PayonePayoutFulfillResponse>,
    ) -> Result<Self, Self::Error> {
        let response: PayonePayoutFulfillResponse = item.response;

        Ok(Self {
            response: Ok(PayoutsResponseData {
                status: Some(PayoutStatus::foreign_from(response.status)),
                connector_payout_id: Some(response.id),
                payout_eligible: None,
                should_add_next_step_to_process_tracker: false,
                error_code: None,
                error_message: None,
                payout_connector_metadata: None,
            }),
            ..item.data
        })
    }
}