use api_models::webhooks::IncomingWebhookEvent;
use common_enums::{AttemptStatus, Currency, FraudCheckStatus, PaymentMethod};
use common_utils::{ext_traits::ValueExt, pii::Email};
use error_stack::{self, ResultExt};
pub use hyperswitch_domain_models::router_request_types::fraud_check::RefundMethod;
use hyperswitch_domain_models::{
    router_data::RouterData,
    router_flow_types::Fulfillment,
    router_request_types::{
        fraud_check::{self, FraudCheckFulfillmentData, FrmFulfillmentRequest},
        ResponseId,
    },
    router_response_types::fraud_check::FraudCheckResponseData,
};
use hyperswitch_interfaces::errors::ConnectorError;
use masking::Secret;
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use utoipa::ToSchema;

use crate::{
    types::{
        FrmCheckoutRouterData, FrmFulfillmentRouterData, FrmRecordReturnRouterData,
        FrmSaleRouterData, FrmTransactionRouterData, ResponseRouterData,
    },
    utils::{
        AddressDetailsData as _, FraudCheckCheckoutRequest, FraudCheckRecordReturnRequest as _,
        FraudCheckSaleRequest as _, FraudCheckTransactionRequest as _, RouterData as _,
    },
};
#[allow(dead_code)]
#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DecisionDelivery {
    Sync,
    AsyncOnly,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Purchase {
    #[serde(with = "common_utils::custom_serde::iso8601")]
    created_at: PrimitiveDateTime,
    order_channel: OrderChannel,
    total_price: i64,
    products: Vec<Products>,
    shipments: Shipments,
    currency: Option<Currency>,
    total_shipping_cost: Option<i64>,
    confirmation_email: Option<Email>,
    confirmation_phone: Option<Secret<String>>,
}

#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)]
#[serde(rename_all(serialize = "SCREAMING_SNAKE_CASE", deserialize = "snake_case"))]
pub enum OrderChannel {
    Web,
    Phone,
    MobileApp,
    Social,
    Marketplace,
    InStoreKiosk,
    ScanAndGo,
    SmartTv,
    Mit,
}

#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)]
#[serde(rename_all(serialize = "SCREAMING_SNAKE_CASE", deserialize = "snake_case"))]
pub enum FulfillmentMethod {
    Delivery,
    CounterPickup,
    CubsidePickup,
    LockerPickup,
    StandardShipping,
    ExpeditedShipping,
    GasPickup,
    ScheduledDelivery,
}

#[derive(Debug, Serialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Products {
    item_name: String,
    item_price: i64,
    item_quantity: i32,
    item_id: Option<String>,
    item_category: Option<String>,
    item_sub_category: Option<String>,
    item_is_digital: Option<bool>,
}

#[derive(Debug, Serialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Shipments {
    destination: Destination,
    fulfillment_method: Option<FulfillmentMethod>,
}

#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Destination {
    full_name: Secret<String>,
    organization: Option<String>,
    email: Option<Email>,
    address: Address,
}

#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Address {
    street_address: Secret<String>,
    unit: Option<Secret<String>>,
    postal_code: Secret<String>,
    city: String,
    province_code: Secret<String>,
    country_code: common_enums::CountryAlpha2,
}

#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)]
#[serde(rename_all(serialize = "SCREAMING_SNAKE_CASE", deserialize = "snake_case"))]
pub enum CoverageRequests {
    Fraud, // use when you need a financial guarantee for Payment Fraud.
    Inr,   // use when you need a financial guarantee for Item Not Received.
    Snad, // use when you need a financial guarantee for fraud alleging items are Significantly Not As Described.
    All,  // use when you need a financial guarantee on all chargebacks.
    None, // use when you do not need a financial guarantee. Suggested actions in decision.checkpointAction are recommendations.
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SignifydPaymentsSaleRequest {
    order_id: String,
    purchase: Purchase,
    decision_delivery: DecisionDelivery,
    coverage_requests: Option<CoverageRequests>,
}

#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)]
#[serde(rename_all(serialize = "camelCase", deserialize = "snake_case"))]
pub struct SignifydFrmMetadata {
    pub total_shipping_cost: Option<i64>,
    pub fulfillment_method: Option<FulfillmentMethod>,
    pub coverage_request: Option<CoverageRequests>,
    pub order_channel: OrderChannel,
}

impl TryFrom<&FrmSaleRouterData> for SignifydPaymentsSaleRequest {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(item: &FrmSaleRouterData) -> Result<Self, Self::Error> {
        let products = item
            .request
            .get_order_details()?
            .iter()
            .map(|order_detail| Products {
                item_name: order_detail.product_name.clone(),
                item_price: order_detail.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future.
                item_quantity: i32::from(order_detail.quantity),
                item_id: order_detail.product_id.clone(),
                item_category: order_detail.category.clone(),
                item_sub_category: order_detail.sub_category.clone(),
                item_is_digital: order_detail
                    .product_type
                    .as_ref()
                    .map(|product| product == &common_enums::ProductType::Digital),
            })
            .collect::<Vec<_>>();
        let metadata: SignifydFrmMetadata = item
            .frm_metadata
            .clone()
            .ok_or(ConnectorError::MissingRequiredField {
                field_name: "frm_metadata",
            })?
            .parse_value("Signifyd Frm Metadata")
            .change_context(ConnectorError::InvalidDataFormat {
                field_name: "frm_metadata",
            })?;
        let ship_address = item.get_shipping_address()?;
        let billing_address = item.get_billing()?;
        let street_addr = ship_address.get_line1()?;
        let city_addr = ship_address.get_city()?;
        let zip_code_addr = ship_address.get_zip()?;
        let country_code_addr = ship_address.get_country()?;
        let _first_name_addr = ship_address.get_first_name()?;
        let _last_name_addr = ship_address.get_last_name()?;
        let address: Address = Address {
            street_address: street_addr.clone(),
            unit: None,
            postal_code: zip_code_addr.clone(),
            city: city_addr.clone(),
            province_code: zip_code_addr.clone(),
            country_code: country_code_addr.to_owned(),
        };
        let destination: Destination = Destination {
            full_name: ship_address.get_full_name().unwrap_or_default(),
            organization: None,
            email: None,
            address,
        };

        let created_at = common_utils::date_time::now();
        let order_channel = metadata.order_channel;
        let shipments = Shipments {
            destination,
            fulfillment_method: metadata.fulfillment_method,
        };
        let purchase = Purchase {
            created_at,
            order_channel,
            total_price: item.request.amount,
            products,
            shipments,
            currency: item.request.currency,
            total_shipping_cost: metadata.total_shipping_cost,
            confirmation_email: item.request.email.clone(),
            confirmation_phone: billing_address
                .clone()
                .phone
                .and_then(|phone_data| phone_data.number),
        };
        Ok(Self {
            order_id: item.attempt_id.clone(),
            purchase,
            decision_delivery: DecisionDelivery::Sync, // Specify SYNC if you require the Response to contain a decision field. If you have registered for a webhook associated with this checkpoint, then the webhook will also be sent when SYNC is specified. If ASYNC_ONLY is specified, then the decision field in the response will be null, and you will require a Webhook integration to receive Signifyd's final decision
            coverage_requests: metadata.coverage_request,
        })
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Decision {
    #[serde(with = "common_utils::custom_serde::iso8601")]
    created_at: PrimitiveDateTime,
    checkpoint_action: SignifydPaymentStatus,
    checkpoint_action_reason: Option<String>,
    checkpoint_action_policy: Option<String>,
    score: Option<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SignifydPaymentStatus {
    Accept,
    Challenge,
    Credit,
    Hold,
    Reject,
}

impl From<SignifydPaymentStatus> for FraudCheckStatus {
    fn from(item: SignifydPaymentStatus) -> Self {
        match item {
            SignifydPaymentStatus::Accept => Self::Legit,
            SignifydPaymentStatus::Reject => Self::Fraud,
            SignifydPaymentStatus::Hold => Self::ManualReview,
            SignifydPaymentStatus::Challenge | SignifydPaymentStatus::Credit => Self::Pending,
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SignifydPaymentsResponse {
    signifyd_id: i64,
    order_id: String,
    decision: Decision,
}

impl<F, T> TryFrom<ResponseRouterData<F, SignifydPaymentsResponse, T, FraudCheckResponseData>>
    for RouterData<F, T, FraudCheckResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, SignifydPaymentsResponse, T, FraudCheckResponseData>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(FraudCheckResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.order_id),
                status: FraudCheckStatus::from(item.response.decision.checkpoint_action),
                connector_metadata: None,
                score: item.response.decision.score.and_then(|data| data.to_i32()),
                reason: item
                    .response
                    .decision
                    .checkpoint_action_reason
                    .map(serde_json::Value::from),
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct SignifydErrorResponse {
    pub messages: Vec<String>,
    pub errors: serde_json::Value,
}

#[derive(Debug, Serialize, Eq, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Transactions {
    transaction_id: String,
    gateway_status_code: String,
    payment_method: PaymentMethod,
    amount: i64,
    currency: Currency,
    gateway: Option<String>,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SignifydPaymentsTransactionRequest {
    order_id: String,
    checkout_id: String,
    transactions: Transactions,
}

impl From<AttemptStatus> for GatewayStatusCode {
    fn from(item: AttemptStatus) -> Self {
        match item {
            AttemptStatus::Pending => Self::Pending,
            AttemptStatus::Failure => Self::Failure,
            AttemptStatus::Charged => Self::Success,
            _ => Self::Pending,
        }
    }
}

impl TryFrom<&FrmTransactionRouterData> for SignifydPaymentsTransactionRequest {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(item: &FrmTransactionRouterData) -> Result<Self, Self::Error> {
        let currency = item.request.get_currency()?;
        let transactions = Transactions {
            amount: item.request.amount,
            transaction_id: item.clone().payment_id,
            gateway_status_code: GatewayStatusCode::from(item.status).to_string(),
            payment_method: item.payment_method,
            currency,
            gateway: item.request.connector.clone(),
        };
        Ok(Self {
            order_id: item.attempt_id.clone(),
            checkout_id: item.payment_id.clone(),
            transactions,
        })
    }
}

#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    Eq,
    PartialEq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumString,
)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum GatewayStatusCode {
    Success,
    Failure,
    #[default]
    Pending,
    Error,
    Cancelled,
    Expired,
    SoftDecline,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SignifydPaymentsCheckoutRequest {
    checkout_id: String,
    order_id: String,
    purchase: Purchase,
    coverage_requests: Option<CoverageRequests>,
}

impl TryFrom<&FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequest {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(item: &FrmCheckoutRouterData) -> Result<Self, Self::Error> {
        let products = item
            .request
            .get_order_details()?
            .iter()
            .map(|order_detail| Products {
                item_name: order_detail.product_name.clone(),
                item_price: order_detail.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future.
                item_quantity: i32::from(order_detail.quantity),
                item_id: order_detail.product_id.clone(),
                item_category: order_detail.category.clone(),
                item_sub_category: order_detail.sub_category.clone(),
                item_is_digital: order_detail
                    .product_type
                    .as_ref()
                    .map(|product| product == &common_enums::ProductType::Digital),
            })
            .collect::<Vec<_>>();
        let metadata: SignifydFrmMetadata = item
            .frm_metadata
            .clone()
            .ok_or(ConnectorError::MissingRequiredField {
                field_name: "frm_metadata",
            })?
            .parse_value("Signifyd Frm Metadata")
            .change_context(ConnectorError::InvalidDataFormat {
                field_name: "frm_metadata",
            })?;
        let ship_address = item.get_shipping_address()?;
        let street_addr = ship_address.get_line1()?;
        let city_addr = ship_address.get_city()?;
        let zip_code_addr = ship_address.get_zip()?;
        let country_code_addr = ship_address.get_country()?;
        let _first_name_addr = ship_address.get_first_name()?;
        let _last_name_addr = ship_address.get_last_name()?;
        let billing_address = item.get_billing()?;
        let address: Address = Address {
            street_address: street_addr.clone(),
            unit: None,
            postal_code: zip_code_addr.clone(),
            city: city_addr.clone(),
            province_code: zip_code_addr.clone(),
            country_code: country_code_addr.to_owned(),
        };
        let destination: Destination = Destination {
            full_name: ship_address.get_full_name().unwrap_or_default(),
            organization: None,
            email: None,
            address,
        };
        let created_at = common_utils::date_time::now();
        let order_channel = metadata.order_channel;
        let shipments: Shipments = Shipments {
            destination,
            fulfillment_method: metadata.fulfillment_method,
        };
        let purchase = Purchase {
            created_at,
            order_channel,
            total_price: item.request.amount,
            products,
            shipments,
            currency: item.request.currency,
            total_shipping_cost: metadata.total_shipping_cost,
            confirmation_email: item.request.email.clone(),
            confirmation_phone: billing_address
                .clone()
                .phone
                .and_then(|phone_data| phone_data.number),
        };
        Ok(Self {
            checkout_id: item.payment_id.clone(),
            order_id: item.attempt_id.clone(),
            purchase,
            coverage_requests: metadata.coverage_request,
        })
    }
}

#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)]
#[serde(deny_unknown_fields)]
#[serde_with::skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub struct FrmFulfillmentSignifydRequest {
    pub order_id: String,
    pub fulfillment_status: Option<FulfillmentStatus>,
    pub fulfillments: Vec<Fulfillments>,
}

#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(untagged)]
#[serde_with::skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub enum FulfillmentStatus {
    PARTIAL,
    COMPLETE,
    REPLACEMENT,
    CANCELED,
}

#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde_with::skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub struct Fulfillments {
    pub shipment_id: String,
    pub products: Option<Vec<Product>>,
    pub destination: Destination,
    pub fulfillment_method: Option<String>,
    pub carrier: Option<String>,
    pub shipment_status: Option<String>,
    pub tracking_urls: Option<Vec<String>>,
    pub tracking_numbers: Option<Vec<String>>,
    pub shipped_at: Option<String>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde_with::skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub struct Product {
    pub item_name: String,
    pub item_quantity: i64,
    pub item_id: String,
}

impl TryFrom<&FrmFulfillmentRouterData> for FrmFulfillmentSignifydRequest {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(item: &FrmFulfillmentRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            order_id: item.request.fulfillment_req.order_id.clone(),
            fulfillment_status: item
                .request
                .fulfillment_req
                .fulfillment_status
                .as_ref()
                .map(|fulfillment_status| FulfillmentStatus::from(&fulfillment_status.clone())),
            fulfillments: get_signifyd_fulfillments_from_frm_fulfillment_request(
                &item.request.fulfillment_req,
            ),
        })
    }
}

impl From<&fraud_check::FulfillmentStatus> for FulfillmentStatus {
    fn from(status: &fraud_check::FulfillmentStatus) -> Self {
        match status {
            fraud_check::FulfillmentStatus::PARTIAL => Self::PARTIAL,
            fraud_check::FulfillmentStatus::COMPLETE => Self::COMPLETE,
            fraud_check::FulfillmentStatus::REPLACEMENT => Self::REPLACEMENT,
            fraud_check::FulfillmentStatus::CANCELED => Self::CANCELED,
        }
    }
}
pub(crate) fn get_signifyd_fulfillments_from_frm_fulfillment_request(
    fulfillment_req: &FrmFulfillmentRequest,
) -> Vec<Fulfillments> {
    fulfillment_req
        .fulfillments
        .iter()
        .map(|fulfillment| Fulfillments {
            shipment_id: fulfillment.shipment_id.clone(),
            products: fulfillment
                .products
                .as_ref()
                .map(|products| products.iter().map(|p| Product::from(p.clone())).collect()),
            destination: Destination::from(fulfillment.destination.clone()),
            tracking_urls: fulfillment_req.tracking_urls.clone(),
            tracking_numbers: fulfillment_req.tracking_numbers.clone(),
            fulfillment_method: fulfillment_req.fulfillment_method.clone(),
            carrier: fulfillment_req.carrier.clone(),
            shipment_status: fulfillment_req.shipment_status.clone(),
            shipped_at: fulfillment_req.shipped_at.clone(),
        })
        .collect()
}

impl From<fraud_check::Product> for Product {
    fn from(product: fraud_check::Product) -> Self {
        Self {
            item_name: product.item_name,
            item_quantity: product.item_quantity,
            item_id: product.item_id,
        }
    }
}

impl From<fraud_check::Destination> for Destination {
    fn from(destination: fraud_check::Destination) -> Self {
        Self {
            full_name: destination.full_name,
            organization: destination.organization,
            email: destination.email,
            address: Address::from(destination.address),
        }
    }
}

impl From<fraud_check::Address> for Address {
    fn from(address: fraud_check::Address) -> Self {
        Self {
            street_address: address.street_address,
            unit: address.unit,
            postal_code: address.postal_code,
            city: address.city,
            province_code: address.province_code,
            country_code: address.country_code,
        }
    }
}

#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)]
#[serde_with::skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub struct FrmFulfillmentSignifydApiResponse {
    pub order_id: String,
    pub shipment_ids: Vec<String>,
}

impl
    TryFrom<
        ResponseRouterData<
            Fulfillment,
            FrmFulfillmentSignifydApiResponse,
            FraudCheckFulfillmentData,
            FraudCheckResponseData,
        >,
    > for RouterData<Fulfillment, FraudCheckFulfillmentData, FraudCheckResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            Fulfillment,
            FrmFulfillmentSignifydApiResponse,
            FraudCheckFulfillmentData,
            FraudCheckResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(FraudCheckResponseData::FulfillmentResponse {
                order_id: item.response.order_id,
                shipment_ids: item.response.shipment_ids,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize, Eq, PartialEq, Clone)]
#[serde_with::skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub struct SignifydRefund {
    method: RefundMethod,
    amount: String,
    currency: Currency,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde_with::skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub struct SignifydPaymentsRecordReturnRequest {
    order_id: String,
    return_id: String,
    refund_transaction_id: Option<String>,
    refund: SignifydRefund,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde_with::skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub struct SignifydPaymentsRecordReturnResponse {
    return_id: String,
    order_id: String,
}

impl<F, T>
    TryFrom<ResponseRouterData<F, SignifydPaymentsRecordReturnResponse, T, FraudCheckResponseData>>
    for RouterData<F, T, FraudCheckResponseData>
{
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            SignifydPaymentsRecordReturnResponse,
            T,
            FraudCheckResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(FraudCheckResponseData::RecordReturnResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.response.order_id),
                return_id: Some(item.response.return_id.to_string()),
                connector_metadata: None,
            }),
            ..item.data
        })
    }
}

impl TryFrom<&FrmRecordReturnRouterData> for SignifydPaymentsRecordReturnRequest {
    type Error = error_stack::Report<ConnectorError>;
    fn try_from(item: &FrmRecordReturnRouterData) -> Result<Self, Self::Error> {
        let currency = item.request.get_currency()?;
        let refund = SignifydRefund {
            method: item.request.refund_method.clone(),
            amount: item.request.amount.to_string(),
            currency,
        };
        Ok(Self {
            return_id: uuid::Uuid::new_v4().to_string(),
            refund_transaction_id: item.request.refund_transaction_id.clone(),
            refund,
            order_id: item.attempt_id.clone(),
        })
    }
}

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

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ReviewDisposition {
    Fraudulent,
    Good,
}

impl From<ReviewDisposition> for IncomingWebhookEvent {
    fn from(value: ReviewDisposition) -> Self {
        match value {
            ReviewDisposition::Fraudulent => Self::FrmRejected,
            ReviewDisposition::Good => Self::FrmApproved,
        }
    }
}


pub mod transformers;

use common_utils::{
    errors::CustomResult,
    ext_traits::BytesExt,
    request::{Method, Request, RequestBuilder, RequestContent},
    types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector},
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
        refunds::{Execute, RSync},
    },
    router_request_types::{
        AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods,
    },
    types::{
        PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData,
        RefundSyncRouterData, RefundsRouterData,
    },
};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications,
        ConnectorValidation,
    },
    configs::Connectors,
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::{self, Response},
    webhooks,
};
use masking::{ExposeInterface, Mask};
use transformers as thunes;

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

#[derive(Clone)]
pub struct Thunes {
    amount_converter: &'static (dyn AmountConvertor<Output = StringMinorUnit> + Sync),
}

impl Thunes {
    pub fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMinorUnitForConnector,
        }
    }
}

impl api::Payment for Thunes {}
impl api::PaymentSession for Thunes {}
impl api::ConnectorAccessToken for Thunes {}
impl api::MandateSetup for Thunes {}
impl api::PaymentAuthorize for Thunes {}
impl api::PaymentSync for Thunes {}
impl api::PaymentCapture for Thunes {}
impl api::PaymentVoid for Thunes {}
impl api::Refund for Thunes {}
impl api::RefundExecute for Thunes {}
impl api::RefundSync for Thunes {}
impl api::PaymentToken for Thunes {}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Thunes
{
    // Not Implemented (R)
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Thunes
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut header = vec![(
            headers::CONTENT_TYPE.to_string(),
            self.get_content_type().to_string().into(),
        )];
        let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
        header.append(&mut api_key);
        Ok(header)
    }
}

impl ConnectorCommon for Thunes {
    fn id(&self) -> &'static str {
        "thunes"
    }

    fn get_currency_unit(&self) -> api::CurrencyUnit {
        api::CurrencyUnit::Base
        //    TODO! Check connector documentation, on which unit they are processing the currency.
        //    If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor,
        //    if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base
    }

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }

    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.thunes.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = thunes::ThunesAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            auth.api_key.expose().into_masked(),
        )])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: thunes::ThunesErrorResponse = res
            .response
            .parse_struct("ThunesErrorResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: response.code,
            message: response.message,
            reason: response.reason,
            attempt_status: None,
            connector_transaction_id: None,
            connector_response_reference_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

impl ConnectorValidation for Thunes {
    //TODO: implement functions when support enabled
}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Thunes {
    //TODO: implement sessions flow
}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Thunes {}

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Thunes {}

impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Thunes {
    fn get_headers(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &PaymentsAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

    fn get_request_body(
        &self,
        req: &PaymentsAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.currency,
        )?;

        let connector_router_data = thunes::ThunesRouterData::from((amount, req));
        let connector_req = thunes::ThunesPaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&types::PaymentsAuthorizeType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(types::PaymentsAuthorizeType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(types::PaymentsAuthorizeType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &PaymentsAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
        let response: thunes::ThunesPaymentsResponse = res
            .response
            .parse_struct("Thunes PaymentsAuthorizeResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Thunes {
    fn get_headers(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &PaymentsSyncRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

    fn build_request(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Get)
                .url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &PaymentsSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
        let response: thunes::ThunesPaymentsResponse = res
            .response
            .parse_struct("thunes PaymentsSyncResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Thunes {
    fn get_headers(
        &self,
        req: &PaymentsCaptureRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &PaymentsCaptureRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

    fn get_request_body(
        &self,
        _req: &PaymentsCaptureRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into())
    }

    fn build_request(
        &self,
        req: &PaymentsCaptureRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(types::PaymentsCaptureType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(types::PaymentsCaptureType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &PaymentsCaptureRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsCaptureRouterData, errors::ConnectorError> {
        let response: thunes::ThunesPaymentsResponse = res
            .response
            .parse_struct("Thunes PaymentsCaptureResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Thunes {}

impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Thunes {
    fn get_headers(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &RefundsRouterData<Execute>,
        _connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

    fn get_request_body(
        &self,
        req: &RefundsRouterData<Execute>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let refund_amount = utils::convert_amount(
            self.amount_converter,
            req.request.minor_refund_amount,
            req.request.currency,
        )?;

        let connector_router_data = thunes::ThunesRouterData::from((refund_amount, req));
        let connector_req = thunes::ThunesRefundRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let request = RequestBuilder::new()
            .method(Method::Post)
            .url(&types::RefundExecuteType::get_url(self, req, connectors)?)
            .attach_default_headers()
            .headers(types::RefundExecuteType::get_headers(
                self, req, connectors,
            )?)
            .set_body(types::RefundExecuteType::get_request_body(
                self, req, connectors,
            )?)
            .build();
        Ok(Some(request))
    }

    fn handle_response(
        &self,
        data: &RefundsRouterData<Execute>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefundsRouterData<Execute>, errors::ConnectorError> {
        let response: thunes::RefundResponse =
            res.response
                .parse_struct("thunes RefundResponse")
                .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Thunes {
    fn get_headers(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }

    fn get_url(
        &self,
        _req: &RefundSyncRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
    }

    fn build_request(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Get)
                .url(&types::RefundSyncType::get_url(self, req, connectors)?)
                .attach_default_headers()
                .headers(types::RefundSyncType::get_headers(self, req, connectors)?)
                .set_body(types::RefundSyncType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &RefundSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefundSyncRouterData, errors::ConnectorError> {
        let response: thunes::RefundResponse = res
            .response
            .parse_struct("thunes RefundSyncResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[async_trait::async_trait]
impl webhooks::IncomingWebhook for Thunes {
    fn get_webhook_object_reference_id(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_event_type(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
        _context: Option<&webhooks::WebhookContext>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_resource_object(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }
}

static THUNES_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Thunes",
    description: "Thunes Payouts is a global payment solution that enables businesses to send instant, secure, and cost-effective cross-border payments to bank accounts, mobile wallets, and cards in over 130 countries using a single API",
    connector_type: common_enums::HyperswitchConnectorCategory::PayoutProcessor,
    integration_status: common_enums::ConnectorIntegrationStatus::Sandbox,
};

impl ConnectorSpecifications for Thunes {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&THUNES_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        None
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [common_enums::enums::EventClass]> {
        None
    }
}


pub mod transformers;

use std::sync::LazyLock;

use common_enums::enums;
use common_utils::{
    errors::CustomResult,
    ext_traits::BytesExt,
    request::{Method, Request, RequestBuilder, RequestContent},
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void},
        refunds::{Execute, RSync},
        ExternalVaultInsertFlow, ExternalVaultRetrieveFlow,
    },
    router_request_types::{
        AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData, VaultRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods,
        VaultResponseData,
    },
    types::VaultRouterData,
};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications,
        ConnectorValidation,
    },
    configs::Connectors,
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::{self, Response},
    webhooks,
};
use masking::{ExposeInterface, Mask};
use transformers as tokenex;

use crate::{constants::headers, types::ResponseRouterData};

#[derive(Clone)]
pub struct Tokenex;

impl api::Payment for Tokenex {}
impl api::PaymentSession for Tokenex {}
impl api::ConnectorAccessToken for Tokenex {}
impl api::MandateSetup for Tokenex {}
impl api::PaymentAuthorize for Tokenex {}
impl api::PaymentSync for Tokenex {}
impl api::PaymentCapture for Tokenex {}
impl api::PaymentVoid for Tokenex {}
impl api::Refund for Tokenex {}
impl api::RefundExecute for Tokenex {}
impl api::RefundSync for Tokenex {}
impl api::PaymentToken for Tokenex {}
impl api::ExternalVaultInsert for Tokenex {}
impl api::ExternalVault for Tokenex {}
impl api::ExternalVaultRetrieve for Tokenex {}

impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
    for Tokenex
{
    // Not Implemented (R)
}

pub mod auth_headers {
    pub const TOKENEX_ID: &str = "tx-tokenex-id";
    pub const TOKENEX_API_KEY: &str = "tx-apikey";
    pub const TOKENEX_SCHEME: &str = "tx-token-scheme";
    pub const TOKENEX_SCHEME_VALUE: &str = "PCI";
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Tokenex
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = tokenex::TokenexAuthType::try_from(&req.connector_auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        let header = vec![
            (
                headers::CONTENT_TYPE.to_string(),
                self.get_content_type().to_string().into(),
            ),
            (
                auth_headers::TOKENEX_ID.to_string(),
                auth.tokenex_id.expose().into_masked(),
            ),
            (
                auth_headers::TOKENEX_API_KEY.to_string(),
                auth.api_key.expose().into_masked(),
            ),
            (
                auth_headers::TOKENEX_SCHEME.to_string(),
                auth_headers::TOKENEX_SCHEME_VALUE.to_string().into(),
            ),
        ];
        Ok(header)
    }
}

impl ConnectorCommon for Tokenex {
    fn id(&self) -> &'static str {
        "tokenex"
    }

    fn get_currency_unit(&self) -> api::CurrencyUnit {
        api::CurrencyUnit::Base
    }

    fn common_get_content_type(&self) -> &'static str {
        "application/json"
    }

    fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str {
        connectors.tokenex.base_url.as_ref()
    }

    fn get_auth_header(
        &self,
        auth_type: &ConnectorAuthType,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let auth = tokenex::TokenexAuthType::try_from(auth_type)
            .change_context(errors::ConnectorError::FailedToObtainAuthType)?;
        Ok(vec![(
            headers::AUTHORIZATION.to_string(),
            auth.api_key.expose().into_masked(),
        )])
    }

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        let response: tokenex::TokenexErrorResponse = res
            .response
            .parse_struct("TokenexErrorResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);

        let (code, message) = response.error.split_once(':').unwrap_or(("", ""));

        Ok(ErrorResponse {
            status_code: res.status_code,
            code: code.to_string(),
            message: message.to_string(),
            reason: Some(response.message),
            attempt_status: None,
            connector_transaction_id: None,
            connector_response_reference_id: None,
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    }
}

impl ConnectorValidation for Tokenex {}

impl ConnectorIntegration<Session, PaymentsSessionData, PaymentsResponseData> for Tokenex {
    //TODO: implement sessions flow
}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Tokenex {}

impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData> for Tokenex {}

impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData> for Tokenex {}

impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Tokenex {}

impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> for Tokenex {}

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Tokenex {}

impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Tokenex {}

impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Tokenex {}

impl ConnectorIntegration<ExternalVaultInsertFlow, VaultRequestData, VaultResponseData>
    for Tokenex
{
    fn get_url(
        &self,
        _req: &VaultRouterData<ExternalVaultInsertFlow>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!("{}/v2/Pci/Tokenize", self.base_url(connectors)))
    }

    fn get_headers(
        &self,
        req: &VaultRouterData<ExternalVaultInsertFlow>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &VaultRouterData<ExternalVaultInsertFlow>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = tokenex::TokenexInsertRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &VaultRouterData<ExternalVaultInsertFlow>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&types::ExternalVaultInsertType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(types::ExternalVaultInsertType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(types::ExternalVaultInsertType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &VaultRouterData<ExternalVaultInsertFlow>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<VaultRouterData<ExternalVaultInsertFlow>, errors::ConnectorError> {
        let response: tokenex::TokenexInsertResponse = res
            .response
            .parse_struct("TokenexInsertResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

impl ConnectorIntegration<ExternalVaultRetrieveFlow, VaultRequestData, VaultResponseData>
    for Tokenex
{
    fn get_url(
        &self,
        _req: &VaultRouterData<ExternalVaultRetrieveFlow>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}/v2/Pci/DetokenizeWithCvv",
            self.base_url(connectors)
        ))
    }

    fn get_headers(
        &self,
        req: &VaultRouterData<ExternalVaultRetrieveFlow>,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

    fn get_request_body(
        &self,
        req: &VaultRouterData<ExternalVaultRetrieveFlow>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = tokenex::TokenexRetrieveRequest::try_from(req)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

    fn build_request(
        &self,
        req: &VaultRouterData<ExternalVaultRetrieveFlow>,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&types::ExternalVaultRetrieveType::get_url(
                    self, req, connectors,
                )?)
                .attach_default_headers()
                .headers(types::ExternalVaultRetrieveType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(types::ExternalVaultRetrieveType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }

    fn handle_response(
        &self,
        data: &VaultRouterData<ExternalVaultRetrieveFlow>,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<VaultRouterData<ExternalVaultRetrieveFlow>, errors::ConnectorError> {
        let response: tokenex::TokenexRetrieveResponse = res
            .response
            .parse_struct("TokenexRetrieveResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
    }

    fn get_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        self.build_error_response(res, event_builder)
    }
}

#[async_trait::async_trait]
impl webhooks::IncomingWebhook for Tokenex {
    fn get_webhook_object_reference_id(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_event_type(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
        _context: Option<&webhooks::WebhookContext>,
    ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }

    fn get_webhook_resource_object(
        &self,
        _request: &webhooks::IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        Err(report!(errors::ConnectorError::WebhooksNotImplemented))
    }
}

static TOKENEX_SUPPORTED_PAYMENT_METHODS: LazyLock<SupportedPaymentMethods> =
    LazyLock::new(SupportedPaymentMethods::new);

static TOKENEX_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Tokenex",
    description: "Tokenex connector",
    connector_type: enums::HyperswitchConnectorCategory::PaymentGateway,
    integration_status: enums::ConnectorIntegrationStatus::Alpha,
};

static TOKENEX_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = [];

impl ConnectorSpecifications for Tokenex {
    fn get_connector_about(&self) -> Option<&'static ConnectorInfo> {
        Some(&TOKENEX_CONNECTOR_INFO)
    }

    fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> {
        Some(&*TOKENEX_SUPPORTED_PAYMENT_METHODS)
    }

    fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> {
        Some(&TOKENEX_SUPPORTED_WEBHOOK_FLOWS)
    }
}