// File: crates/hyperswitch_connectors/src/connectors/nordea/requests.rs

use common_utils::types::StringMajorUnit;
use masking::Secret;
use serde::{Deserialize, Serialize};

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

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NordeaOAuthRequest {
    /// Country is a mandatory parameter with possible values FI, DK, NO or SE
    pub country: api_models::enums::CountryAlpha2,
    /// Duration of access authorization in minutes. range: 1 to 259200 minutes (180 days).
    /// Duration should be left empty if the request includes PAYMENTS_SINGLE_SCA scope.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration: Option<i32>,
    /// Maximum transaction history in months. Optional if ACCOUNTS_TRANSACTIONS scope is requested. Default=2 months. range: 1 to 18 months
    #[serde(rename = "max_tx_history")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub maximum_transaction_history: Option<i32>,
    /// Redirect URI you used when this application was registered with Nordea.
    pub redirect_uri: String,
    pub scope: Vec<AccessScope>,
    /// The OAuth2 state parameter. This is a nonce and should be used to prevent CSRF attacks.
    pub state: Secret<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum GrantType {
    AuthorizationCode,
    RefreshToken,
}

// To be passed in query parameters for OAuth scopes
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AccessScope {
    AccountsBasic,
    AccountsBalances,
    AccountsDetails,
    AccountsTransactions,
    PaymentsMultiple,
    PaymentsSingleSca,
    CardsInformation,
    CardsTransactions,
}

#[derive(Debug, Clone, Serialize)]
pub struct NordeaOAuthExchangeRequest {
    /// authorization_code flow
    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<Secret<String>>,
    pub grant_type: GrantType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub redirect_uri: Option<String>,
    /// refresh_token flow
    #[serde(skip_serializing_if = "Option::is_none")]
    pub refresh_token: Option<Secret<String>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AccountType {
    /// International bank account number
    Iban,
    /// National bank account number of Sweden
    BbanSe,
    /// National bank account number of Denmark
    BbanDk,
    /// National bank account number of Norway
    BbanNo,
    /// Bankgiro number
    Bgnr,
    /// Plusgiro number
    Pgnr,
    /// Creditor number (Giro) Denmark
    GiroDk,
    /// Any bank account number without any check-digit validations
    BbanOther,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct AccountNumber {
    /// Type of account number
    #[serde(rename = "_type")]
    pub account_type: AccountType,
    /// Currency of the account (Mandatory for debtor, Optional for creditor)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub currency: Option<api_models::enums::Currency>,
    /// Actual account number
    pub value: Secret<String>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CreditorAccountReference {
    /// RF or Invoice for FI Sepa payments, OCR for NO Kid payments and 01, 04, 15, 71, 73 or 75 for Danish Transfer Form payments.
    #[serde(rename = "_type")]
    pub creditor_reference_type: String,
    /// Actual reference number
    #[serde(skip_serializing_if = "Option::is_none")]
    pub value: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct NordeaAddress {
    /// First line of the address. e.g. Street address
    pub line1: Option<Secret<String>>,
    /// Second line of the address (optional). e.g. Postal address
    pub line2: Option<Secret<String>>,
    /// Third line of the address (optional). e.g. Country
    pub line3: Option<Secret<String>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CreditorBank {
    /// Address
    pub address: Option<NordeaAddress>,
    /// Bank code
    pub bank_code: Option<String>,
    /// Business identifier code (BIC) of the creditor bank.
    /// This information is required, if the creditor account number is not in IBAN format.
    #[serde(rename = "bic")]
    pub business_identifier_code: Option<Secret<String>>,
    /// Country of the creditor bank. Only ISO 3166 alpha-2 codes are used.
    pub country: api_models::enums::CountryAlpha2,
    /// Name of the creditor bank.
    #[serde(rename = "name")]
    pub bank_name: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CreditorAccount {
    /// Account number
    pub account: AccountNumber,
    /// Creditor bank information.
    pub bank: Option<CreditorBank>,
    /// Country of the creditor
    pub country: Option<api_models::enums::CountryAlpha2>,
    /// Address
    pub creditor_address: Option<NordeaAddress>,
    /// Message for the creditor to appear on their transaction.
    /// Max length: FI SEPA:140; SE:12; PGNR:25; BGNR:150; DK: 40 (Instant/Express: 140); NO: 140
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    /// Name of the creditor.
    /// Max length: FI SEPA: 30; SE: 35; DK: Not use (Mandatory for Instant/Express payments: 70);
    /// NO: 30 (mandatory for Straksbetaling/Express payments).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<Secret<String>>,
    /// Creditor reference number.
    /// Either Reference or Message has to be passed in the Request
    pub reference: Option<CreditorAccountReference>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct DebitorAccount {
    /// Account number
    pub account: AccountNumber,
    /// Own message to be on the debtor's transaction.
    /// Max length 20. NB: This field is not supported for SEPA and Norwegian payments and will be ignored.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct InstructedAmount {
    /// Monetary amount of the payment. Max (digits+decimals): FI SEPA: (9+2); SE:(11+2); DK:(7+2); NO:(7+2)
    pub amount: StringMajorUnit,
    /// Currency code according to ISO 4217.
    /// NB: Possible value depends on the type of the payment.
    /// For domestic payment it should be same as debtor local currency,
    /// for SEPA it must be EUR,
    /// for cross border it can be Currency code according to ISO 4217.
    pub currency: api_models::enums::Currency,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RecurrenceType {
    Daily,
    Weekly,
    Biweekly,
    MonthlySameDay,
    MonthlyEom,
    QuartelySameDay,
    QuartelyEom,
    SemiAnnuallySameDay,
    SemiAnnuallyEom,
    TriAnnuallySameDay,
    YearlySameDay,
    YearlyEom,
    EveryMinuteSandboxOnly,
}

#[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
#[allow(dead_code)] // This is an optional field and not having it is fine
pub enum FundsAvailabilityRequest {
    True,
    False,
}

#[derive(Debug, Serialize, PartialEq, Clone)]
pub enum PaymentsUrgency {
    Standard,
    Express,
    Sameday,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct RecurringInformation {
    /// Number of occurrences. Not applicable for NO (use end_date instead). Format: int32.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub count: Option<i32>,
    /// Date on which the recurrence will end. Format: YYYY-MM-DD. Applicable only for Norway. Format: date
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_date: Option<String>,
    /// Repeats every interval
    #[serde(skip_serializing_if = "Option::is_none")]
    pub recurrence_type: Option<RecurrenceType>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TppCategory {
    Error,
    Warning,
    Info,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TppCode {
    Ds0a,
    Narr,
    Am21,
    Am04,
    Tm01,
    Am12,
    Rc06,
    Rc07,
    Rc04,
    Ag06,
    Bg06,
    Be22,
    Be20,
    Ff06,
    Be19,
    Am03,
    Am11,
    Ch04,
    Dt01,
    Ch03,
    Ff08,
    Ac10,
    Ac02,
    Ag08,
    Rr09,
    Rc11,
    Ff10,
    Rr10,
    Ff05,
    Ch15,
    Ff04,
    Ac11,
    Ac03,
    Ac13,
    Ac14,
    Ac05,
    Ac06,
    Rr07,
    Dt03,
    Am13,
    Ds24,
    Fr01,
    Am02,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct ThirdPartyMessages {
    /// Category of the TPP message, INFO is further information, WARNING is something can be fixed, ERROR possibly non fixable issue
    #[serde(skip_serializing_if = "Option::is_none")]
    pub category: Option<TppCategory>,
    /// Additional code that is combined with the text
    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<TppCode>,
    /// Additional explaining text to the TPP
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text: Option<String>,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct NordeaPaymentsRequest {
    /// Creditor of the payment
    #[serde(rename = "creditor")]
    pub creditor_account: CreditorAccount,
    /// Debtor of the payment
    #[serde(rename = "debtor")]
    pub debitor_account: DebitorAccount,
    /// Free text reference that can be provided by the PSU.
    /// This identification is passed on throughout the entire end-to-end chain.
    /// Only in scope for Nordea Business DK.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_to_end_identification: Option<String>,
    /// Unique identification as assigned by a partner to identify the payment.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub external_id: Option<String>,
    /// Monetary amount
    pub instructed_amount: InstructedAmount,
    /// Recurring information
    #[serde(skip_serializing_if = "Option::is_none")]
    pub recurring: Option<RecurringInformation>,
    /// Use as an indicator that the supplied payment (amount, currency and debtor account)
    /// should be used to check whether the funds are available for further processing - at this moment.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub request_availability_of_funds: Option<FundsAvailabilityRequest>,
    /// Choose a preferred execution date (or leave blank for today's date).
    /// This should be a valid bank day, and depending on the country the date will either be
    /// pushed to the next valid bank day, or return an error if a non-banking day date was
    /// supplied (all dates accepted in sandbox). SEPA: max +5 years from yesterday,
    /// Domestic: max. +1 year from yesterday. NB: Not supported for Own transfer Non-Recurring Norway.
    /// Format:date.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub requested_execution_date: Option<String>,
    /// Additional messages for third parties
    #[serde(rename = "tpp_messages")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tpp_messages: Option<Vec<ThirdPartyMessages>>,
    /// Urgency of the payment. NB: This field is supported for
    /// DK Domestic ('standard' and 'express')
    /// NO Domestic bank transfer payments ('standard'). Use 'express' for Straksbetaling (Instant payment).
    /// FI Sepa ('standard' and 'express') All other payment types ignore this input.
    /// For further details on urgencies and cut-offs, refer to the Nordea website. Value 'sameday' is marked as deprecated and will be removed in the future.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub urgency: Option<PaymentsUrgency>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum NordeaAuthenticationMethod {
    Mta,
    #[serde(rename = "CCALC (Deprecated)")]
    Ccalc,
    Qrt,
    CardRdr,
    BankidSe,
    QrtSe,
    BankidNo,
    BankidmNo,
    MtaNo,
    #[serde(rename = "NEMID_2F")]
    Nemid2f,
    Mitid,
    MtaDk,
    QrtDk,
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "snake_case")]
pub enum NordeaConfirmLanguage {
    Fi,
    Da,
    Sv,
    En,
    No,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct NordeaPaymentsConfirmRequest {
    /// Authentication method to use for the signing of payment.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub authentication_method: Option<NordeaAuthenticationMethod>,
    /// Language of the signing page that will be displayed to client, ISO639-1 and 639-2, default=en
    #[serde(skip_serializing_if = "Option::is_none")]
    pub language: Option<NordeaConfirmLanguage>,
    pub payments_ids: Vec<String>,
    pub redirect_url: Option<String>,
    pub state: Option<String>,
}


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

use common_enums::enums;
use common_utils::{
    pii::{Email, IpAddress},
    types::FloatMajorUnit,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{Card, PaymentMethodData},
    router_data::{ConnectorAuthType, RouterData},
    router_flow_types::refunds::{Execute, RSync},
    router_request_types::{ResponseId, SetupMandateRequestData},
    router_response_types::{PaymentsResponseData, RefundsResponseData},
    types::{
        PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData,
        PaymentsSyncRouterData, RefundsRouterData, SetupMandateRouterData,
    },
};
use hyperswitch_interfaces::errors;
use masking::Secret;
use serde::{Deserialize, Serialize};

use crate::{
    types::{
        PaymentsCancelResponseRouterData, PaymentsCaptureResponseRouterData,
        PaymentsResponseRouterData, PaymentsSyncResponseRouterData, RefundsResponseRouterData,
        ResponseRouterData,
    },
    utils::{
        self, AddressDetailsData, BrowserInformationData, CardData, PaymentsAuthorizeRequestData,
        PaymentsCancelRequestData, PaymentsCaptureRequestData, PaymentsSetupMandateRequestData,
        RefundsRequestData, RouterData as RouterDataUtils,
    },
};

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

impl<T> TryFrom<(FloatMajorUnit, T)> for HelcimRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from((amount, item): (FloatMajorUnit, T)) -> Result<Self, Self::Error> {
        Ok(Self {
            amount,
            router_data: item,
        })
    }
}

pub fn check_currency(
    currency: enums::Currency,
) -> Result<enums::Currency, errors::ConnectorError> {
    if currency == enums::Currency::USD {
        Ok(currency)
    } else {
        Err(errors::ConnectorError::NotSupported {
            message: format!("currency {currency} is not supported for this merchant account"),
            connector: "Helcim",
        })?
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimVerifyRequest {
    currency: enums::Currency,
    ip_address: Secret<String, IpAddress>,
    card_data: HelcimCard,
    billing_address: HelcimBillingAddress,
    #[serde(skip_serializing_if = "Option::is_none")]
    ecommerce: Option<bool>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimPaymentsRequest {
    amount: FloatMajorUnit,
    currency: enums::Currency,
    ip_address: Secret<String, IpAddress>,
    card_data: HelcimCard,
    invoice: HelcimInvoice,
    billing_address: HelcimBillingAddress,
    //The ecommerce field is an optional field in Connector Helcim.
    //Setting the ecommerce field to true activates the Helcim Fraud Defender.
    #[serde(skip_serializing_if = "Option::is_none")]
    ecommerce: Option<bool>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimBillingAddress {
    name: Secret<String>,
    street1: Secret<String>,
    postal_code: Secret<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    street2: Option<Secret<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    city: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    email: Option<Email>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimInvoice {
    invoice_number: String,
    line_items: Vec<HelcimLineItems>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimLineItems {
    description: String,
    quantity: u8,
    price: FloatMajorUnit,
    total: FloatMajorUnit,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimCard {
    card_number: cards::CardNumber,
    card_expiry: Secret<String>,
    card_c_v_v: Secret<String>,
}

impl TryFrom<(&SetupMandateRouterData, &Card)> for HelcimVerifyRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: (&SetupMandateRouterData, &Card)) -> Result<Self, Self::Error> {
        let (item, req_card) = value;
        let card_data = HelcimCard {
            card_expiry: req_card
                .get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?,
            card_number: req_card.card_number.clone(),
            card_c_v_v: req_card.card_cvc.clone(),
        };
        let req_address = item.get_billing_address()?.to_owned();

        let billing_address = HelcimBillingAddress {
            name: req_address.get_full_name()?,
            street1: req_address.get_line1()?.to_owned(),
            postal_code: req_address.get_zip()?.to_owned(),
            street2: req_address.line2,
            city: req_address.city,
            email: item.request.email.clone(),
        };
        let ip_address = item.request.get_browser_info()?.get_ip_address()?;
        let currency = check_currency(item.request.currency)?;
        Ok(Self {
            currency,
            ip_address,
            card_data,
            billing_address,
            ecommerce: None,
        })
    }
}

impl TryFrom<&SetupMandateRouterData> for HelcimVerifyRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &SetupMandateRouterData) -> Result<Self, Self::Error> {
        match item.request.payment_method_data.clone() {
            PaymentMethodData::Card(req_card) => Self::try_from((item, &req_card)),
            PaymentMethodData::BankTransfer(_) => {
                Err(errors::ConnectorError::NotImplemented("Payment Method".to_string()).into())
            }
            PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankDebit(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Helcim"),
                ))?
            }
        }
    }
}

impl TryFrom<(&HelcimRouterData<&PaymentsAuthorizeRouterData>, &Card)> for HelcimPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        value: (&HelcimRouterData<&PaymentsAuthorizeRouterData>, &Card),
    ) -> Result<Self, Self::Error> {
        let (item, req_card) = value;
        let card_data = HelcimCard {
            card_expiry: req_card
                .get_card_expiry_month_year_2_digit_with_delimiter("".to_string())?,
            card_number: req_card.card_number.clone(),
            card_c_v_v: req_card.card_cvc.clone(),
        };
        let req_address = item
            .router_data
            .get_billing()?
            .to_owned()
            .address
            .ok_or_else(utils::missing_field_err("billing.address"))?;

        let billing_address = HelcimBillingAddress {
            name: req_address.get_full_name()?,
            street1: req_address.get_line1()?.to_owned(),
            postal_code: req_address.get_zip()?.to_owned(),
            street2: req_address.line2,
            city: req_address.city,
            email: item.router_data.request.email.clone(),
        };

        let ip_address = item
            .router_data
            .request
            .get_browser_info()?
            .get_ip_address()?;
        let line_items = vec![
            (HelcimLineItems {
                description: item
                    .router_data
                    .description
                    .clone()
                    .unwrap_or("No Description".to_string()),
                // By default quantity is set to 1 and price and total is set to amount because these three fields are required to generate an invoice.
                quantity: 1,
                price: item.amount,
                total: item.amount,
            }),
        ];
        let invoice = HelcimInvoice {
            invoice_number: item.router_data.connector_request_reference_id.clone(),
            line_items,
        };
        let currency = check_currency(item.router_data.request.currency)?;
        Ok(Self {
            amount: item.amount,
            currency,
            ip_address,
            card_data,
            invoice,
            billing_address,
            ecommerce: None,
        })
    }
}

impl TryFrom<&HelcimRouterData<&PaymentsAuthorizeRouterData>> for HelcimPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &HelcimRouterData<&PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        if item.router_data.is_three_ds() {
            Err(errors::ConnectorError::NotSupported {
                message: "Cards 3DS".to_string(),
                connector: "Helcim",
            })?
        }
        match item.router_data.request.payment_method_data.clone() {
            PaymentMethodData::Card(req_card) => Self::try_from((item, &req_card)),
            PaymentMethodData::BankTransfer(_) => {
                Err(errors::ConnectorError::NotImplemented("Payment Method".to_string()).into())
            }
            PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Wallet(_)
            | PaymentMethodData::PayLater(_)
            | PaymentMethodData::BankRedirect(_)
            | PaymentMethodData::BankDebit(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("Helcim"),
                ))?
            }
        }
    }
}

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

impl TryFrom<&ConnectorAuthType> for HelcimAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        match auth_type {
            ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
                api_key: api_key.to_owned(),
            }),
            _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
        }
    }
}
// PaymentsResponse
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum HelcimPaymentStatus {
    Approved,
    Declined,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum HelcimTransactionType {
    Purchase,
    PreAuth,
    Capture,
    Verify,
    Reverse,
}

impl From<HelcimPaymentsResponse> for enums::AttemptStatus {
    fn from(item: HelcimPaymentsResponse) -> Self {
        match item.transaction_type {
            HelcimTransactionType::Purchase | HelcimTransactionType::Verify => match item.status {
                HelcimPaymentStatus::Approved => Self::Charged,
                HelcimPaymentStatus::Declined => Self::Failure,
            },
            HelcimTransactionType::PreAuth => match item.status {
                HelcimPaymentStatus::Approved => Self::Authorized,
                HelcimPaymentStatus::Declined => Self::AuthorizationFailed,
            },
            HelcimTransactionType::Capture => match item.status {
                HelcimPaymentStatus::Approved => Self::Charged,
                HelcimPaymentStatus::Declined => Self::CaptureFailed,
            },
            HelcimTransactionType::Reverse => match item.status {
                HelcimPaymentStatus::Approved => Self::Voided,
                HelcimPaymentStatus::Declined => Self::VoidFailed,
            },
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimPaymentsResponse {
    status: HelcimPaymentStatus,
    transaction_id: u64,
    invoice_number: Option<String>,
    #[serde(rename = "type")]
    transaction_type: HelcimTransactionType,
}

impl<F>
    TryFrom<
        ResponseRouterData<
            F,
            HelcimPaymentsResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    > for RouterData<F, SetupMandateRequestData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            F,
            HelcimPaymentsResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(
                    item.response.transaction_id.to_string(),
                ),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item.response.invoice_number.clone(),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            status: enums::AttemptStatus::from(item.response),
            ..item.data
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct HelcimMetaData {
    pub preauth_transaction_id: u64,
}

impl TryFrom<PaymentsResponseRouterData<HelcimPaymentsResponse>> for PaymentsAuthorizeRouterData {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsResponseRouterData<HelcimPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        //PreAuth Transaction ID is stored in connector metadata
        //Initially resource_id is stored as NoResponseID for manual capture
        //After Capture Transaction is completed it is updated to store the Capture ID
        let resource_id = if item.data.request.is_auto_capture()? {
            ResponseId::ConnectorTransactionId(item.response.transaction_id.to_string())
        } else {
            ResponseId::NoResponseId
        };
        let connector_metadata = if !item.data.request.is_auto_capture()? {
            Some(serde_json::json!(HelcimMetaData {
                preauth_transaction_id: item.response.transaction_id,
            }))
        } else {
            None
        };
        Ok(Self {
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id,
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata,
                network_txn_id: None,
                connector_response_reference_id: item.response.invoice_number.clone(),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            status: enums::AttemptStatus::from(item.response),
            ..item.data
        })
    }
}

// impl utils::MultipleCaptureSyncResponse for HelcimPaymentsResponse {
//     fn get_connector_capture_id(&self) -> String {
//         self.transaction_id.to_string()
//     }

//     fn get_capture_attempt_status(&self) -> diesel_models::enums::AttemptStatus {
//         enums::AttemptStatus::from(self.to_owned())
//     }

//     fn is_capture_response(&self) -> bool {
//         true
//     }

//     fn get_amount_captured(&self) -> Option<i64> {
//         Some(self.amount)
//     }
//     fn get_connector_reference_id(&self) -> Option<String> {
//         None
//     }
// }

impl TryFrom<PaymentsSyncResponseRouterData<HelcimPaymentsResponse>> for PaymentsSyncRouterData {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsSyncResponseRouterData<HelcimPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        match item.data.request.sync_type {
            hyperswitch_domain_models::router_request_types::SyncRequestType::SinglePaymentSync => Ok(Self {
                response: Ok(PaymentsResponseData::TransactionResponse {
                    resource_id: ResponseId::ConnectorTransactionId(
                        item.response.transaction_id.to_string(),
                    ),
                    redirection_data: Box::new(None),
                    mandate_reference: Box::new(None),
                    connector_metadata: None,
                    network_txn_id: None,
                    connector_response_reference_id: item.response.invoice_number.clone(),
                    incremental_authorization_allowed: None,
                    charges: None,
                }),
                status: enums::AttemptStatus::from(item.response),
                ..item.data
            }),
            hyperswitch_domain_models::router_request_types::SyncRequestType::MultipleCaptureSync(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    "manual multiple capture sync".to_string(),
                )
                .into())
                // let capture_sync_response_list =
                //     utils::construct_captures_response_hashmap(vec![item.response]);
                // Ok(Self {
                //     response: Ok(PaymentsResponseData::MultipleCaptureResponse {
                //         capture_sync_response_list,
                //     }),
                //     ..item.data
                // })
            }
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimCaptureRequest {
    pre_auth_transaction_id: u64,
    amount: FloatMajorUnit,
    ip_address: Secret<String, IpAddress>,
    #[serde(skip_serializing_if = "Option::is_none")]
    ecommerce: Option<bool>,
}

impl TryFrom<&HelcimRouterData<&PaymentsCaptureRouterData>> for HelcimCaptureRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &HelcimRouterData<&PaymentsCaptureRouterData>) -> Result<Self, Self::Error> {
        let ip_address = item
            .router_data
            .request
            .get_browser_info()?
            .get_ip_address()?;
        Ok(Self {
            pre_auth_transaction_id: item
                .router_data
                .request
                .connector_transaction_id
                .parse::<u64>()
                .change_context(errors::ConnectorError::RequestEncodingFailed)?,
            amount: item.amount,
            ip_address,
            ecommerce: None,
        })
    }
}

impl TryFrom<PaymentsCaptureResponseRouterData<HelcimPaymentsResponse>>
    for PaymentsCaptureRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsCaptureResponseRouterData<HelcimPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(
                    item.response.transaction_id.to_string(),
                ),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item.response.invoice_number.clone(),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            status: enums::AttemptStatus::from(item.response),
            ..item.data
        })
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimVoidRequest {
    card_transaction_id: u64,
    ip_address: Secret<String, IpAddress>,
    #[serde(skip_serializing_if = "Option::is_none")]
    ecommerce: Option<bool>,
}

impl TryFrom<&PaymentsCancelRouterData> for HelcimVoidRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        let ip_address = item.request.get_browser_info()?.get_ip_address()?;
        Ok(Self {
            card_transaction_id: item
                .request
                .connector_transaction_id
                .parse::<u64>()
                .change_context(errors::ConnectorError::RequestEncodingFailed)?,
            ip_address,
            ecommerce: None,
        })
    }
}

impl TryFrom<PaymentsCancelResponseRouterData<HelcimPaymentsResponse>>
    for PaymentsCancelRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PaymentsCancelResponseRouterData<HelcimPaymentsResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(
                    item.response.transaction_id.to_string(),
                ),
                redirection_data: Box::new(None),
                mandate_reference: Box::new(None),
                connector_metadata: None,
                network_txn_id: None,
                connector_response_reference_id: item.response.invoice_number.clone(),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            status: enums::AttemptStatus::from(item.response),
            ..item.data
        })
    }
}

// REFUND :
// Type definition for RefundRequest
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HelcimRefundRequest {
    amount: FloatMajorUnit,
    original_transaction_id: u64,
    ip_address: Secret<String, IpAddress>,
    #[serde(skip_serializing_if = "Option::is_none")]
    ecommerce: Option<bool>,
}

impl<F> TryFrom<&HelcimRouterData<&RefundsRouterData<F>>> for HelcimRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &HelcimRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
        let original_transaction_id = item
            .router_data
            .request
            .connector_transaction_id
            .parse::<u64>()
            .change_context(errors::ConnectorError::RequestEncodingFailed)?;

        let ip_address = item
            .router_data
            .request
            .get_browser_info()?
            .get_ip_address()?;
        Ok(Self {
            amount: item.amount,
            original_transaction_id,
            ip_address,
            ecommerce: None,
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum HelcimRefundTransactionType {
    Refund,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RefundResponse {
    status: HelcimPaymentStatus,
    transaction_id: u64,
    #[serde(rename = "type")]
    transaction_type: HelcimRefundTransactionType,
}

impl From<RefundResponse> for enums::RefundStatus {
    fn from(item: RefundResponse) -> Self {
        match item.transaction_type {
            HelcimRefundTransactionType::Refund => match item.status {
                HelcimPaymentStatus::Approved => Self::Success,
                HelcimPaymentStatus::Declined => Self::Failure,
            },
        }
    }
}

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

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

#[derive(Debug, strum::Display, Deserialize, Serialize)]
#[serde(untagged)]
pub enum HelcimErrorTypes {
    StringType(String),
    JsonType(serde_json::Value),
}

#[derive(Debug, Deserialize, Serialize)]
pub struct HelcimPaymentsErrorResponse {
    pub errors: HelcimErrorTypes,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum HelcimErrorResponse {
    Payment(HelcimPaymentsErrorResponse),
    General(String),
}


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

use common_enums::enums;
use common_utils::{
    errors::ParsingError,
    pii::{Email, IpAddress},
    request::Method,
    types::{MinorUnit, StringMajorUnit},
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payment_method_data::{
        BankRedirectData, BankTransferData, PayLaterData, PaymentMethodData, WalletData,
    },
    router_data::{AccessToken, ConnectorAuthType, RouterData},
    router_flow_types::{
        refunds::{Execute, RSync},
        PSync,
    },
    router_request_types::{PaymentsSyncData, ResponseId},
    router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
    types,
};
use hyperswitch_interfaces::errors;
use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use url::Url;
use uuid::Uuid;

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

pub struct AirwallexAuthType {
    pub x_api_key: Secret<String>,
    pub x_client_id: Secret<String>,
}

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

    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        if let ConnectorAuthType::BodyKey { api_key, key1 } = auth_type {
            Ok(Self {
                x_api_key: api_key.clone(),
                x_client_id: key1.clone(),
            })
        } else {
            Err(errors::ConnectorError::FailedToObtainAuthType)?
        }
    }
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct ReferrerData {
    #[serde(rename = "type")]
    r_type: String,
    version: String,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexIntentRequest {
    // Unique ID to be sent for each transaction/operation request to the connector
    request_id: String,
    amount: StringMajorUnit,
    currency: enums::Currency,
    //ID created in merchant's order system that corresponds to this PaymentIntent.
    merchant_order_id: String,
    // This data is required to whitelist Hyperswitch at Airwallex.
    referrer_data: ReferrerData,
    order: Option<AirwallexOrderData>,
}

impl TryFrom<&AirwallexRouterData<&types::CreateOrderRouterData>> for AirwallexIntentRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &AirwallexRouterData<&types::CreateOrderRouterData>,
    ) -> Result<Self, Self::Error> {
        let referrer_data = ReferrerData {
            r_type: "hyperswitch".to_string(),
            version: "1.0.0".to_string(),
        };
        let amount = item.amount.clone();
        let currency = item.router_data.request.currency;

        let order = match item.router_data.request.payment_method_data {
            Some(PaymentMethodData::PayLater(_)) => Some(
                item.router_data
                    .request
                    .order_details
                    .as_ref()
                    .map(|order_data| AirwallexOrderData {
                        products: order_data
                            .iter()
                            .map(|product| AirwallexProductData {
                                name: product.product_name.clone(),
                                quantity: product.quantity,
                                unit_price: product.amount,
                            })
                            .collect(),
                        shipping: Some(AirwallexShippingData {
                            first_name: item.router_data.get_optional_shipping_first_name(),
                            last_name: item.router_data.get_optional_shipping_last_name(),
                            phone_number: item.router_data.get_optional_shipping_phone_number(),
                            address: AirwallexPLShippingAddress {
                                country_code: item.router_data.get_optional_shipping_country(),
                                city: item.router_data.get_optional_shipping_city(),
                                street: item.router_data.get_optional_shipping_line1(),
                                postcode: item.router_data.get_optional_shipping_zip(),
                            },
                        }),
                    })
                    .ok_or(errors::ConnectorError::MissingRequiredField {
                        field_name: "order_details",
                    })?,
            ),
            _ => None,
        };

        Ok(Self {
            request_id: Uuid::new_v4().to_string(),
            amount,
            currency,
            merchant_order_id: item.router_data.connector_request_reference_id.clone(),
            referrer_data,
            order,
        })
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AirwallexOrderResponse {
    pub status: AirwallexPaymentStatus,
    pub id: String,
    pub payment_consent_id: Option<Secret<String>>,
    pub next_action: Option<AirwallexPaymentsNextAction>,
}

impl TryFrom<CreateOrderResponseRouterData<AirwallexOrderResponse>>
    for types::CreateOrderRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: CreateOrderResponseRouterData<AirwallexOrderResponse>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            response: Ok(PaymentsResponseData::PaymentsCreateOrderResponse {
                order_id: item.response.id.clone(),
            }),
            ..item.data
        })
    }
}

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

impl<T> TryFrom<(StringMajorUnit, T)> for AirwallexRouterData<T> {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from((amount, router_data): (StringMajorUnit, T)) -> Result<Self, Self::Error> {
        Ok(Self {
            amount,
            router_data,
        })
    }
}

#[derive(Debug, Serialize)]
pub struct AirwallexPaymentsRequest {
    // Unique ID to be sent for each transaction/operation request to the connector
    request_id: String,
    payment_method: AirwallexPaymentMethod,
    payment_method_options: Option<AirwallexPaymentOptions>,
    return_url: Option<String>,
    device_data: DeviceData,
}

#[derive(Debug, Serialize, Eq, PartialEq, Default)]
pub struct AirwallexOrderData {
    products: Vec<AirwallexProductData>,
    shipping: Option<AirwallexShippingData>,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexProductData {
    name: String,
    quantity: u16,
    unit_price: MinorUnit,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexShippingData {
    first_name: Option<Secret<String>>,
    last_name: Option<Secret<String>>,
    phone_number: Option<Secret<String>>,
    address: AirwallexPLShippingAddress,
}

#[derive(Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexPLShippingAddress {
    country_code: Option<enums::CountryAlpha2>,
    city: Option<String>,
    street: Option<Secret<String>>,
    postcode: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
pub struct DeviceData {
    accept_header: String,
    browser: Browser,
    ip_address: Secret<String, IpAddress>,
    language: String,
    mobile: Option<Mobile>,
    screen_color_depth: u8,
    screen_height: u32,
    screen_width: u32,
    timezone: String,
}

#[derive(Debug, Serialize)]
pub struct Browser {
    java_enabled: bool,
    javascript_enabled: bool,
    user_agent: String,
}

#[derive(Debug, Serialize)]
pub struct Mobile {
    device_model: Option<String>,
    os_type: Option<String>,
    os_version: Option<String>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum AirwallexPaymentMethod {
    Card(AirwallexCard),
    Wallets(AirwallexWalletData),
    PayLater(AirwallexPayLaterData),
    BankRedirect(AirwallexBankRedirectData),
    BankTransfer(AirwallexBankTransferData),
}

#[derive(Debug, Serialize)]
pub struct AirwallexCard {
    card: AirwallexCardDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}
#[derive(Debug, Serialize)]
pub struct AirwallexCardDetails {
    expiry_month: Secret<String>,
    expiry_year: Secret<String>,
    number: cards::CardNumber,
    cvc: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum AirwallexWalletData {
    GooglePay(GooglePayData),
    Paypal(PaypalData),
    Skrill(SkrillData),
}

#[derive(Debug, Serialize)]
pub struct GooglePayData {
    googlepay: GooglePayDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}

#[derive(Debug, Serialize)]
pub struct PaypalData {
    paypal: PaypalDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}

#[derive(Debug, Serialize)]
pub struct SkrillData {
    skrill: SkrillDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}

#[derive(Debug, Serialize)]
pub struct GooglePayDetails {
    encrypted_payment_token: Secret<String>,
    payment_data_type: GpayPaymentDataType,
}

#[derive(Debug, Serialize)]
pub struct PaypalDetails {
    shopper_name: Secret<String>,
    country_code: enums::CountryAlpha2,
}

#[derive(Debug, Serialize)]
pub struct SkrillDetails {
    shopper_name: Secret<String>,
    shopper_email: Email,
    country_code: enums::CountryAlpha2,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum AirwallexPayLaterData {
    Klarna(Box<KlarnaData>),
    Atome(AtomeData),
}

#[derive(Debug, Serialize)]
pub struct KlarnaData {
    klarna: KlarnaDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}

#[derive(Debug, Serialize)]
pub struct KlarnaDetails {
    country_code: enums::CountryAlpha2,
    billing: Option<Billing>,
}

#[derive(Debug, Serialize)]
pub struct Billing {
    date_of_birth: Option<Secret<String>>,
    email: Option<Email>,
    first_name: Option<Secret<String>>,
    last_name: Option<Secret<String>>,
    phone_number: Option<Secret<String>>,
    address: Option<AddressAirwallex>,
}

#[derive(Debug, Serialize)]
pub struct AddressAirwallex {
    country_code: Option<enums::CountryAlpha2>,
    city: Option<String>,
    street: Option<Secret<String>>,
    postcode: Option<Secret<String>>,
}

#[derive(Debug, Serialize)]
pub struct AtomeData {
    atome: AtomeDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}

#[derive(Debug, Serialize)]
pub struct AtomeDetails {
    shopper_phone: Secret<String>,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum AirwallexBankTransferData {
    IndonesianBankTransfer(IndonesianBankTransferData),
}

#[derive(Debug, Serialize)]
pub struct IndonesianBankTransferData {
    bank_transfer: IndonesianBankTransferDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}

#[derive(Debug, Serialize)]
pub struct IndonesianBankTransferDetails {
    shopper_name: Secret<String>,
    shopper_email: Email,
    bank_name: common_enums::BankNames,
    country_code: enums::CountryAlpha2,
}

#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum AirwallexBankRedirectData {
    Trustly(TrustlyData),
    Blik(BlikData),
    Ideal(IdealData),
}

#[derive(Debug, Serialize)]
pub struct TrustlyData {
    trustly: TrustlyDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}

#[derive(Debug, Serialize)]
pub struct TrustlyDetails {
    shopper_name: Secret<String>,
    country_code: enums::CountryAlpha2,
}

#[derive(Debug, Serialize)]
pub struct BlikData {
    blik: BlikDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}

#[derive(Debug, Serialize)]
pub struct BlikDetails {
    shopper_name: Secret<String>,
}

#[derive(Debug, Serialize)]
pub struct IdealData {
    ideal: IdealDetails,
    #[serde(rename = "type")]
    payment_method_type: AirwallexPaymentType,
}

#[derive(Debug, Serialize)]
pub struct IdealDetails {
    bank_name: Option<common_enums::BankNames>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AirwallexPaymentType {
    Card,
    Googlepay,
    Paypal,
    Klarna,
    Atome,
    Trustly,
    Blik,
    Ideal,
    Skrill,
    BankTransfer,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GpayPaymentDataType {
    EncryptedPaymentToken,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AirwallexPaymentOptions {
    Card(AirwallexCardPaymentOptions),
    Klarna(AirwallexPayLaterPaymentOptions),
    Atome(AirwallexPayLaterPaymentOptions),
}

#[derive(Debug, Serialize)]
pub struct AirwallexCardPaymentOptions {
    auto_capture: bool,
}

#[derive(Debug, Serialize)]
pub struct AirwallexPayLaterPaymentOptions {
    auto_capture: bool,
}

impl TryFrom<&AirwallexRouterData<&types::PaymentsAuthorizeRouterData>>
    for AirwallexPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &AirwallexRouterData<&types::PaymentsAuthorizeRouterData>,
    ) -> Result<Self, Self::Error> {
        let mut payment_method_options = None;
        let request = &item.router_data.request;
        let payment_method = match request.payment_method_data.clone() {
            PaymentMethodData::Card(ccard) => {
                payment_method_options =
                    Some(AirwallexPaymentOptions::Card(AirwallexCardPaymentOptions {
                        auto_capture: matches!(
                            request.capture_method,
                            Some(enums::CaptureMethod::Automatic)
                                | Some(enums::CaptureMethod::SequentialAutomatic)
                                | None
                        ),
                    }));
                Ok(AirwallexPaymentMethod::Card(AirwallexCard {
                    card: AirwallexCardDetails {
                        number: ccard.card_number.clone(),
                        expiry_month: ccard.card_exp_month.clone(),
                        expiry_year: ccard.get_expiry_year_4_digit(),
                        cvc: ccard.card_cvc,
                    },
                    payment_method_type: AirwallexPaymentType::Card,
                }))
            }
            PaymentMethodData::Wallet(ref wallet_data) => get_wallet_details(wallet_data, item),
            PaymentMethodData::PayLater(ref paylater_data) => {
                let paylater_options = AirwallexPayLaterPaymentOptions {
                    auto_capture: item.router_data.request.is_auto_capture()?,
                };

                payment_method_options = match paylater_data {
                    PayLaterData::KlarnaRedirect { .. } => {
                        Some(AirwallexPaymentOptions::Klarna(paylater_options))
                    }
                    PayLaterData::AtomeRedirect { .. } => {
                        Some(AirwallexPaymentOptions::Atome(paylater_options))
                    }
                    _ => None,
                };

                get_paylater_details(paylater_data, item)
            }
            PaymentMethodData::BankTransfer(ref banktransfer_data) => {
                get_banktransfer_details(banktransfer_data, item)
            }
            PaymentMethodData::BankRedirect(ref bankredirect_data) => {
                get_bankredirect_details(bankredirect_data, item)
            }
            PaymentMethodData::BankDebit(_)
            | PaymentMethodData::CardRedirect(_)
            | PaymentMethodData::Crypto(_)
            | PaymentMethodData::MandatePayment
            | PaymentMethodData::Reward
            | PaymentMethodData::RealTimePayment(_)
            | PaymentMethodData::MobilePayment(_)
            | PaymentMethodData::Upi(_)
            | PaymentMethodData::Voucher(_)
            | PaymentMethodData::GiftCard(_)
            | PaymentMethodData::OpenBanking(_)
            | PaymentMethodData::CardToken(_)
            | PaymentMethodData::NetworkToken(_)
            | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    utils::get_unimplemented_payment_method_error_message("airwallex"),
                ))
            }
        }?;
        let device_data = get_device_data(item.router_data)?;

        let return_url = match &request.payment_method_data {
            PaymentMethodData::Wallet(wallet_data) => match wallet_data {
                WalletData::PaypalRedirect(_paypal_details) => {
                    item.router_data.request.router_return_url.clone()
                }
                WalletData::Skrill(_) => item.router_data.request.router_return_url.clone(),
                _ => request.complete_authorize_url.clone(),
            },
            PaymentMethodData::BankRedirect(_bankredirect_data) => {
                item.router_data.request.router_return_url.clone()
            }
            PaymentMethodData::PayLater(_paylater_data) => {
                item.router_data.request.router_return_url.clone()
            }
            _ => request.complete_authorize_url.clone(),
        };

        Ok(Self {
            request_id: Uuid::new_v4().to_string(),
            payment_method,
            payment_method_options,
            return_url,
            device_data,
        })
    }
}

fn get_device_data(
    item: &types::PaymentsAuthorizeRouterData,
) -> Result<DeviceData, error_stack::Report<errors::ConnectorError>> {
    let info = item.request.get_browser_info()?;
    let browser = Browser {
        java_enabled: info.get_java_enabled()?,
        javascript_enabled: info.get_java_script_enabled()?,
        user_agent: info.get_user_agent()?,
    };
    let mobile = {
        let device_model = info.get_device_model().ok();
        let os_type = info.get_os_type().ok();
        let os_version = info.get_os_version().ok();

        if device_model.is_some() || os_type.is_some() || os_version.is_some() {
            Some(Mobile {
                device_model,
                os_type,
                os_version,
            })
        } else {
            None
        }
    };
    Ok(DeviceData {
        accept_header: info.get_accept_header()?,
        browser,
        ip_address: info.get_ip_address()?,
        mobile,
        screen_color_depth: info.get_color_depth()?,
        screen_height: info.get_screen_height()?,
        screen_width: info.get_screen_width()?,
        timezone: info.get_time_zone()?.to_string(),
        language: info.get_language()?,
    })
}

fn get_banktransfer_details(
    banktransfer_data: &BankTransferData,
    item: &AirwallexRouterData<&types::PaymentsAuthorizeRouterData>,
) -> Result<AirwallexPaymentMethod, errors::ConnectorError> {
    let _bank_transfer_details = match banktransfer_data {
        BankTransferData::IndonesianBankTransfer { bank_name } => {
            AirwallexPaymentMethod::BankTransfer(AirwallexBankTransferData::IndonesianBankTransfer(
                IndonesianBankTransferData {
                    bank_transfer: IndonesianBankTransferDetails {
                        shopper_name: item.router_data.get_billing_full_name().map_err(|_| {
                            errors::ConnectorError::MissingRequiredField {
                                field_name: "shopper_name",
                            }
                        })?,
                        shopper_email: item.router_data.get_billing_email().map_err(|_| {
                            errors::ConnectorError::MissingRequiredField {
                                field_name: "shopper_email",
                            }
                        })?,
                        bank_name: bank_name.ok_or(
                            errors::ConnectorError::MissingRequiredField {
                                field_name: "bank_name",
                            },
                        )?,
                        country_code: item.router_data.get_billing_country().map_err(|_| {
                            errors::ConnectorError::MissingRequiredField {
                                field_name: "country_code",
                            }
                        })?,
                    },
                    payment_method_type: AirwallexPaymentType::BankTransfer,
                },
            ))
        }
        _ => Err(errors::ConnectorError::NotImplemented(
            utils::get_unimplemented_payment_method_error_message("airwallex"),
        ))?,
    };
    let not_implemented = Err(errors::ConnectorError::NotImplemented(
        utils::get_unimplemented_payment_method_error_message("airwallex"),
    ))?;
    Ok(not_implemented)
}

fn get_paylater_details(
    paylater_data: &PayLaterData,
    item: &AirwallexRouterData<&types::PaymentsAuthorizeRouterData>,
) -> Result<AirwallexPaymentMethod, errors::ConnectorError> {
    let paylater_details = match paylater_data {
        PayLaterData::KlarnaRedirect {} => {
            AirwallexPaymentMethod::PayLater(AirwallexPayLaterData::Klarna(Box::new(KlarnaData {
                klarna: KlarnaDetails {
                    country_code: item.router_data.get_billing_country().map_err(|_| {
                        errors::ConnectorError::MissingRequiredField {
                            field_name: "country_code",
                        }
                    })?,
                    billing: Some(Billing {
                        date_of_birth: None,
                        first_name: item.router_data.get_optional_billing_first_name(),
                        last_name: item.router_data.get_optional_billing_last_name(),
                        email: item.router_data.get_optional_billing_email(),
                        phone_number: item.router_data.get_optional_billing_phone_number(),
                        address: Some(AddressAirwallex {
                            country_code: item.router_data.get_optional_billing_country(),
                            city: item.router_data.get_optional_billing_city(),
                            street: item.router_data.get_optional_billing_line1(),
                            postcode: item.router_data.get_optional_billing_zip(),
                        }),
                    }),
                },
                payment_method_type: AirwallexPaymentType::Klarna,
            })))
        }
        PayLaterData::AtomeRedirect {} => {
            AirwallexPaymentMethod::PayLater(AirwallexPayLaterData::Atome(AtomeData {
                atome: AtomeDetails {
                    shopper_phone: item
                        .router_data
                        .get_billing_phone()
                        .map_err(|_| errors::ConnectorError::MissingRequiredField {
                            field_name: "shopper_phone",
                        })?
                        .get_number_with_country_code()
                        .map_err(|_| errors::ConnectorError::MissingRequiredField {
                            field_name: "country_code",
                        })?,
                },
                payment_method_type: AirwallexPaymentType::Atome,
            }))
        }
        _ => Err(errors::ConnectorError::NotImplemented(
            utils::get_unimplemented_payment_method_error_message("airwallex"),
        ))?,
    };
    Ok(paylater_details)
}

fn get_bankredirect_details(
    bankredirect_data: &BankRedirectData,
    item: &AirwallexRouterData<&types::PaymentsAuthorizeRouterData>,
) -> Result<AirwallexPaymentMethod, errors::ConnectorError> {
    let bank_redirect_details = match bankredirect_data {
        BankRedirectData::Trustly { .. } => {
            AirwallexPaymentMethod::BankRedirect(AirwallexBankRedirectData::Trustly(TrustlyData {
                trustly: TrustlyDetails {
                    shopper_name: item.router_data.get_billing_full_name().map_err(|_| {
                        errors::ConnectorError::MissingRequiredField {
                            field_name: "shopper_name",
                        }
                    })?,
                    country_code: item.router_data.get_billing_country().map_err(|_| {
                        errors::ConnectorError::MissingRequiredField {
                            field_name: "country_code",
                        }
                    })?,
                },
                payment_method_type: AirwallexPaymentType::Trustly,
            }))
        }
        BankRedirectData::Blik { .. } => {
            AirwallexPaymentMethod::BankRedirect(AirwallexBankRedirectData::Blik(BlikData {
                blik: BlikDetails {
                    shopper_name: item.router_data.get_billing_full_name().map_err(|_| {
                        errors::ConnectorError::MissingRequiredField {
                            field_name: "shopper_name",
                        }
                    })?,
                },
                payment_method_type: AirwallexPaymentType::Blik,
            }))
        }
        BankRedirectData::Ideal { .. } => {
            AirwallexPaymentMethod::BankRedirect(AirwallexBankRedirectData::Ideal(IdealData {
                ideal: IdealDetails { bank_name: None },
                payment_method_type: AirwallexPaymentType::Ideal,
            }))
        }
        _ => Err(errors::ConnectorError::NotImplemented(
            utils::get_unimplemented_payment_method_error_message("airwallex"),
        ))?,
    };
    Ok(bank_redirect_details)
}

fn get_wallet_details(
    wallet_data: &WalletData,
    item: &AirwallexRouterData<&types::PaymentsAuthorizeRouterData>,
) -> Result<AirwallexPaymentMethod, errors::ConnectorError> {
    let wallet_details: AirwallexPaymentMethod = match wallet_data {
        WalletData::GooglePay(gpay_details) => {
            let token = gpay_details
                .tokenization_data
                .get_encrypted_google_pay_token()
                .attach_printable("Failed to get gpay wallet token")
                .map_err(|_| errors::ConnectorError::MissingRequiredField {
                    field_name: "gpay wallet_token",
                })?;
            AirwallexPaymentMethod::Wallets(AirwallexWalletData::GooglePay(GooglePayData {
                googlepay: GooglePayDetails {
                    encrypted_payment_token: Secret::new(token.clone()),
                    payment_data_type: GpayPaymentDataType::EncryptedPaymentToken,
                },
                payment_method_type: AirwallexPaymentType::Googlepay,
            }))
        }
        WalletData::PaypalRedirect(_paypal_details) => {
            AirwallexPaymentMethod::Wallets(AirwallexWalletData::Paypal(PaypalData {
                paypal: PaypalDetails {
                    shopper_name: item
                        .router_data
                        .request
                        .customer_name
                        .as_ref()
                        .cloned()
                        .or_else(|| item.router_data.get_billing_full_name().ok())
                        .ok_or(errors::ConnectorError::MissingRequiredField {
                            field_name: "shopper_name",
                        })?,
                    country_code: item.router_data.get_billing_country().map_err(|_| {
                        errors::ConnectorError::MissingRequiredField {
                            field_name: "country_code",
                        }
                    })?,
                },
                payment_method_type: AirwallexPaymentType::Paypal,
            }))
        }
        WalletData::Skrill(_skrill_details) => {
            AirwallexPaymentMethod::Wallets(AirwallexWalletData::Skrill(SkrillData {
                skrill: SkrillDetails {
                    shopper_name: item
                        .router_data
                        .request
                        .customer_name
                        .as_ref()
                        .cloned()
                        .or_else(|| item.router_data.get_billing_full_name().ok())
                        .ok_or(errors::ConnectorError::MissingRequiredField {
                            field_name: "shopper_name",
                        })?,
                    shopper_email: item.router_data.get_billing_email().map_err(|_| {
                        errors::ConnectorError::MissingRequiredField {
                            field_name: "shopper_email",
                        }
                    })?,
                    country_code: item.router_data.get_billing_country().map_err(|_| {
                        errors::ConnectorError::MissingRequiredField {
                            field_name: "country_code",
                        }
                    })?,
                },
                payment_method_type: AirwallexPaymentType::Skrill,
            }))
        }
        WalletData::AliPayQr(_)
        | WalletData::AliPayRedirect(_)
        | WalletData::AliPayHkRedirect(_)
        | WalletData::AmazonPayRedirect(_)
        | WalletData::Paysera(_)
        | WalletData::MomoRedirect(_)
        | WalletData::KakaoPayRedirect(_)
        | WalletData::GoPayRedirect(_)
        | WalletData::GcashRedirect(_)
        | WalletData::AmazonPay(_)
        | WalletData::ApplePay(_)
        | WalletData::BluecodeRedirect {}
        | WalletData::ApplePayRedirect(_)
        | WalletData::ApplePayThirdPartySdk(_)
        | WalletData::DanaRedirect {}
        | WalletData::GooglePayRedirect(_)
        | WalletData::GooglePayThirdPartySdk(_)
        | WalletData::MbWayRedirect(_)
        | WalletData::MobilePayRedirect(_)
        | WalletData::PaypalSdk(_)
        | WalletData::Paze(_)
        | WalletData::SamsungPay(_)
        | WalletData::TwintRedirect {}
        | WalletData::VippsRedirect {}
        | WalletData::TouchNGoRedirect(_)
        | WalletData::WeChatPayRedirect(_)
        | WalletData::WeChatPayQr(_)
        | WalletData::CashappQr(_)
        | WalletData::SwishQr(_)
        | WalletData::Mifinity(_)
        | WalletData::RevolutPay(_) => Err(errors::ConnectorError::NotImplemented(
            utils::get_unimplemented_payment_method_error_message("airwallex"),
        ))?,
    };
    Ok(wallet_details)
}

#[derive(Deserialize, Debug, Serialize)]
pub struct AirwallexAuthUpdateResponse {
    #[serde(with = "common_utils::custom_serde::iso8601")]
    expires_at: PrimitiveDateTime,
    token: Secret<String>,
}

impl<F, T> TryFrom<ResponseRouterData<F, AirwallexAuthUpdateResponse, T, AccessToken>>
    for RouterData<F, T, AccessToken>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, AirwallexAuthUpdateResponse, T, AccessToken>,
    ) -> Result<Self, Self::Error> {
        let expires = (item.response.expires_at - common_utils::date_time::now()).whole_seconds();
        Ok(Self {
            response: Ok(AccessToken {
                token: item.response.token,
                expires,
            }),
            ..item.data
        })
    }
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexCompleteRequest {
    request_id: String,
    three_ds: AirwallexThreeDsData,
    #[serde(rename = "type")]
    three_ds_type: AirwallexThreeDsType,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexThreeDsData {
    acs_response: Option<Secret<String>>,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub enum AirwallexThreeDsType {
    #[default]
    #[serde(rename = "3ds_continue")]
    ThreeDSContinue,
}

impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for AirwallexCompleteRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            request_id: Uuid::new_v4().to_string(),
            three_ds: AirwallexThreeDsData {
                acs_response: item
                    .request
                    .redirect_response
                    .as_ref()
                    .map(|f| f.payload.to_owned())
                    .ok_or(errors::ConnectorError::MissingRequiredField {
                        field_name: "redirect_response.payload",
                    })?
                    .as_ref()
                    .map(|data| serde_json::to_string(data.peek()))
                    .transpose()
                    .change_context(errors::ConnectorError::ResponseDeserializationFailed)?
                    .map(Secret::new),
            },
            three_ds_type: AirwallexThreeDsType::ThreeDSContinue,
        })
    }
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexPaymentsCaptureRequest {
    // Unique ID to be sent for each transaction/operation request to the connector
    request_id: String,
    amount: Option<String>,
}

impl TryFrom<&types::PaymentsCaptureRouterData> for AirwallexPaymentsCaptureRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            request_id: Uuid::new_v4().to_string(),
            amount: Some(utils::to_currency_base_unit(
                item.request.amount_to_capture,
                item.request.currency,
            )?),
        })
    }
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexPaymentsCancelRequest {
    // Unique ID to be sent for each transaction/operation request to the connector
    request_id: String,
    cancellation_reason: Option<String>,
}

impl TryFrom<&types::PaymentsCancelRouterData> for AirwallexPaymentsCancelRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        Ok(Self {
            request_id: Uuid::new_v4().to_string(),
            cancellation_reason: item.request.cancellation_reason.clone(),
        })
    }
}

// PaymentsResponse
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AirwallexPaymentStatus {
    Succeeded,
    Failed,
    #[default]
    Pending,
    RequiresPaymentMethod,
    RequiresCustomerAction,
    RequiresCapture,
    Cancelled,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AirwallexNextAction {
    Payments(AirwallexPaymentsNextAction),
    Redirect(AirwallexRedirectNextAction),
}

fn get_payment_status(
    status: &AirwallexPaymentStatus,
    next_action: &Option<AirwallexNextAction>,
) -> enums::AttemptStatus {
    match status.clone() {
        AirwallexPaymentStatus::Succeeded => enums::AttemptStatus::Charged,
        AirwallexPaymentStatus::Failed => enums::AttemptStatus::Failure,
        AirwallexPaymentStatus::Pending => enums::AttemptStatus::Pending,
        AirwallexPaymentStatus::RequiresPaymentMethod => enums::AttemptStatus::PaymentMethodAwaited,
        AirwallexPaymentStatus::RequiresCustomerAction => next_action.as_ref().map_or(
            enums::AttemptStatus::AuthenticationPending,
            |next_action| match next_action {
                AirwallexNextAction::Payments(payments_next_action) => {
                    match payments_next_action.stage {
                        AirwallexNextActionStage::WaitingDeviceDataCollection => {
                            enums::AttemptStatus::DeviceDataCollectionPending
                        }
                        AirwallexNextActionStage::WaitingUserInfoInput => {
                            enums::AttemptStatus::AuthenticationPending
                        }
                    }
                }
                AirwallexNextAction::Redirect(_) => enums::AttemptStatus::AuthenticationPending,
            },
        ),
        AirwallexPaymentStatus::RequiresCapture => enums::AttemptStatus::Authorized,
        AirwallexPaymentStatus::Cancelled => enums::AttemptStatus::Voided,
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AirwallexNextActionStage {
    WaitingDeviceDataCollection,
    WaitingUserInfoInput,
}

#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct AirwallexRedirectFormData {
    #[serde(rename = "JWT")]
    jwt: Option<Secret<String>>,
    #[serde(rename = "threeDSMethodData")]
    three_ds_method_data: Option<Secret<String>>,
    token: Option<Secret<String>>,
    provider: Option<String>,
    version: Option<String>,
}

#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct AirwallexPaymentsNextAction {
    url: Url,
    method: Method,
    data: AirwallexRedirectFormData,
    stage: AirwallexNextActionStage,
}

#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct AirwallexRedirectNextAction {
    url: Url,
    method: Method,
}

#[derive(Default, Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct AirwallexPaymentsResponse {
    status: AirwallexPaymentStatus,
    //Unique identifier for the PaymentIntent
    id: String,
    amount: Option<f32>,
    //ID of the PaymentConsent related to this PaymentIntent
    payment_consent_id: Option<Secret<String>>,
    next_action: Option<AirwallexPaymentsNextAction>,
}

#[derive(Default, Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct AirwallexRedirectResponse {
    status: AirwallexPaymentStatus,
    id: String,
    amount: Option<f32>,
    payment_consent_id: Option<Secret<String>>,
    next_action: Option<AirwallexRedirectNextAction>,
}

#[derive(Default, Debug, Clone, Deserialize, PartialEq, Serialize)]
pub struct AirwallexPaymentsSyncResponse {
    status: AirwallexPaymentStatus,
    //Unique identifier for the PaymentIntent
    id: String,
    amount: Option<f32>,
    //ID of the PaymentConsent related to this PaymentIntent
    payment_consent_id: Option<Secret<String>>,
    next_action: Option<AirwallexPaymentsNextAction>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum AirwallexAuthorizeResponse {
    AirwallexPaymentsResponse(AirwallexPaymentsResponse),
    AirwallexRedirectResponse(AirwallexRedirectResponse),
}

fn get_redirection_form(response_url_data: AirwallexPaymentsNextAction) -> Option<RedirectForm> {
    Some(RedirectForm::Form {
        endpoint: response_url_data.url.to_string(),
        method: response_url_data.method,
        form_fields: std::collections::HashMap::from([
            //Some form fields might be empty based on the authentication type by the connector
            (
                "JWT".to_string(),
                response_url_data
                    .data
                    .jwt
                    .map(|jwt| jwt.expose())
                    .unwrap_or_default(),
            ),
            (
                "threeDSMethodData".to_string(),
                response_url_data
                    .data
                    .three_ds_method_data
                    .map(|three_ds_method_data| three_ds_method_data.expose())
                    .unwrap_or_default(),
            ),
            (
                "token".to_string(),
                response_url_data
                    .data
                    .token
                    .map(|token: Secret<String>| token.expose())
                    .unwrap_or_default(),
            ),
            (
                "provider".to_string(),
                response_url_data.data.provider.unwrap_or_default(),
            ),
            (
                "version".to_string(),
                response_url_data.data.version.unwrap_or_default(),
            ),
        ]),
    })
}

impl<F, T>
    ForeignTryFrom<ResponseRouterData<F, AirwallexAuthorizeResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn foreign_try_from(
        item: ResponseRouterData<F, AirwallexAuthorizeResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let ResponseRouterData {
            response,
            data,
            http_code,
        } = item;

        match response {
            AirwallexAuthorizeResponse::AirwallexPaymentsResponse(res) => {
                Self::try_from(ResponseRouterData::<
                    F,
                    AirwallexPaymentsResponse,
                    T,
                    PaymentsResponseData,
                > {
                    response: res,
                    data,
                    http_code,
                })
            }
            AirwallexAuthorizeResponse::AirwallexRedirectResponse(res) => {
                Self::try_from(ResponseRouterData::<
                    F,
                    AirwallexRedirectResponse,
                    T,
                    PaymentsResponseData,
                > {
                    response: res,
                    data,
                    http_code,
                })
            }
        }
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, AirwallexPaymentsResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, AirwallexPaymentsResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let (status, redirection_data) = item.response.next_action.clone().map_or(
            // If no next action is there, map the status and set redirection form as None
            (
                get_payment_status(
                    &item.response.status,
                    &item
                        .response
                        .next_action
                        .clone()
                        .map(AirwallexNextAction::Payments)
                        .clone(),
                ),
                None,
            ),
            |response_url_data| {
                // If the connector sends a customer action response that is already under
                // process from our end it can cause an infinite loop to break this this check
                // is added and fail the payment
                if matches!(
                    (
                        response_url_data.stage.clone(),
                        item.data.status,
                        item.response.status.clone(),
                    ),
                    // If the connector sends waiting for DDC and our status is already DDC Pending
                    // that means we initiated the call to collect the data and now we expect a different response
                    (
                            AirwallexNextActionStage::WaitingDeviceDataCollection,
                            enums::AttemptStatus::DeviceDataCollectionPending,
                            _
                        )
                        // If the connector sends waiting for Customer Action and our status is already Authenticaition Pending
                        // that means we initiated the call to authenticate and now we do not expect a requires_customer action
                        // it will start a loop
                        | (
                            _,
                            enums::AttemptStatus::AuthenticationPending,
                            AirwallexPaymentStatus::RequiresCustomerAction,
                        )
                ) {
                    // Fail the payment for above conditions
                    (enums::AttemptStatus::AuthenticationFailed, None)
                } else {
                    (
                        //Build the redirect form and update the payment status
                        get_payment_status(
                            &item.response.status,
                            &item
                                .response
                                .next_action
                                .map(AirwallexNextAction::Payments)
                                .clone(),
                        ),
                        get_redirection_form(response_url_data),
                    )
                }
            },
        );

        Ok(Self {
            status,
            reference_id: Some(item.response.id.clone()),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.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: Some(item.response.id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl<F, T> TryFrom<ResponseRouterData<F, AirwallexRedirectResponse, T, PaymentsResponseData>>
    for RouterData<F, T, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<F, AirwallexRedirectResponse, T, PaymentsResponseData>,
    ) -> Result<Self, Self::Error> {
        let (status, redirection_data) = item.response.next_action.clone().map_or(
            (
                get_payment_status(
                    &item.response.status,
                    &item
                        .response
                        .next_action
                        .clone()
                        .map(AirwallexNextAction::Redirect)
                        .clone(),
                ),
                None,
            ),
            |response_url_data| {
                let redirection_data =
                    Some(RedirectForm::from((response_url_data.url, Method::Get)));
                (
                    get_payment_status(
                        &item.response.status,
                        &item
                            .response
                            .next_action
                            .map(AirwallexNextAction::Redirect)
                            .clone(),
                    ),
                    redirection_data,
                )
            },
        );

        Ok(Self {
            status,
            reference_id: Some(item.response.id.clone()),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.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: Some(item.response.id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}

impl
    TryFrom<
        ResponseRouterData<
            PSync,
            AirwallexPaymentsSyncResponse,
            PaymentsSyncData,
            PaymentsResponseData,
        >,
    > for types::PaymentsSyncRouterData
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            PSync,
            AirwallexPaymentsSyncResponse,
            PaymentsSyncData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let status = get_payment_status(
            &item.response.status,
            &item
                .response
                .next_action
                .clone()
                .map(AirwallexNextAction::Payments)
                .clone(),
        );
        let redirection_data = if let Some(redirect_url_data) = item.response.next_action {
            get_redirection_form(redirect_url_data)
        } else {
            None
        };
        Ok(Self {
            status,
            reference_id: Some(item.response.id.clone()),
            response: Ok(PaymentsResponseData::TransactionResponse {
                resource_id: ResponseId::ConnectorTransactionId(item.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: Some(item.response.id),
                incremental_authorization_allowed: None,
                charges: None,
            }),
            ..item.data
        })
    }
}
// Type definition for RefundRequest
#[derive(Default, Debug, Serialize)]
pub struct AirwallexRefundRequest {
    // Unique ID to be sent for each transaction/operation request to the connector
    request_id: String,
    amount: Option<StringMajorUnit>,
    reason: Option<String>,
    //Identifier for the PaymentIntent for which Refund is requested
    payment_intent_id: String,
}

impl<F> TryFrom<&AirwallexRouterData<&types::RefundsRouterData<F>>> for AirwallexRefundRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: &AirwallexRouterData<&types::RefundsRouterData<F>>,
    ) -> Result<Self, Self::Error> {
        Ok(Self {
            request_id: Uuid::new_v4().to_string(),
            amount: Some(item.amount.to_owned()),
            reason: item.router_data.request.reason.clone(),
            payment_intent_id: item.router_data.request.connector_transaction_id.clone(),
        })
    }
}

// Type definition for Refund Response
#[allow(dead_code)]
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RefundStatus {
    Succeeded,
    Failed,
    #[default]
    Received,
    Accepted,
}

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

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct RefundResponse {
    //A unique number that tags a credit or debit card transaction when it goes from the merchant's bank through to the cardholder's bank.
    acquirer_reference_number: Option<String>,
    amount: f32,
    //Unique identifier for the Refund
    id: String,
    status: RefundStatus,
}

impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>>
    for types::RefundsRouterData<Execute>
{
    type Error = error_stack::Report<ParsingError>;
    fn try_from(
        item: RefundsResponseRouterData<Execute, 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
        })
    }
}

impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for types::RefundsRouterData<RSync> {
    type Error = error_stack::Report<ParsingError>;
    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, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AirwallexWebhookData {
    pub source_id: Option<String>,
    pub name: AirwallexWebhookEventType,
    pub data: AirwallexObjectData,
}

#[derive(Debug, Deserialize, strum::Display, PartialEq)]
pub enum AirwallexWebhookEventType {
    #[serde(rename = "payment_intent.created")]
    PaymentIntentCreated,
    #[serde(rename = "payment_intent.requires_payment_method")]
    PaymentIntentRequiresPaymentMethod,
    #[serde(rename = "payment_intent.cancelled")]
    PaymentIntentCancelled,
    #[serde(rename = "payment_intent.succeeded")]
    PaymentIntentSucceeded,
    #[serde(rename = "payment_intent.requires_capture")]
    PaymentIntentRequiresCapture,
    #[serde(rename = "payment_intent.requires_customer_action")]
    PaymentIntentRequiresCustomerAction,
    #[serde(rename = "payment_attempt.authorized")]
    PaymentAttemptAuthorized,
    #[serde(rename = "payment_attempt.authorization_failed")]
    PaymentAttemptAuthorizationFailed,
    #[serde(rename = "payment_attempt.capture_requested")]
    PaymentAttemptCaptureRequested,
    #[serde(rename = "payment_attempt.capture_failed")]
    PaymentAttemptCaptureFailed,
    #[serde(rename = "payment_attempt.authentication_redirected")]
    PaymentAttemptAuthenticationRedirected,
    #[serde(rename = "payment_attempt.authentication_failed")]
    PaymentAttemptAuthenticationFailed,
    #[serde(rename = "payment_attempt.failed_to_process")]
    PaymentAttemptFailedToProcess,
    #[serde(rename = "payment_attempt.cancelled")]
    PaymentAttemptCancelled,
    #[serde(rename = "payment_attempt.expired")]
    PaymentAttemptExpired,
    #[serde(rename = "payment_attempt.risk_declined")]
    PaymentAttemptRiskDeclined,
    #[serde(rename = "payment_attempt.settled")]
    PaymentAttemptSettled,
    #[serde(rename = "payment_attempt.paid")]
    PaymentAttemptPaid,
    #[serde(rename = "refund.received")]
    RefundReceived,
    #[serde(rename = "refund.accepted")]
    RefundAccepted,
    #[serde(rename = "refund.succeeded")]
    RefundSucceeded,
    #[serde(rename = "refund.failed")]
    RefundFailed,
    #[serde(rename = "dispute.rfi_responded_by_merchant")]
    DisputeRfiRespondedByMerchant,
    #[serde(rename = "dispute.dispute.pre_chargeback_accepted")]
    DisputePreChargebackAccepted,
    #[serde(rename = "dispute.accepted")]
    DisputeAccepted,
    #[serde(rename = "dispute.dispute_received_by_merchant")]
    DisputeReceivedByMerchant,
    #[serde(rename = "dispute.dispute_responded_by_merchant")]
    DisputeRespondedByMerchant,
    #[serde(rename = "dispute.won")]
    DisputeWon,
    #[serde(rename = "dispute.lost")]
    DisputeLost,
    #[serde(rename = "dispute.dispute_reversed")]
    DisputeReversed,
    #[serde(other)]
    Unknown,
}

pub fn is_transaction_event(event_code: &AirwallexWebhookEventType) -> bool {
    matches!(
        event_code,
        AirwallexWebhookEventType::PaymentAttemptFailedToProcess
            | AirwallexWebhookEventType::PaymentAttemptAuthorized
    )
}

pub fn is_refund_event(event_code: &AirwallexWebhookEventType) -> bool {
    matches!(
        event_code,
        AirwallexWebhookEventType::RefundSucceeded | AirwallexWebhookEventType::RefundFailed
    )
}

pub fn is_dispute_event(event_code: &AirwallexWebhookEventType) -> bool {
    matches!(
        event_code,
        AirwallexWebhookEventType::DisputeAccepted
            | AirwallexWebhookEventType::DisputePreChargebackAccepted
            | AirwallexWebhookEventType::DisputeRespondedByMerchant
            | AirwallexWebhookEventType::DisputeWon
            | AirwallexWebhookEventType::DisputeLost
    )
}

#[derive(Debug, Deserialize)]
pub struct AirwallexObjectData {
    pub object: serde_json::Value,
}

#[derive(Debug, Deserialize)]
pub struct AirwallexDisputeObject {
    pub payment_intent_id: String,
    pub dispute_amount: MinorUnit,
    pub dispute_currency: enums::Currency,
    pub stage: AirwallexDisputeStage,
    pub dispute_id: String,
    pub dispute_reason_type: Option<String>,
    pub dispute_original_reason_code: Option<String>,
    pub status: String,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub created_at: Option<PrimitiveDateTime>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub updated_at: Option<PrimitiveDateTime>,
}

#[derive(Debug, Deserialize, strum::Display, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AirwallexDisputeStage {
    Rfi,
    Dispute,
    Arbitration,
}

#[derive(Debug, Deserialize)]
pub struct AirwallexWebhookDataResource {
    // Should this be a secret by default since it represents webhook payload
    pub object: Secret<serde_json::Value>,
}

#[derive(Debug, Deserialize)]
pub struct AirwallexWebhookObjectResource {
    pub data: AirwallexWebhookDataResource,
}

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

impl TryFrom<AirwallexWebhookEventType> for api_models::webhooks::IncomingWebhookEvent {
    type Error = errors::ConnectorError;
    fn try_from(value: AirwallexWebhookEventType) -> Result<Self, Self::Error> {
        Ok(match value {
            AirwallexWebhookEventType::PaymentAttemptFailedToProcess => Self::PaymentIntentFailure,
            AirwallexWebhookEventType::PaymentAttemptAuthorized => Self::PaymentIntentSuccess,
            AirwallexWebhookEventType::RefundSucceeded => Self::RefundSuccess,
            AirwallexWebhookEventType::RefundFailed => Self::RefundFailure,
            AirwallexWebhookEventType::DisputeAccepted
            | AirwallexWebhookEventType::DisputePreChargebackAccepted => Self::DisputeAccepted,
            AirwallexWebhookEventType::DisputeRespondedByMerchant => Self::DisputeChallenged,
            AirwallexWebhookEventType::DisputeWon | AirwallexWebhookEventType::DisputeReversed => {
                Self::DisputeWon
            }
            AirwallexWebhookEventType::DisputeLost => Self::DisputeLost,
            AirwallexWebhookEventType::Unknown
            | AirwallexWebhookEventType::PaymentAttemptAuthenticationRedirected
            | AirwallexWebhookEventType::PaymentIntentCreated
            | AirwallexWebhookEventType::PaymentIntentRequiresPaymentMethod
            | AirwallexWebhookEventType::PaymentIntentCancelled
            | AirwallexWebhookEventType::PaymentIntentSucceeded
            | AirwallexWebhookEventType::PaymentIntentRequiresCapture
            | AirwallexWebhookEventType::PaymentIntentRequiresCustomerAction
            | AirwallexWebhookEventType::PaymentAttemptAuthorizationFailed
            | AirwallexWebhookEventType::PaymentAttemptCaptureRequested
            | AirwallexWebhookEventType::PaymentAttemptCaptureFailed
            | AirwallexWebhookEventType::PaymentAttemptAuthenticationFailed
            | AirwallexWebhookEventType::PaymentAttemptCancelled
            | AirwallexWebhookEventType::PaymentAttemptExpired
            | AirwallexWebhookEventType::PaymentAttemptRiskDeclined
            | AirwallexWebhookEventType::PaymentAttemptSettled
            | AirwallexWebhookEventType::PaymentAttemptPaid
            | AirwallexWebhookEventType::RefundReceived
            | AirwallexWebhookEventType::RefundAccepted
            | AirwallexWebhookEventType::DisputeRfiRespondedByMerchant
            | AirwallexWebhookEventType::DisputeReceivedByMerchant => Self::EventNotSupported,
        })
    }
}

impl From<AirwallexDisputeStage> for api_models::enums::DisputeStage {
    fn from(code: AirwallexDisputeStage) -> Self {
        match code {
            AirwallexDisputeStage::Rfi => Self::PreDispute,
            AirwallexDisputeStage::Dispute => Self::Dispute,
            AirwallexDisputeStage::Arbitration => Self::PreArbitration,
        }
    }
}