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

use cards::CardNumber;
use common_enums::enums;
use common_utils::{
    errors::CustomResult,
    ext_traits::{OptionExt, ValueExt},
    pii::{self},
    request::Method,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{
        BankDebitData, BankRedirectData, BankTransferData, Card, CardRedirectData, GiftCardData,
        PayLaterData, PaymentMethodData, VoucherData, WalletData,
    },
    router_data::{ConnectorAuthType, ErrorResponse, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::{BrowserInformation, ResponseId},
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types,
};
use hyperswitch_interfaces::{
    api,
    consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE},
    errors,
};
use masking::{ExposeInterface, PeekInterface, Secret};
use ring::digest;
use serde::{Deserialize, Serialize};
use strum::Display;

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

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

impl<T> TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for ZenRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (currency_unit, currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T),
    ) -> Result<Self, Self::Error> {
        let amount = utils::get_amount_as_string(currency_unit, amount, currency)?;
        Ok(Self {
            amount,
            router_data: item,
        })
    }
}

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

impl TryFrom<&ConnectorAuthType> for ZenAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        if let ConnectorAuthType::HeaderKey { api_key } = auth_type {
            Ok(Self {
                api_key: api_key.to_owned(),
            })
        } else {
            Err(errors::ConnectorError::FailedToObtainAuthType.into())
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiRequest {
    merchant_transaction_id: String,
    payment_channel: ZenPaymentChannels,
    amount: String,
    currency: enums::Currency,
    payment_specific_data: ZenPaymentSpecificData,
    customer: ZenCustomerDetails,
    custom_ipn_url: String,
    items: Vec<ZenItemObject>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum ZenPaymentsRequest {
    ApiRequest(Box<ApiRequest>),
    CheckoutRequest(Box<CheckoutRequest>),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckoutRequest {
    amount: String,
    currency: enums::Currency,
    custom_ipn_url: String,
    items: Vec<ZenItemObject>,
    merchant_transaction_id: String,
    signature: Option<Secret<String>>,
    specified_payment_channel: ZenPaymentChannels,
    terminal_uuid: Secret<String>,
    url_redirect: String,
}

#[derive(Clone, Debug, Display, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[allow(clippy::enum_variant_names)]
pub enum ZenPaymentChannels {
    PclCard,
    PclGooglepay,
    PclApplepay,
    PclBoacompraBoleto,
    PclBoacompraEfecty,
    PclBoacompraMultibanco,
    PclBoacompraPagoefectivo,
    PclBoacompraPix,
    PclBoacompraPse,
    PclBoacompraRedcompra,
    PclBoacompraRedpagos,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenCustomerDetails {
    email: pii::Email,
    ip: Secret<String, pii::IpAddress>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum ZenPaymentSpecificData {
    ZenOnetimePayment(Box<ZenPaymentData>),
    ZenGeneralPayment(ZenGeneralPaymentData),
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenPaymentData {
    browser_details: ZenBrowserDetails,
    #[serde(rename = "type")]
    payment_type: ZenPaymentTypes,
    #[serde(skip_serializing_if = "Option::is_none")]
    token: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    card: Option<ZenCardDetails>,
    descriptor: String,
    return_verify_url: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenGeneralPaymentData {
    #[serde(rename = "type")]
    payment_type: ZenPaymentTypes,
    return_url: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenBrowserDetails {
    color_depth: String,
    java_enabled: bool,
    lang: String,
    screen_height: String,
    screen_width: String,
    timezone: String,
    accept_header: String,
    window_size: String,
    user_agent: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ZenPaymentTypes {
    Onetime,
    General,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenCardDetails {
    number: CardNumber,
    expiry_date: Secret<String>,
    cvv: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenItemObject {
    name: String,
    price: String,
    quantity: u16,
    line_amount_total: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SessionObject {
    pub apple_pay: Option<WalletSessionData>,
    pub google_pay: Option<WalletSessionData>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct WalletSessionData {
    pub terminal_uuid: Option<Secret<String>>,
    pub pay_wall_secret: Option<Secret<String>>,
}

impl TryFrom<(&ZenRouterData<&types::PaymentsAuthorizeRouterData>, &Card)> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: (&ZenRouterData<&types::PaymentsAuthorizeRouterData>, &Card),
    ) -> Result<Self, Self::Error> {
        let (item, ccard) = value;
        let browser_info = item.router_data.request.get_browser_info()?;
        let ip = browser_info.get_ip_address()?;
        let browser_details = get_browser_details(&browser_info)?;
        let amount = item.amount.to_owned();
        let payment_specific_data =
            ZenPaymentSpecificData::ZenOnetimePayment(Box::new(ZenPaymentData {
                browser_details,
                //Connector Specific for cards
                payment_type: ZenPaymentTypes::Onetime,
                token: None,
                card: Some(ZenCardDetails {
                    number: ccard.card_number.clone(),
                    expiry_date: ccard
                        .get_card_expiry_month_year_2_digit_with_delimiter("".to_owned())?,
                    cvv: ccard.card_cvc.clone(),
                }),
                descriptor: item
                    .router_data
                    .get_description()?
                    .chars()
                    .take(24)
                    .collect(),
                return_verify_url: item.router_data.request.router_return_url.clone(),
            }));
        Ok(Self::ApiRequest(Box::new(ApiRequest {
            merchant_transaction_id: item.router_data.connector_request_reference_id.clone(),
            payment_channel: ZenPaymentChannels::PclCard,
            currency: item.router_data.request.currency,
            payment_specific_data,
            customer: get_customer(item.router_data, ip)?,
            custom_ipn_url: item.router_data.request.get_webhook_url()?,
            items: get_item_object(item.router_data)?,
            amount,
        })))
    }
}

impl
    TryFrom<(
        &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
        &VoucherData,
    )> for ZenPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: (
            &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
            &VoucherData,
        ),
    ) -> Result<Self, Self::Error> {
        let (item, voucher_data) = value;
        let browser_info = item.router_data.request.get_browser_info()?;
        let ip = browser_info.get_ip_address()?;
        let amount = item.amount.to_owned();
        let payment_specific_data =
            ZenPaymentSpecificData::ZenGeneralPayment(ZenGeneralPaymentData {
                //Connector Specific for Latam Methods
                payment_type: ZenPaymentTypes::General,
                return_url: item.router_data.request.get_router_return_url()?,
            });
        let payment_channel = match voucher_data {
            VoucherData::Boleto { .. } => ZenPaymentChannels::PclBoacompraBoleto,
            VoucherData::Efecty => ZenPaymentChannels::PclBoacompraEfecty,
            VoucherData::PagoEfectivo => ZenPaymentChannels::PclBoacompraPagoefectivo,
            VoucherData::RedCompra => ZenPaymentChannels::PclBoacompraRedcompra,
            VoucherData::RedPagos => ZenPaymentChannels::PclBoacompraRedpagos,
            VoucherData::Oxxo
            | VoucherData::Alfamart { .. }
            | VoucherData::Indomaret { .. }
            | VoucherData::SevenEleven { .. }
            | VoucherData::Lawson { .. }
            | VoucherData::MiniStop { .. }
            | VoucherData::FamilyMart { .. }
            | VoucherData::Seicomart { .. }
            | VoucherData::PayEasy { .. } => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            ))?,
        };
        Ok(Self::ApiRequest(Box::new(ApiRequest {
            merchant_transaction_id: item.router_data.connector_request_reference_id.clone(),
            payment_channel,
            currency: item.router_data.request.currency,
            payment_specific_data,
            customer: get_customer(item.router_data, ip)?,
            custom_ipn_url: item.router_data.request.get_webhook_url()?,
            items: get_item_object(item.router_data)?,
            amount,
        })))
    }
}

impl
    TryFrom<(
        &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
        &Box<BankTransferData>,
    )> for ZenPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: (
            &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
            &Box<BankTransferData>,
        ),
    ) -> Result<Self, Self::Error> {
        let (item, bank_transfer_data) = value;
        let browser_info = item.router_data.request.get_browser_info()?;
        let ip = browser_info.get_ip_address()?;
        let amount = item.amount.to_owned();
        let payment_specific_data =
            ZenPaymentSpecificData::ZenGeneralPayment(ZenGeneralPaymentData {
                //Connector Specific for Latam Methods
                payment_type: ZenPaymentTypes::General,
                return_url: item.router_data.request.get_router_return_url()?,
            });
        let payment_channel = match **bank_transfer_data {
            BankTransferData::MultibancoBankTransfer { .. } => {
                ZenPaymentChannels::PclBoacompraMultibanco
            }
            BankTransferData::Pix { .. } => ZenPaymentChannels::PclBoacompraPix,
            BankTransferData::Pse { .. } => ZenPaymentChannels::PclBoacompraPse,
            BankTransferData::SepaBankTransfer { .. }
            | BankTransferData::AchBankTransfer { .. }
            | BankTransferData::BacsBankTransfer { .. }
            | BankTransferData::PermataBankTransfer { .. }
            | BankTransferData::BcaBankTransfer { .. }
            | BankTransferData::BniVaBankTransfer { .. }
            | BankTransferData::BriVaBankTransfer { .. }
            | BankTransferData::CimbVaBankTransfer { .. }
            | BankTransferData::DanamonVaBankTransfer { .. }
            | BankTransferData::LocalBankTransfer { .. }
            | BankTransferData::InstantBankTransfer {}
            | BankTransferData::InstantBankTransferFinland { .. }
            | BankTransferData::InstantBankTransferPoland { .. }
            | BankTransferData::IndonesianBankTransfer { .. }
            | BankTransferData::MandiriVaBankTransfer { .. } => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Zen"),
                ))?
            }
        };
        Ok(Self::ApiRequest(Box::new(ApiRequest {
            merchant_transaction_id: item.router_data.connector_request_reference_id.clone(),
            payment_channel,
            currency: item.router_data.request.currency,
            payment_specific_data,
            customer: get_customer(item.router_data, ip)?,
            custom_ipn_url: item.router_data.request.get_webhook_url()?,
            items: get_item_object(item.router_data)?,
            amount,
        })))
    }
}

/*
impl TryFrom<(&types::PaymentsAuthorizeRouterData, &GooglePayWalletData)> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, gpay_pay_redirect_data): (&types::PaymentsAuthorizeRouterData, &GooglePayWalletData),
    ) -> Result<Self, Self::Error> {
        let amount = utils::to_currency_base_unit(item.request.amount, item.request.currency)?;
        let browser_info = item.request.get_browser_info()?;
        let browser_details = get_browser_details(&browser_info)?;
        let ip = browser_info.get_ip_address()?;
        let payment_specific_data = ZenPaymentData {
            browser_details,
            //Connector Specific for wallet
            payment_type: ZenPaymentTypes::ExternalPaymentToken,
            token: Some(Secret::new(
                gpay_pay_redirect_data.tokenization_data.token.clone(),
            )),
            card: None,
            descriptor: item.get_description()?.chars().take(24).collect(),
            return_verify_url: item.request.router_return_url.clone(),
        };
        Ok(Self::ApiRequest(Box::new(ApiRequest {
            merchant_transaction_id: item.attempt_id.clone(),
            payment_channel: ZenPaymentChannels::PclGooglepay,
            currency: item.request.currency,
            payment_specific_data,
            customer: get_customer(item, ip)?,
            custom_ipn_url: item.request.get_webhook_url()?,
            items: get_item_object(item, amount.clone())?,
            amount,
        })))
    }
}
*/
/*
impl
    TryFrom<(
        &types::PaymentsAuthorizeRouterData,
        &Box<ApplePayRedirectData>,
    )> for ZenPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, _apple_pay_redirect_data): (
            &types::PaymentsAuthorizeRouterData,
            &Box<ApplePayRedirectData>,
        ),
    ) -> Result<Self, Self::Error> {
        let amount = utils::to_currency_base_unit(item.request.amount, item.request.currency)?;
        let connector_meta = item.get_connector_meta()?;
        let session: SessionObject = connector_meta
            .parse_value("SessionObject")
            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let applepay_session_data = session
            .apple_pay
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
        let terminal_uuid = applepay_session_data
            .terminal_uuid
            .clone()
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
        let mut checkout_request = CheckoutRequest {
            merchant_transaction_id: item.attempt_id.clone(),
            specified_payment_channel: ZenPaymentChannels::PclApplepay,
            currency: item.request.currency,
            custom_ipn_url: item.request.get_webhook_url()?,
            items: get_item_object(item, amount.clone())?,
            amount,
            terminal_uuid: Secret::new(terminal_uuid),
            signature: None,
            url_redirect: item.request.get_return_url()?,
        };
        checkout_request.signature = Some(get_checkout_signature(
            &checkout_request,
            &applepay_session_data,
        )?);
        Ok(Self::CheckoutRequest(Box::new(checkout_request)))
    }
}
*/

impl
    TryFrom<(
        &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
        &WalletData,
    )> for ZenPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        (item, wallet_data): (
            &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
            &WalletData,
        ),
    ) -> Result<Self, Self::Error> {
        let amount = item.amount.to_owned();
        let connector_meta = item.router_data.get_connector_meta()?;
        let session: SessionObject = connector_meta
            .parse_value("SessionObject")
            .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let (specified_payment_channel, session_data) = match wallet_data {
            WalletData::ApplePayRedirect(_) => (
                ZenPaymentChannels::PclApplepay,
                session
                    .apple_pay
                    .ok_or(errors::ConnectorError::InvalidWalletToken {
                        wallet_name: "Apple Pay".to_string(),
                    })?,
            ),
            WalletData::GooglePayRedirect(_) => (
                ZenPaymentChannels::PclGooglepay,
                session
                    .google_pay
                    .ok_or(errors::ConnectorError::InvalidWalletToken {
                        wallet_name: "Google Pay".to_string(),
                    })?,
            ),
            WalletData::WeChatPayRedirect(_)
            | WalletData::PaypalRedirect(_)
            | 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::ApplePayThirdPartySdk(_)
            | WalletData::DanaRedirect {}
            | WalletData::GooglePayThirdPartySdk(_)
            | WalletData::MbWayRedirect(_)
            | WalletData::MobilePayRedirect(_)
            | WalletData::PaypalSdk(_)
            | WalletData::Paze(_)
            | WalletData::SamsungPay(_)
            | WalletData::TwintRedirect {}
            | WalletData::VippsRedirect {}
            | WalletData::TouchNGoRedirect(_)
            | WalletData::CashappQr(_)
            | WalletData::SwishQr(_)
            | WalletData::WeChatPayQr(_)
            | WalletData::Mifinity(_)
            | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            ))?,
        };
        let terminal_uuid = session_data
            .terminal_uuid
            .clone()
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?
            .expose();
        let mut checkout_request = CheckoutRequest {
            merchant_transaction_id: item.router_data.connector_request_reference_id.clone(),
            specified_payment_channel,
            currency: item.router_data.request.currency,
            custom_ipn_url: item.router_data.request.get_webhook_url()?,
            items: get_item_object(item.router_data)?,
            amount,
            terminal_uuid: Secret::new(terminal_uuid),
            signature: None,
            url_redirect: item.router_data.request.get_router_return_url()?,
        };
        checkout_request.signature =
            Some(get_checkout_signature(&checkout_request, &session_data)?);
        Ok(Self::CheckoutRequest(Box::new(checkout_request)))
    }
}

fn get_checkout_signature(
    checkout_request: &CheckoutRequest,
    session: &WalletSessionData,
) -> Result<Secret<String>, error_stack::Report<errors::ConnectorError>> {
    let pay_wall_secret = session
        .pay_wall_secret
        .clone()
        .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
    let mut signature_data = get_signature_data(checkout_request)?;
    signature_data.push_str(&pay_wall_secret.expose());
    let payload_digest = digest::digest(&digest::SHA256, signature_data.as_bytes());
    let mut signature = hex::encode(payload_digest);
    signature.push_str(";sha256");
    Ok(Secret::new(signature))
}

/// Fields should be in alphabetical order
fn get_signature_data(
    checkout_request: &CheckoutRequest,
) -> Result<String, errors::ConnectorError> {
    let specified_payment_channel = match checkout_request.specified_payment_channel {
        ZenPaymentChannels::PclCard => "pcl_card",
        ZenPaymentChannels::PclGooglepay => "pcl_googlepay",
        ZenPaymentChannels::PclApplepay => "pcl_applepay",
        ZenPaymentChannels::PclBoacompraBoleto => "pcl_boacompra_boleto",
        ZenPaymentChannels::PclBoacompraEfecty => "pcl_boacompra_efecty",
        ZenPaymentChannels::PclBoacompraMultibanco => "pcl_boacompra_multibanco",
        ZenPaymentChannels::PclBoacompraPagoefectivo => "pcl_boacompra_pagoefectivo",
        ZenPaymentChannels::PclBoacompraPix => "pcl_boacompra_pix",
        ZenPaymentChannels::PclBoacompraPse => "pcl_boacompra_pse",
        ZenPaymentChannels::PclBoacompraRedcompra => "pcl_boacompra_redcompra",
        ZenPaymentChannels::PclBoacompraRedpagos => "pcl_boacompra_redpagos",
    };
    let mut signature_data = vec![
        format!("amount={}", checkout_request.amount),
        format!("currency={}", checkout_request.currency),
        format!("customipnurl={}", checkout_request.custom_ipn_url),
    ];
    for index in 0..checkout_request.items.len() {
        let prefix = format!("items[{index}].");
        let checkout_request_items = checkout_request
            .items
            .get(index)
            .ok_or(errors::ConnectorError::RequestEncodingFailed)?;
        signature_data.push(format!(
            "{prefix}lineamounttotal={}",
            checkout_request_items.line_amount_total
        ));
        signature_data.push(format!("{prefix}name={}", checkout_request_items.name));
        signature_data.push(format!("{prefix}price={}", checkout_request_items.price));
        signature_data.push(format!(
            "{prefix}quantity={}",
            checkout_request_items.quantity
        ));
    }
    signature_data.push(format!(
        "merchanttransactionid={}",
        checkout_request.merchant_transaction_id
    ));
    signature_data.push(format!(
        "specifiedpaymentchannel={specified_payment_channel}"
    ));
    signature_data.push(format!(
        "terminaluuid={}",
        checkout_request.terminal_uuid.peek()
    ));
    signature_data.push(format!("urlredirect={}", checkout_request.url_redirect));
    let signature = signature_data.join("&");
    Ok(signature.to_lowercase())
}

fn get_customer(
    item: &types::PaymentsAuthorizeRouterData,
    ip: Secret<String, pii::IpAddress>,
) -> Result<ZenCustomerDetails, error_stack::Report<errors::ConnectorError>> {
    Ok(ZenCustomerDetails {
        email: item.request.get_email()?,
        ip,
    })
}

fn get_item_object(
    item: &types::PaymentsAuthorizeRouterData,
) -> Result<Vec<ZenItemObject>, error_stack::Report<errors::ConnectorError>> {
    let order_details = item.request.get_order_details()?;

    order_details
        .iter()
        .map(|data| {
            Ok(ZenItemObject {
                name: data.product_name.clone(),
                quantity: data.quantity,
                price: utils::to_currency_base_unit_with_zero_decimal_check(
                    data.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.request.currency,
                )?,
                line_amount_total: (f64::from(data.quantity)
                    * utils::to_currency_base_unit_asf64(
                        data.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.request.currency,
                    )?)
                .to_string(),
            })
        })
        .collect::<Result<_, _>>()
}

fn get_browser_details(
    browser_info: &BrowserInformation,
) -> CustomResult<ZenBrowserDetails, errors::ConnectorError> {
    let screen_height = browser_info
        .screen_height
        .get_required_value("screen_height")
        .change_context(errors::ConnectorError::MissingRequiredField {
            field_name: "screen_height",
        })?;

    let screen_width = browser_info
        .screen_width
        .get_required_value("screen_width")
        .change_context(errors::ConnectorError::MissingRequiredField {
            field_name: "screen_width",
        })?;

    let window_size = match (screen_height, screen_width) {
        (250, 400) => "01",
        (390, 400) => "02",
        (500, 600) => "03",
        (600, 400) => "04",
        _ => "05",
    }
    .to_string();

    Ok(ZenBrowserDetails {
        color_depth: browser_info.get_color_depth()?.to_string(),
        java_enabled: browser_info.get_java_enabled()?,
        lang: browser_info.get_language()?,
        screen_height: screen_height.to_string(),
        screen_width: screen_width.to_string(),
        timezone: browser_info.get_time_zone()?.to_string(),
        accept_header: browser_info.get_accept_header()?,
        user_agent: browser_info.get_user_agent()?,
        window_size,
    })
}

impl TryFrom<&ZenRouterData<&types::PaymentsAuthorizeRouterData>> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &ZenRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        match &item.router_data.request.payment_method_data {
            PaymentMethodData::Card(card) => Self::try_from((item, card)),
            PaymentMethodData::Wallet(wallet_data) => Self::try_from((item, wallet_data)),
            PaymentMethodData::Voucher(voucher_data) => Self::try_from((item, voucher_data)),
            PaymentMethodData::BankTransfer(bank_transfer_data) => {
                Self::try_from((item, bank_transfer_data))
            }
            PaymentMethodData::BankRedirect(bank_redirect_data) => {
                Self::try_from(bank_redirect_data)
            }
            PaymentMethodData::PayLater(paylater_data) => Self::try_from(paylater_data),
            PaymentMethodData::BankDebit(bank_debit_data) => Self::try_from(bank_debit_data),
            PaymentMethodData::CardRedirect(car_redirect_data) => Self::try_from(car_redirect_data),
            PaymentMethodData::GiftCard(gift_card_data) => Self::try_from(gift_card_data.as_ref()),
            PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Zen"),
                ))?
            }
        }
    }
}

impl TryFrom<&BankRedirectData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &BankRedirectData) -> Result<Self, Self::Error> {
        match value {
            BankRedirectData::Ideal { .. }
            | BankRedirectData::Sofort { .. }
            | BankRedirectData::BancontactCard { .. }
            | BankRedirectData::Blik { .. }
            | BankRedirectData::Trustly { .. }
            | BankRedirectData::Eft { .. }
            | BankRedirectData::Eps { .. }
            | BankRedirectData::Giropay { .. }
            | BankRedirectData::Przelewy24 { .. }
            | BankRedirectData::Bizum {}
            | BankRedirectData::Interac { .. }
            | BankRedirectData::OnlineBankingCzechRepublic { .. }
            | BankRedirectData::OnlineBankingFinland { .. }
            | BankRedirectData::OnlineBankingPoland { .. }
            | BankRedirectData::OnlineBankingSlovakia { .. }
            | BankRedirectData::OpenBankingUk { .. }
            | BankRedirectData::OnlineBankingFpx { .. }
            | BankRedirectData::OnlineBankingThailand { .. }
            | BankRedirectData::LocalBankRedirect {} => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Zen"),
                )
                .into())
            }
        }
    }
}

impl TryFrom<&PayLaterData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &PayLaterData) -> Result<Self, Self::Error> {
        match value {
            PayLaterData::KlarnaRedirect { .. }
            | PayLaterData::KlarnaSdk { .. }
            | PayLaterData::AffirmRedirect {}
            | PayLaterData::AfterpayClearpayRedirect { .. }
            | 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("Zen"),
            )
            .into()),
        }
    }
}

impl TryFrom<&BankDebitData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &BankDebitData) -> Result<Self, Self::Error> {
        match value {
            BankDebitData::AchBankDebit { .. }
            | BankDebitData::SepaBankDebit { .. }
            | BankDebitData::BecsBankDebit { .. }
            | BankDebitData::BacsBankDebit { .. }
            | BankDebitData::SepaGuarenteedBankDebit { .. } => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Zen"),
                )
                .into())
            }
        }
    }
}

impl TryFrom<&CardRedirectData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &CardRedirectData) -> Result<Self, Self::Error> {
        match value {
            CardRedirectData::Knet {}
            | CardRedirectData::Benefit {}
            | CardRedirectData::MomoAtm {}
            | CardRedirectData::CardRedirect {} => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            )
            .into()),
        }
    }
}

impl TryFrom<&GiftCardData> for ZenPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &GiftCardData) -> Result<Self, Self::Error> {
        match value {
            GiftCardData::PaySafeCard {}
            | GiftCardData::Givex(_)
            | GiftCardData::BhnCardNetwork(_) => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("Zen"),
            )
            .into()),
        }
    }
}

// PaymentsResponse
#[derive(Debug, Default, Deserialize, Clone, strum::Display, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum ZenPaymentStatus {
    Authorized,
    Accepted,
    #[default]
    Pending,
    Rejected,
    Canceled,
}

impl ForeignTryFrom<(ZenPaymentStatus, Option<ZenActions>)> for enums::AttemptStatus {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn foreign_try_from(item: (ZenPaymentStatus, Option<ZenActions>)) -> Result<Self, Self::Error> {
        let (item_txn_status, item_action_status) = item;
        Ok(match item_txn_status {
            // Payment has been authorized at connector end, They will send webhook when it gets accepted
            ZenPaymentStatus::Authorized => Self::Pending,
            ZenPaymentStatus::Accepted => Self::Charged,
            ZenPaymentStatus::Pending => {
                item_action_status.map_or(Self::Pending, |action| match action {
                    ZenActions::Redirect => Self::AuthenticationPending,
                })
            }
            ZenPaymentStatus::Rejected => Self::Failure,
            ZenPaymentStatus::Canceled => Self::Voided,
        })
    }
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiResponse {
    status: ZenPaymentStatus,
    id: String,
    // merchant_transaction_id: Option<String>,
    merchant_action: Option<ZenMerchantAction>,
    reject_code: Option<String>,
    reject_reason: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ZenPaymentsResponse {
    ApiResponse(ApiResponse),
    CheckoutResponse(CheckoutResponse),
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CheckoutResponse {
    redirect_url: url::Url,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenMerchantAction {
    action: ZenActions,
    data: ZenMerchantActionData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum ZenActions {
    Redirect,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenMerchantActionData {
    redirect_url: url::Url,
}

impl<F, T> TryFrom<ResponseRouterData<F, ZenPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, ZenPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        match item.response {
            ZenPaymentsResponse::ApiResponse(response) => Self::try_from(ResponseRouterData {
                response,
                data: item.data,
                http_code: item.http_code,
            }),
            ZenPaymentsResponse::CheckoutResponse(response) => Self::try_from(ResponseRouterData {
                response,
                data: item.data,
                http_code: item.http_code,
            }),
        }
    }
}

fn get_zen_response(
    response: ApiResponse,
    status_code: u16,
) -> CustomResult<
    (
        enums::AttemptStatus,
        Option<ErrorResponse>,
        PaymentsResponseData,
    ),
    errors::ConnectorError,
> {
    let redirection_data_action = response.merchant_action.map(|merchant_action| {
        (
            RedirectForm::from((merchant_action.data.redirect_url, Method::Get)),
            merchant_action.action,
        )
    });
    let (redirection_data, action) = match redirection_data_action {
        Some((redirect_form, action)) => (Some(redirect_form), Some(action)),
        None => (None, None),
    };
    let status = enums::AttemptStatus::foreign_try_from((response.status, action))?;
    let error = if utils::is_payment_failure(status) {
        Some(ErrorResponse {
            code: response
                .reject_code
                .unwrap_or_else(|| NO_ERROR_CODE.to_string()),
            message: response
                .reject_reason
                .clone()
                .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
            reason: response.reject_reason,
            status_code,
            attempt_status: Some(status),
            connector_transaction_id: Some(response.id.clone()),
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    } else {
        None
    };
    let payment_response_data = PaymentsResponseData::TransactionResponse {
        resource_id: ResponseId::ConnectorTransactionId(response.id.clone()),
        redirection_data: Box::new(redirection_data),
        mandate_reference: Box::new(None),
        connector_metadata: None,
        network_txn_id: None,
        connector_response_reference_id: None,
        incremental_authorization_allowed: None,
        charges: None,
    };
    Ok((status, error, payment_response_data))
}

impl<F, T> TryFrom<ResponseRouterData<F, ApiResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: ResponseRouterData<F, ApiResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let (status, error, payment_response_data) =
            get_zen_response(value.response.clone(), value.http_code)?;

        Ok(Self {
            status,
            response: error.map_or_else(|| Ok(payment_response_data), Err),
            ..value.data
        })
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, CheckoutResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: ResponseRouterData<F, CheckoutResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let redirection_data = Some(RedirectForm::from((
            value.response.redirect_url,
            Method::Get,
        )));
        Ok(Self {
            status: enums::AttemptStatus::AuthenticationPending,
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::NoResponseId,
                redirection_data: Box::new(redirection_data),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: None,
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..value.data
        })
    }
}

#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenRefundRequest {
    amount: String,
    transaction_id: String,
    currency: enums::Currency,
    merchant_transaction_id: String,
}

impl<F> TryFrom<&ZenRouterData<&types::RefundsRouterData<F>>> for ZenRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &ZenRouterData<&types::RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        Ok(Self {
            amount: item.amount.to_owned(),
            transaction_id: item.router_data.request.connector_transaction_id.clone(),
            currency: item.router_data.request.currency,
            merchant_transaction_id: item.router_data.request.refund_id.clone(),
        })
    }
}

#[derive(Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum RefundStatus {
    Authorized,
    Accepted,
    #[default]
    Pending,
    Rejected,
}

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

#[derive(Default, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RefundResponse {
    id: String,
    status: RefundStatus,
    reject_code: Option<String>,
    reject_reason: Option<String>,
}

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> {
        let (error, refund_response_data) = get_zen_refund_response(item.response, item.http_code)?;
        Ok(Self {
            response: error.map_or_else(|| Ok(refund_response_data), Err),
            ..item.data
        })
    }
}

fn get_zen_refund_response(
    response: RefundResponse,
    status_code: u16,
) -> CustomResult<(Option<ErrorResponse>, RefundsResponseData), errors::ConnectorError> {
    let refund_status = enums::RefundStatus::from(response.status);
    let error = if utils::is_refund_failure(refund_status) {
        Some(ErrorResponse {
            code: response
                .reject_code
                .unwrap_or_else(|| NO_ERROR_CODE.to_string()),
            message: response
                .reject_reason
                .clone()
                .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()),
            reason: response.reject_reason,
            status_code,
            attempt_status: None,
            connector_transaction_id: Some(response.id.clone()),
            network_advice_code: None,
            network_decline_code: None,
            network_error_message: None,
            connector_metadata: None,
        })
    } else {
        None
    };
    let refund_response_data = RefundsResponseData {
        connector_refund_id: response.id,
        refund_status,
    };
    Ok((error, refund_response_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> {
        let refund_status = enums::RefundStatus::from(item.response.status);
        Ok(Self {
            response: Ok(RefundsResponseData {
                connector_refund_id: item.response.id,
                refund_status,
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenWebhookBody {
    #[serde(rename = "transactionId")]
    pub id: String,
    pub merchant_transaction_id: String,
    pub amount: String,
    pub currency: String,
    pub status: ZenPaymentStatus,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ZenWebhookSignature {
    pub hash: String,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenWebhookObjectReference {
    #[serde(rename = "type")]
    pub transaction_type: ZenWebhookTxnType,
    pub transaction_id: String,
    pub merchant_transaction_id: String,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ZenWebhookEventType {
    #[serde(rename = "type")]
    pub transaction_type: ZenWebhookTxnType,
    pub transaction_id: String,
    pub status: ZenPaymentStatus,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ZenWebhookTxnType {
    TrtPurchase,
    TrtRefund,
    #[serde(other)]
    Unknown,
}

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

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


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

use common_enums::{enums, MerchantCategoryCode};
use common_types::payments::MerchantCountryCode;
use common_utils::{ext_traits::OptionExt as _, types::FloatMajorUnit};
use hyperswitch_domain_models::{
    router_data::{ConnectorAuthType, RouterData},
    router_request_types::{
        authentication::{AuthNFlowType, ChallengeParams},
        unified_authentication_service::{
            AuthenticationInfo, DynamicData, PostAuthenticationDetails, PreAuthenticationDetails,
            RawCardDetails, TokenDetails, UasAuthenticationResponseData,
        },
    },
    types::{
        UasAuthenticationConfirmationRouterData, UasAuthenticationRouterData,
        UasPostAuthenticationRouterData, UasPreAuthenticationRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;

use crate::types::ResponseRouterData;
//TODO: Fill the struct with respective fields
pub struct UnifiedAuthenticationServiceRouterData<T> {
    pub amount: FloatMajorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
    pub router_data: T,
}

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

use error_stack::ResultExt;

#[derive(Debug, Serialize)]
pub struct UnifiedAuthenticationServicePreAuthenticateRequest {
    pub authenticate_by: String,
    pub session_id: common_utils::id_type::AuthenticationId,
    pub source_authentication_id: common_utils::id_type::AuthenticationId,
    pub authentication_info: Option<AuthenticationInfo>,
    pub service_details: Option<CtpServiceDetails>,
    pub customer_details: Option<CustomerDetails>,
    pub pmt_details: Option<PaymentDetails>,
    pub auth_creds: UnifiedAuthenticationServiceAuthType,
    pub transaction_details: Option<TransactionDetails>,
    pub acquirer_details: Option<Acquirer>,
    pub billing_address: Option<Address>,
}

#[derive(Debug, Serialize)]
pub struct UnifiedAuthenticationServiceAuthenticateConfirmationRequest {
    pub authenticate_by: String,
    pub source_authentication_id: common_utils::id_type::AuthenticationId,
    pub auth_creds: UnifiedAuthenticationServiceAuthType,
    pub x_src_flow_id: Option<String>,
    pub transaction_amount: Option<FloatMajorUnit>,
    pub transaction_currency: Option<enums::Currency>,
    pub checkout_event_type: Option<String>,
    pub checkout_event_status: Option<String>,
    pub confirmation_status: Option<String>,
    pub confirmation_reason: Option<String>,
    pub confirmation_timestamp: Option<String>,
    pub network_authorization_code: Option<String>,
    pub network_transaction_identifier: Option<String>,
    pub correlation_id: Option<String>,
    pub merchant_transaction_id: Option<String>,
}

#[derive(Debug, Serialize, PartialEq, Deserialize)]
pub struct UnifiedAuthenticationServiceAuthenticateConfirmationResponse {
    status: String,
}

impl<F, T>
    TryFrom<
        ResponseRouterData<
            F,
            UnifiedAuthenticationServiceAuthenticateConfirmationResponse,
            T,
            UasAuthenticationResponseData,
        >,
    > for RouterData<F, T, UasAuthenticationResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            UnifiedAuthenticationServiceAuthenticateConfirmationResponse,
            T,
            UasAuthenticationResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(UasAuthenticationResponseData::Confirmation {}),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct PaymentDetails {
    pub pan: cards::CardNumber,
    pub digital_card_id: Option<String>,
    pub payment_data_type: Option<common_enums::PaymentMethodType>,
    pub encrypted_src_card_details: Option<String>,
    pub card_expiry_month: Secret<String>,
    pub card_expiry_year: Secret<String>,
    pub cardholder_name: Option<Secret<String>>,
    pub card_token_number: Option<Secret<String>>,
    pub account_type: Option<common_enums::PaymentMethodType>,
    pub card_cvc: Option<Secret<String>>,
}

#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct TransactionDetails {
    pub amount: FloatMajorUnit,
    pub currency: enums::Currency,
    pub date: Option<PrimitiveDateTime>,
    pub pan_source: Option<String>,
    pub protection_type: Option<String>,
    pub entry_mode: Option<String>,
    pub transaction_type: Option<String>,
    pub otp_value: Option<String>,
    pub three_ds_data: Option<ThreeDSData>,
    pub message_category: Option<MessageCategory>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MessageCategory {
    Payment,
    NonPayment,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThreeDSData {
    pub preferred_protocol_version: common_utils::types::SemanticVersion,
    pub threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator,
    pub force_3ds_challenge: Option<bool>,
    pub psd2_sca_exemption_type: Option<common_enums::ScaExemptionType>,
}

#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Acquirer {
    pub acquirer_merchant_id: Option<String>,
    pub acquirer_bin: Option<String>,
    pub acquirer_country_code: Option<String>,
}

#[derive(Default, Debug, Serialize, PartialEq, Clone, Deserialize)]
pub struct BrowserInfo {
    pub color_depth: Option<u8>,
    pub java_enabled: Option<bool>,
    pub java_script_enabled: Option<bool>,
    pub language: Option<String>,
    pub screen_height: Option<u32>,
    pub screen_width: Option<u32>,
    pub time_zone: Option<i32>,
    pub ip_address: Option<std::net::IpAddr>,
    pub accept_header: Option<String>,
    pub user_agent: Option<String>,
    pub os_type: Option<String>,
    pub os_version: Option<String>,
    pub device_model: Option<String>,
    pub accept_language: Option<String>,
}

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct CtpServiceDetails {
    pub service_session_ids: Option<ServiceSessionIds>,
    pub merchant_details: Option<MerchantDetails>,
}

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct ServiceSessionIds {
    pub client_id: Option<String>,
    pub service_id: Option<String>,
    pub correlation_id: Option<String>,
    pub client_reference_id: Option<String>,
    pub merchant_transaction_id: Option<String>,
    pub x_src_flow_id: Option<String>,
}

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct MerchantDetails {
    pub merchant_id: Option<String>,
    pub merchant_name: Option<String>,
    pub merchant_category_code: Option<MerchantCategoryCode>,
    pub configuration_id: Option<String>,
    pub endpoint_prefix: Option<String>,
    pub three_ds_requestor_url: Option<String>,
    pub three_ds_requestor_id: Option<String>,
    pub three_ds_requestor_name: Option<String>,
    pub merchant_country_code: Option<MerchantCountryCode>,
    pub notification_url: Option<url::Url>,
}

#[derive(Default, Clone, Debug, Serialize, PartialEq, Deserialize)]
pub struct Address {
    pub city: Option<String>,
    pub country: Option<common_enums::CountryAlpha2>,
    pub line1: Option<Secret<String>>,
    pub line2: Option<Secret<String>>,
    pub line3: Option<Secret<String>>,
    pub post_code: Option<Secret<String>>,
    pub state: Option<Secret<String>>,
}

#[derive(Default, Clone, Debug, Serialize, PartialEq, Deserialize)]
pub struct CustomerDetails {
    pub name: Secret<String>,
    pub email: Option<Secret<String>>,
    pub phone_number: Option<Secret<String>>,
    pub customer_id: String,
    #[serde(rename = "type")]
    pub customer_type: Option<String>,
    pub billing_address: Address,
    pub shipping_address: Address,
    pub wallet_account_id: Secret<String>,
    pub email_hash: Secret<String>,
    pub country_code: String,
    pub national_identifier: String,
}

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

impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasPreAuthenticationRouterData>>
    for UnifiedAuthenticationServicePreAuthenticateRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &UnifiedAuthenticationServiceRouterData<&UasPreAuthenticationRouterData>,
    ) -> Result<Self, Self::Error> {
        let authentication_id = item.router_data.authentication_id.clone().ok_or(
            errors::ConnectorError::MissingRequiredField {
                field_name: "authentication_id",
            },
        )?;
        let authentication_info = item.router_data.request.authentication_info.clone();
        let auth_type =
            UnifiedAuthenticationServiceAuthType::try_from(&item.router_data.connector_auth_type)?;
        Ok(Self {
            authenticate_by: item.router_data.connector.clone(),
            session_id: authentication_id.clone(),
            source_authentication_id: authentication_id,
            authentication_info,
            service_details: Some(CtpServiceDetails {
                service_session_ids: item.router_data.request.service_details.clone().map(
                    |service_details| ServiceSessionIds {
                        client_id: None,
                        service_id: None,
                        correlation_id: service_details
                            .service_session_ids
                            .clone()
                            .and_then(|service_session_ids| service_session_ids.correlation_id),
                        client_reference_id: None,
                        merchant_transaction_id: service_details
                            .service_session_ids
                            .clone()
                            .and_then(|service_session_ids| {
                                service_session_ids.merchant_transaction_id
                            }),
                        x_src_flow_id: service_details
                            .service_session_ids
                            .clone()
                            .and_then(|service_session_ids| service_session_ids.x_src_flow_id),
                    },
                ),
                merchant_details: None,
            }),
            customer_details: None,
            pmt_details: None,
            auth_creds: auth_type,
            acquirer_details: None,
            billing_address: None,
            transaction_details: Some(TransactionDetails {
                amount: item.amount,
                currency: item
                    .router_data
                    .request
                    .transaction_details
                    .clone()
                    .ok_or(errors::ConnectorError::MissingRequiredField {
                        field_name: "transaction_details",
                    })?
                    .currency
                    .ok_or(errors::ConnectorError::MissingRequiredField {
                        field_name: "currency",
                    })?,
                date: None,
                pan_source: None,
                protection_type: None,
                entry_mode: None,
                transaction_type: None,
                otp_value: None,
                three_ds_data: None,
                message_category: None,
            }),
        })
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum UnifiedAuthenticationServicePreAuthenticateStatus {
    ACKSUCCESS,
    ACKFAILURE,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UnifiedAuthenticationServicePreAuthenticateResponse {
    status: UnifiedAuthenticationServicePreAuthenticateStatus,
    pub eligibility: Option<Eligibility>,
}

impl<F, T>
    TryFrom<
        ResponseRouterData<
            F,
            UnifiedAuthenticationServicePreAuthenticateResponse,
            T,
            UasAuthenticationResponseData,
        >,
    > for RouterData<F, T, UasAuthenticationResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            UnifiedAuthenticationServicePreAuthenticateResponse,
            T,
            UasAuthenticationResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let three_ds_eligibility_response = if let Some(Eligibility::ThreeDsEligibilityResponse {
            three_ds_eligibility_response,
        }) = item.response.eligibility
        {
            Some(three_ds_eligibility_response)
        } else {
            None
        };
        let max_acs_protocol_version = three_ds_eligibility_response
            .as_ref()
            .and_then(|response| response.get_max_acs_protocol_version_if_available());
        let maximum_supported_3ds_version = max_acs_protocol_version
            .as_ref()
            .map(|acs_protocol_version| acs_protocol_version.version.clone());
        let three_ds_method_data =
            three_ds_eligibility_response
                .as_ref()
                .and_then(|three_ds_eligibility_response| {
                    three_ds_eligibility_response
                        .three_ds_method_data_form
                        .as_ref()
                        .and_then(|form| form.three_ds_method_data.clone())
                });
        let three_ds_method_url = max_acs_protocol_version
            .and_then(|acs_protocol_version| acs_protocol_version.three_ds_method_url);
        Ok(Self {
            response: Ok(UasAuthenticationResponseData::PreAuthentication {
                authentication_details: PreAuthenticationDetails {
                    threeds_server_transaction_id: three_ds_eligibility_response
                        .as_ref()
                        .map(|response| response.three_ds_server_trans_id.clone()),
                    maximum_supported_3ds_version: maximum_supported_3ds_version.clone(),
                    connector_authentication_id: three_ds_eligibility_response
                        .as_ref()
                        .map(|response| response.three_ds_server_trans_id.clone()),
                    three_ds_method_data,
                    three_ds_method_url,
                    message_version: maximum_supported_3ds_version,
                    connector_metadata: None,
                    directory_server_id: three_ds_eligibility_response
                        .and_then(|response| response.directory_server_id),
                },
            }),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
pub struct UnifiedAuthenticationServicePostAuthenticateRequest {
    pub authenticate_by: String,
    pub source_authentication_id: common_utils::id_type::AuthenticationId,
    pub auth_creds: ConnectorAuthType,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UnifiedAuthenticationServicePostAuthenticateResponse {
    pub authentication_details: AuthenticationDetails,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AuthenticationDetails {
    pub eci: Option<String>,
    pub token_details: Option<UasTokenDetails>,
    pub dynamic_data_details: Option<UasDynamicData>,
    pub trans_status: Option<common_enums::TransactionStatus>,
    pub raw_card_details: Option<UasRawCardDetails>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UasTokenDetails {
    pub payment_token: cards::CardNumber,
    pub payment_account_reference: String,
    pub token_expiration_month: Secret<String>,
    pub token_expiration_year: Secret<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UasRawCardDetails {
    pub pan: cards::CardNumber,
    pub expiration_month: Secret<String>,
    pub expiration_year: Secret<String>,
    pub card_security_code: Option<Secret<String>>,
    pub payment_account_reference: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UasDynamicData {
    pub dynamic_data_value: Option<Secret<String>>,
    pub dynamic_data_type: Option<String>,
    pub ds_trans_id: Option<String>,
}

impl TryFrom<&UasPostAuthenticationRouterData>
    for UnifiedAuthenticationServicePostAuthenticateRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &UasPostAuthenticationRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            authenticate_by: item.connector.clone(),
            source_authentication_id: item.authentication_id.clone().ok_or(
                errors::ConnectorError::MissingRequiredField {
                    field_name: "authentication_id",
                },
            )?,
            auth_creds: item.connector_auth_type.clone(),
        })
    }
}

impl<F, T>
    TryFrom<
        ResponseRouterData<
            F,
            UnifiedAuthenticationServicePostAuthenticateResponse,
            T,
            UasAuthenticationResponseData,
        >,
    > for RouterData<F, T, UasAuthenticationResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            UnifiedAuthenticationServicePostAuthenticateResponse,
            T,
            UasAuthenticationResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(UasAuthenticationResponseData::PostAuthentication {
                authentication_details: PostAuthenticationDetails {
                    eci: item.response.authentication_details.eci,
                    token_details: item.response.authentication_details.token_details.map(
                        |token_details| TokenDetails {
                            payment_token: token_details.payment_token,
                            payment_account_reference: token_details.payment_account_reference,
                            token_expiration_month: token_details.token_expiration_month,
                            token_expiration_year: token_details.token_expiration_year,
                        },
                    ),
                    raw_card_details: item.response.authentication_details.raw_card_details.map(
                        |raw_card_details| RawCardDetails {
                            pan: raw_card_details.pan,
                            expiration_month: raw_card_details.expiration_month,
                            expiration_year: raw_card_details.expiration_year,
                            card_security_code: raw_card_details.card_security_code,
                            payment_account_reference: raw_card_details.payment_account_reference,
                        },
                    ),
                    dynamic_data_details: item
                        .response
                        .authentication_details
                        .dynamic_data_details
                        .map(|dynamic_data| DynamicData {
                            dynamic_data_value: dynamic_data.dynamic_data_value,
                            dynamic_data_type: dynamic_data.dynamic_data_type,
                            ds_trans_id: dynamic_data.ds_trans_id,
                        }),
                    trans_status: item.response.authentication_details.trans_status,
                    challenge_cancel: None,
                    challenge_code_reason: None,
                },
            }),
            ..item.data
        })
    }
}

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

impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasAuthenticationConfirmationRouterData>>
    for UnifiedAuthenticationServiceAuthenticateConfirmationRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &UnifiedAuthenticationServiceRouterData<&UasAuthenticationConfirmationRouterData>,
    ) -> Result<Self, Self::Error> {
        let authentication_id = item.router_data.authentication_id.clone().ok_or(
            errors::ConnectorError::MissingRequiredField {
                field_name: "authentication_id",
            },
        )?;
        let auth_type =
            UnifiedAuthenticationServiceAuthType::try_from(&item.router_data.connector_auth_type)?;

        Ok(Self {
            authenticate_by: item.router_data.connector.clone(),
            auth_creds: auth_type,
            source_authentication_id: authentication_id,
            x_src_flow_id: item.router_data.request.x_src_flow_id.clone(),
            transaction_amount: Some(item.amount),
            transaction_currency: Some(item.router_data.request.transaction_currency),
            checkout_event_type: item.router_data.request.checkout_event_type.clone(),
            checkout_event_status: item.router_data.request.checkout_event_status.clone(),
            confirmation_status: item.router_data.request.confirmation_status.clone(),
            confirmation_reason: item.router_data.request.confirmation_reason.clone(),
            confirmation_timestamp: item.router_data.request.confirmation_timestamp.clone(),
            network_authorization_code: item.router_data.request.network_authorization_code.clone(),
            network_transaction_identifier: item
                .router_data
                .request
                .network_transaction_identifier
                .clone(),
            correlation_id: item.router_data.request.correlation_id.clone(),
            merchant_transaction_id: item.router_data.request.merchant_transaction_id.clone(),
        })
    }
}

// ThreeDs request
impl TryFrom<&UasPreAuthenticationRouterData>
    for UnifiedAuthenticationServicePreAuthenticateRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &UasPreAuthenticationRouterData) -> Result<Self, Self::Error> {
        let auth_type = UnifiedAuthenticationServiceAuthType::try_from(&item.connector_auth_type)?;
        let authentication_id =
            item.authentication_id
                .clone()
                .ok_or(errors::ConnectorError::MissingRequiredField {
                    field_name: "authentication_id",
                })?;

        let merchant_data = item.request.merchant_details.clone().ok_or(
            errors::ConnectorError::MissingRequiredField {
                field_name: "merchant_details",
            },
        )?;

        let merchant_details = MerchantDetails {
            merchant_id: merchant_data.merchant_id,
            merchant_name: merchant_data.merchant_name,
            merchant_category_code: merchant_data.merchant_category_code,
            merchant_country_code: merchant_data.merchant_country_code.clone(),
            endpoint_prefix: merchant_data.endpoint_prefix,
            three_ds_requestor_url: merchant_data.three_ds_requestor_url,
            three_ds_requestor_id: merchant_data.three_ds_requestor_id,
            three_ds_requestor_name: merchant_data.three_ds_requestor_name,
            configuration_id: None,
            notification_url: merchant_data.notification_url,
        };

        let acquirer = Acquirer {
            acquirer_bin: item.request.acquirer_bin.clone(),
            acquirer_merchant_id: item.request.acquirer_merchant_id.clone(),
            acquirer_country_code: merchant_data
                .merchant_country_code
                .map(|code| code.get_country_code()),
        };

        let service_details = Some(CtpServiceDetails {
            service_session_ids: None,
            merchant_details: Some(merchant_details),
        });

        let billing_address = item
            .request
            .billing_address
            .clone()
            .map(|address_wrap| Address {
                city: address_wrap
                    .address
                    .clone()
                    .and_then(|address| address.city),
                country: address_wrap
                    .address
                    .clone()
                    .and_then(|address| address.country),
                line1: address_wrap
                    .address
                    .clone()
                    .and_then(|address| address.line1),
                line2: address_wrap
                    .address
                    .clone()
                    .and_then(|address| address.line2),
                line3: address_wrap
                    .address
                    .clone()
                    .and_then(|address| address.line3),
                post_code: address_wrap.address.clone().and_then(|address| address.zip),
                state: address_wrap.address.and_then(|address| address.state),
            });

        Ok(Self {
            authenticate_by: item.connector.clone(),
            session_id: authentication_id.clone(),
            source_authentication_id: authentication_id,
            authentication_info: None,
            service_details,
            customer_details: None,
            pmt_details: item
                .request
                .payment_details
                .clone()
                .map(|details| PaymentDetails {
                    pan: details.pan,
                    digital_card_id: details.digital_card_id,
                    payment_data_type: details.payment_data_type,
                    encrypted_src_card_details: details.encrypted_src_card_details,
                    cardholder_name: details.cardholder_name,
                    card_token_number: details.card_token_number,
                    account_type: details.account_type,
                    card_expiry_month: details.card_expiry_month,
                    card_expiry_year: details.card_expiry_year,
                    card_cvc: details.card_cvc,
                }),
            auth_creds: auth_type,
            transaction_details: None,
            acquirer_details: Some(acquirer),
            billing_address,
        })
    }
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(tag = "auth_type")]
pub enum UnifiedAuthenticationServiceAuthType {
    HeaderKey {
        api_key: Secret<String>,
    },
    CertificateAuth {
        certificate: Secret<String>,
        private_key: Secret<String>,
    },
    SignatureKey {
        api_key: Secret<String>,
        key1: Secret<String>,
        api_secret: Secret<String>,
    },
    NoKey,
}

impl TryFrom<&ConnectorAuthType> for UnifiedAuthenticationServiceAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self::HeaderKey {
                api_key: api_key.clone(),
            }),
            ConnectorAuthType::SignatureKey {
                api_key,
                key1,
                api_secret,
            } => Ok(Self::SignatureKey {
                api_key: api_key.clone(),
                key1: key1.clone(),
                api_secret: api_secret.clone(),
            }),
            ConnectorAuthType::CertificateAuth {
                certificate,
                private_key,
            } => Ok(Self::CertificateAuth {
                certificate: certificate.clone(),
                private_key: private_key.clone(),
            }),
            ConnectorAuthType::NoKey => Ok(Self::NoKey),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "eligibility")]
pub enum Eligibility {
    None,
    TokenEligibilityResponse {
        token_eligibility_response: Box<TokenEligibilityResponse>,
    },
    ThreeDsEligibilityResponse {
        three_ds_eligibility_response: Box<ThreeDsEligibilityResponse>,
    },
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TokenEligibilityResponse {
    pub network_request_id: Option<String>,
    pub network_client_id: Option<String>,
    pub nonce: Option<String>,
    pub payment_method_details: Option<PaymentMethodDetails>,
    pub network_pan_enrollment_id: Option<String>,
    pub ignore_00_field: Option<String>,
    pub token_details: Option<TokenDetails>,
    pub network_provisioned_token_id: Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PaymentMethodDetails {
    pub ignore_01_field: String,
    pub cvv2_printed_ind: String,
    pub last4: String,
    pub exp_date_printed_ind: String,
    pub payment_account_reference: String,
    pub exp_year: String,
    pub exp_month: String,
    pub verification_results: VerificationResults,
    pub enabled_services: EnabledServices,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct VerificationResults {
    pub address_verification_code: String,
    pub cvv2_verification_code: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EnabledServices {
    pub merchant_presented_qr: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ThreeDsEligibilityResponse {
    pub three_ds_server_trans_id: String,
    pub scheme_id: Option<String>,
    pub acs_protocol_versions: Option<Vec<AcsProtocolVersion>>,
    pub ds_protocol_versions: Option<Vec<String>>,
    pub three_ds_method_data_form: Option<ThreeDsMethodDataForm>,
    pub three_ds_method_data: Option<ThreeDsMethodData>,
    pub error_details: Option<String>,
    pub is_card_found_in_2x_ranges: bool,
    pub directory_server_id: Option<String>,
}

impl ThreeDsEligibilityResponse {
    pub fn get_max_acs_protocol_version_if_available(&self) -> Option<AcsProtocolVersion> {
        let max_acs_version =
            self.acs_protocol_versions
                .as_ref()
                .and_then(|acs_protocol_versions| {
                    acs_protocol_versions
                        .iter()
                        .max_by_key(|acs_protocol_versions| acs_protocol_versions.version.clone())
                });
        max_acs_version.cloned()
    }
}

#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct AcsProtocolVersion {
    pub version: common_utils::types::SemanticVersion,
    pub acs_info_ind: Vec<String>,
    pub three_ds_method_url: Option<String>,
    pub supported_msg_ext: Option<Vec<SupportedMsgExt>>,
}

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

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

#[derive(Default, Clone, Serialize, Deserialize, Debug)]
pub struct ThreeDsMethodData {
    pub three_ds_method_notification_url: String,
    pub server_transaction_id: String,
}

#[derive(Serialize, Debug)]
pub struct UnifiedAuthenticationServiceAuthenticateRequest {
    pub authenticate_by: String,
    pub source_authentication_id: common_utils::id_type::AuthenticationId,
    pub transaction_details: TransactionDetails,
    pub device_details: DeviceDetails,
    pub customer_details: Option<CustomerDetails>,
    pub auth_creds: UnifiedAuthenticationServiceAuthType,
}

#[derive(Default, Debug, Serialize, PartialEq)]
pub struct ServiceDetails {
    pub service_session_ids: Option<ServiceSessionIds>,
    pub merchant_details: Option<MerchantDetails>,
}

#[derive(Serialize, Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum UnifiedAuthenticationServiceAuthenticateResponse {
    Success(Box<ThreeDsResponseData>),
    Failure(UnifiedAuthenticationServiceErrorResponse),
}

#[derive(Serialize, Debug, Clone, Deserialize)]
pub struct ThreeDsResponseData {
    pub three_ds_auth_response: ThreeDsAuthDetails,
}

#[derive(Serialize, Debug, Clone, Deserialize)]
pub struct ThreeDsAuthDetails {
    pub three_ds_server_trans_id: String,
    pub acs_trans_id: String,
    pub acs_reference_number: String,
    pub acs_operator_id: Option<String>,
    pub ds_reference_number: Option<String>,
    pub ds_trans_id: String,
    pub sdk_trans_id: Option<String>,
    pub trans_status: common_enums::TransactionStatus,
    pub acs_challenge_mandated: Option<ACSChallengeMandatedEnum>,
    pub message_type: Option<String>,
    pub message_version: Option<String>,
    pub acs_url: Option<url::Url>,
    pub challenge_request: Option<String>,
    pub challenge_request_key: Option<String>,
    pub acs_signed_content: Option<String>,
    pub authentication_value: Option<Secret<String>>,
    pub eci: Option<String>,
    pub challenge_code: Option<String>,
    pub challenge_cancel: Option<String>,
    pub challenge_code_reason: Option<String>,
    pub message_extension: Option<common_utils::pii::SecretSerdeValue>,
}

#[derive(Debug, Serialize, Clone, Copy, Deserialize)]
pub enum ACSChallengeMandatedEnum {
    /// Challenge is mandated
    Y,
    /// Challenge is not mandated
    N,
}

#[derive(Clone, Serialize, Debug)]
pub struct DeviceDetails {
    pub device_channel: api_models::payments::DeviceChannel,
    pub browser_info: Option<BrowserInfo>,
    pub sdk_info: Option<api_models::payments::SdkInformation>,
}

impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasAuthenticationRouterData>>
    for UnifiedAuthenticationServiceAuthenticateRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &UnifiedAuthenticationServiceRouterData<&UasAuthenticationRouterData>,
    ) -> Result<Self, Self::Error> {
        let authentication_id = item.router_data.authentication_id.clone().ok_or(
            errors::ConnectorError::MissingRequiredField {
                field_name: "authentication_id",
            },
        )?;

        let browser_info =
            if let Some(browser_details) = item.router_data.request.browser_details.clone() {
                BrowserInfo {
                    color_depth: browser_details.color_depth,
                    java_enabled: browser_details.java_enabled,
                    java_script_enabled: browser_details.java_script_enabled,
                    language: browser_details.language,
                    screen_height: browser_details.screen_height,
                    screen_width: browser_details.screen_width,
                    time_zone: browser_details.time_zone,
                    ip_address: browser_details.ip_address,
                    accept_header: browser_details.accept_header,
                    user_agent: browser_details.user_agent,
                    os_type: browser_details.os_type,
                    os_version: browser_details.os_version,
                    device_model: browser_details.device_model,
                    accept_language: browser_details.accept_language,
                }
            } else {
                BrowserInfo::default()
            };

        let three_ds_data = ThreeDSData {
            preferred_protocol_version: item
                .router_data
                .request
                .pre_authentication_data
                .message_version
                .clone(),
            threeds_method_comp_ind: item.router_data.request.threeds_method_comp_ind.clone(),
            force_3ds_challenge: item
                .router_data
                .request
                .transaction_details
                .force_3ds_challenge,
            psd2_sca_exemption_type: item
                .router_data
                .request
                .transaction_details
                .psd2_sca_exemption_type,
        };

        let device_details = DeviceDetails {
            device_channel: item
                .router_data
                .request
                .transaction_details
                .device_channel
                .clone()
                .ok_or(errors::ConnectorError::MissingRequiredField {
                    field_name: "device_channel",
                })?,
            browser_info: Some(browser_info),
            sdk_info: item.router_data.request.sdk_information.clone(),
        };

        let message_category = item.router_data.request.transaction_details.message_category.clone().map(|category| match category {
            hyperswitch_domain_models::router_request_types::authentication::MessageCategory::Payment => MessageCategory::Payment ,
            hyperswitch_domain_models::router_request_types::authentication::MessageCategory::NonPayment => MessageCategory::NonPayment,
        });

        let transaction_details = TransactionDetails {
            amount: item.amount,
            currency: item
                .router_data
                .request
                .transaction_details
                .currency
                .get_required_value("currency")
                .change_context(errors::ConnectorError::InSufficientBalanceInPaymentMethod)?,
            date: None,
            pan_source: None,
            protection_type: None,
            entry_mode: None,
            transaction_type: None,
            otp_value: None,
            three_ds_data: Some(three_ds_data),
            message_category,
        };
        let auth_type =
            UnifiedAuthenticationServiceAuthType::try_from(&item.router_data.connector_auth_type)?;

        Ok(Self {
            authenticate_by: item.router_data.connector.clone(),
            source_authentication_id: authentication_id,
            transaction_details,
            auth_creds: auth_type,
            device_details,
            customer_details: None,
        })
    }
}

impl<F, T>
    TryFrom<
        ResponseRouterData<
            F,
            UnifiedAuthenticationServiceAuthenticateResponse,
            T,
            UasAuthenticationResponseData,
        >,
    > for RouterData<F, T, UasAuthenticationResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            UnifiedAuthenticationServiceAuthenticateResponse,
            T,
            UasAuthenticationResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let response = match item.response {
            UnifiedAuthenticationServiceAuthenticateResponse::Success(auth_response) => {
                let authn_flow_type = match auth_response.three_ds_auth_response.trans_status {
                    common_enums::TransactionStatus::ChallengeRequired => {
                        AuthNFlowType::Challenge(Box::new(ChallengeParams {
                            acs_url: auth_response.three_ds_auth_response.acs_url.clone(),
                            challenge_request: auth_response
                                .three_ds_auth_response
                                .challenge_request,
                            challenge_request_key: auth_response
                                .three_ds_auth_response
                                .challenge_request_key,
                            acs_reference_number: Some(
                                auth_response.three_ds_auth_response.acs_reference_number,
                            ),
                            acs_trans_id: Some(auth_response.three_ds_auth_response.acs_trans_id),
                            three_dsserver_trans_id: Some(
                                auth_response
                                    .three_ds_auth_response
                                    .three_ds_server_trans_id,
                            ),
                            acs_signed_content: auth_response
                                .three_ds_auth_response
                                .acs_signed_content,
                        }))
                    }
                    _ => AuthNFlowType::Frictionless,
                };
                Ok(UasAuthenticationResponseData::Authentication {
                    authentication_details: hyperswitch_domain_models::router_request_types::unified_authentication_service::AuthenticationDetails {
                        authn_flow_type,
                        authentication_value: auth_response.three_ds_auth_response.authentication_value,
                        trans_status: auth_response.three_ds_auth_response.trans_status,
                        connector_metadata: None,
                        ds_trans_id: Some(auth_response.three_ds_auth_response.ds_trans_id),
                        eci: auth_response.three_ds_auth_response.eci,
                        challenge_code: auth_response.three_ds_auth_response.challenge_code,
                        challenge_cancel: auth_response.three_ds_auth_response.challenge_cancel,
                        challenge_code_reason: auth_response.three_ds_auth_response.challenge_code_reason,
                        message_extension:  auth_response.three_ds_auth_response.message_extension,
                    },
                })
            }
            UnifiedAuthenticationServiceAuthenticateResponse::Failure(error_response) => {
                Err(hyperswitch_domain_models::router_data::ErrorResponse {
                    code: hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string(),
                    message: error_response.error.clone(),
                    reason: None,
                    status_code: item.http_code,
                    attempt_status: None,
                    connector_transaction_id: None,
                    network_advice_code: None,
                    network_decline_code: None,
                    network_error_message: None,
                    connector_metadata: None,
                })
            }
        };

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