use std::{fmt::Debug, sync::Weak};

use hyperswitch_constraint_graph as cgraph;
use rustc_hash::{FxHashMap, FxHashSet};

use crate::{
    dssa::types,
    frontend::dir,
    types::{DataType, Metadata},
};

pub mod euclid_graph_prelude {
    pub use hyperswitch_constraint_graph as cgraph;
    pub use rustc_hash::{FxHashMap, FxHashSet};
    pub use strum::EnumIter;

    pub use crate::{
        dssa::graph::*,
        frontend::dir::{enums::*, DirKey, DirKeyKind, DirValue},
        types::*,
    };
}

impl cgraph::KeyNode for dir::DirKey {}

impl cgraph::NodeViz for dir::DirKey {
    fn viz(&self) -> String {
        self.kind.to_string()
    }
}

impl cgraph::ValueNode for dir::DirValue {
    type Key = dir::DirKey;

    fn get_key(&self) -> Self::Key {
        Self::get_key(self)
    }
}

impl cgraph::NodeViz for dir::DirValue {
    fn viz(&self) -> String {
        match self {
            Self::PaymentMethod(pm) => pm.to_string(),
            Self::CardBin(bin) => bin.value.clone(),
            Self::ExtendedCardBin(ebin) => ebin.value.clone(),
            Self::CardType(ct) => ct.to_string(),
            Self::CardNetwork(cn) => cn.to_string(),
            Self::PayLaterType(plt) => plt.to_string(),
            Self::WalletType(wt) => wt.to_string(),
            Self::UpiType(ut) => ut.to_string(),
            Self::BankTransferType(btt) => btt.to_string(),
            Self::BankRedirectType(brt) => brt.to_string(),
            Self::BankDebitType(bdt) => bdt.to_string(),
            Self::CryptoType(ct) => ct.to_string(),
            Self::RewardType(rt) => rt.to_string(),
            Self::PaymentAmount(amt) => amt.number.to_string(),
            Self::PaymentCurrency(curr) => curr.to_string(),
            Self::AuthenticationType(at) => at.to_string(),
            Self::CaptureMethod(cm) => cm.to_string(),
            Self::BusinessCountry(bc) => bc.to_string(),
            Self::BillingCountry(bc) => bc.to_string(),
            Self::Connector(conn) => conn.connector.to_string(),
            Self::MetaData(mv) => format!("[{} = {}]", mv.key, mv.value),
            Self::MandateAcceptanceType(mat) => mat.to_string(),
            Self::MandateType(mt) => mt.to_string(),
            Self::PaymentType(pt) => pt.to_string(),
            Self::VoucherType(vt) => vt.to_string(),
            Self::GiftCardType(gct) => gct.to_string(),
            Self::BusinessLabel(bl) => bl.value.to_string(),
            Self::SetupFutureUsage(sfu) => sfu.to_string(),
            Self::CardRedirectType(crt) => crt.to_string(),
            Self::RealTimePaymentType(rtpt) => rtpt.to_string(),
            Self::OpenBankingType(ob) => ob.to_string(),
            Self::MobilePaymentType(mpt) => mpt.to_string(),
            Self::IssuerName(issuer_name) => issuer_name.value.clone(),
            Self::IssuerCountry(issuer_country) => issuer_country.to_string(),
            Self::CustomerDevicePlatform(customer_device_platform) => {
                customer_device_platform.to_string()
            }
            Self::CustomerDeviceType(customer_device_type) => customer_device_type.to_string(),
            Self::CustomerDeviceDisplaySize(customer_device_display_size) => {
                customer_device_display_size.to_string()
            }
            Self::AcquirerCountry(acquirer_country) => acquirer_country.to_string(),
            Self::AcquirerFraudRate(acquirer_fraud_rate) => acquirer_fraud_rate.number.to_string(),
            Self::TransactionInitiator(transaction_initiator) => transaction_initiator.to_string(),
            Self::NetworkTokenType(ntt) => ntt.to_string(),
        }
    }
}

#[derive(Debug, Clone, serde::Serialize)]
#[serde(tag = "type", content = "details", rename_all = "snake_case")]
pub enum AnalysisError<V: cgraph::ValueNode> {
    Graph(cgraph::GraphError<V>),
    AssertionTrace {
        trace: Weak<cgraph::AnalysisTrace<V>>,
        metadata: Metadata,
    },
    NegationTrace {
        trace: Weak<cgraph::AnalysisTrace<V>>,
        metadata: Vec<Metadata>,
    },
}

impl<V: cgraph::ValueNode> AnalysisError<V> {
    fn assertion_from_graph_error(metadata: &Metadata, graph_error: cgraph::GraphError<V>) -> Self {
        match graph_error {
            cgraph::GraphError::AnalysisError(trace) => Self::AssertionTrace {
                trace,
                metadata: metadata.clone(),
            },

            other => Self::Graph(other),
        }
    }

    fn negation_from_graph_error(
        metadata: Vec<&Metadata>,
        graph_error: cgraph::GraphError<V>,
    ) -> Self {
        match graph_error {
            cgraph::GraphError::AnalysisError(trace) => Self::NegationTrace {
                trace,
                metadata: metadata.iter().map(|m| (*m).clone()).collect(),
            },

            other => Self::Graph(other),
        }
    }
}

#[derive(Debug)]
pub struct AnalysisContext {
    keywise_values: FxHashMap<dir::DirKey, FxHashSet<dir::DirValue>>,
}

impl AnalysisContext {
    pub fn from_dir_values(vals: impl IntoIterator<Item = dir::DirValue>) -> Self {
        let mut keywise_values: FxHashMap<dir::DirKey, FxHashSet<dir::DirValue>> =
            FxHashMap::default();

        for dir_val in vals {
            let key = dir_val.get_key();
            let set = keywise_values.entry(key).or_default();
            set.insert(dir_val);
        }

        Self { keywise_values }
    }

    pub fn insert(&mut self, value: dir::DirValue) {
        self.keywise_values
            .entry(value.get_key())
            .or_default()
            .insert(value);
    }

    pub fn remove(&mut self, value: dir::DirValue) {
        let set = self.keywise_values.entry(value.get_key()).or_default();

        set.remove(&value);

        if set.is_empty() {
            self.keywise_values.remove(&value.get_key());
        }
    }
}

impl cgraph::CheckingContext for AnalysisContext {
    type Value = dir::DirValue;

    fn from_node_values<L>(vals: impl IntoIterator<Item = L>) -> Self
    where
        L: Into<Self::Value>,
    {
        let mut keywise_values: FxHashMap<dir::DirKey, FxHashSet<dir::DirValue>> =
            FxHashMap::default();

        for dir_val in vals.into_iter().map(L::into) {
            let key = dir_val.get_key();
            let set = keywise_values.entry(key).or_default();
            set.insert(dir_val);
        }

        Self { keywise_values }
    }

    fn check_presence(
        &self,
        value: &cgraph::NodeValue<dir::DirValue>,
        strength: cgraph::Strength,
    ) -> bool {
        match value {
            cgraph::NodeValue::Key(k) => {
                self.keywise_values.contains_key(k) || matches!(strength, cgraph::Strength::Weak)
            }

            cgraph::NodeValue::Value(val) => {
                let key = val.get_key();
                let value_set = if let Some(set) = self.keywise_values.get(&key) {
                    set
                } else {
                    return matches!(strength, cgraph::Strength::Weak);
                };

                match key.kind.get_type() {
                    DataType::EnumVariant | DataType::StrValue | DataType::MetadataValue => {
                        value_set.contains(val)
                    }
                    DataType::Number => val.get_num_value().is_some_and(|num_val| {
                        value_set.iter().any(|ctx_val| {
                            ctx_val
                                .get_num_value()
                                .is_some_and(|ctx_num_val| num_val.fits(&ctx_num_val))
                        })
                    }),
                }
            }
        }
    }

    fn get_values_by_key(
        &self,
        key: &<Self::Value as cgraph::ValueNode>::Key,
    ) -> Option<Vec<Self::Value>> {
        self.keywise_values
            .get(key)
            .map(|set| set.iter().cloned().collect())
    }
}

pub trait CgraphExt {
    fn key_analysis(
        &self,
        key: dir::DirKey,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>>;

    fn value_analysis(
        &self,
        val: dir::DirValue,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>>;

    fn check_value_validity(
        &self,
        val: dir::DirValue,
        analysis_ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<bool, cgraph::GraphError<dir::DirValue>>;

    fn key_value_analysis(
        &self,
        val: dir::DirValue,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>>;

    fn assertion_analysis(
        &self,
        positive_ctx: &[(&dir::DirValue, &Metadata)],
        analysis_ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>>;

    fn negation_analysis(
        &self,
        negative_ctx: &[(&[dir::DirValue], &Metadata)],
        analysis_ctx: &mut AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>>;

    fn perform_context_analysis(
        &self,
        ctx: &types::ConjunctiveContext<'_>,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>>;
}

impl CgraphExt for cgraph::ConstraintGraph<dir::DirValue> {
    fn key_analysis(
        &self,
        key: dir::DirKey,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>> {
        self.value_map
            .get(&cgraph::NodeValue::Key(key))
            .map_or(Ok(()), |node_id| {
                self.check_node(
                    ctx,
                    *node_id,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Strong,
                    memo,
                    cycle_map,
                    domains,
                )
            })
    }

    fn value_analysis(
        &self,
        val: dir::DirValue,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>> {
        self.value_map
            .get(&cgraph::NodeValue::Value(val))
            .map_or(Ok(()), |node_id| {
                self.check_node(
                    ctx,
                    *node_id,
                    cgraph::Relation::Positive,
                    cgraph::Strength::Strong,
                    memo,
                    cycle_map,
                    domains,
                )
            })
    }

    fn check_value_validity(
        &self,
        val: dir::DirValue,
        analysis_ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<bool, cgraph::GraphError<dir::DirValue>> {
        let maybe_node_id = self.value_map.get(&cgraph::NodeValue::Value(val));

        let node_id = if let Some(nid) = maybe_node_id {
            nid
        } else {
            return Ok(false);
        };

        let result = self.check_node(
            analysis_ctx,
            *node_id,
            cgraph::Relation::Positive,
            cgraph::Strength::Weak,
            memo,
            cycle_map,
            domains,
        );

        match result {
            Ok(_) => Ok(true),
            Err(e) => {
                e.get_analysis_trace()?;
                Ok(false)
            }
        }
    }

    fn key_value_analysis(
        &self,
        val: dir::DirValue,
        ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), cgraph::GraphError<dir::DirValue>> {
        self.key_analysis(val.get_key(), ctx, memo, cycle_map, domains)
            .and_then(|_| self.value_analysis(val, ctx, memo, cycle_map, domains))
    }

    fn assertion_analysis(
        &self,
        positive_ctx: &[(&dir::DirValue, &Metadata)],
        analysis_ctx: &AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>> {
        positive_ctx.iter().try_for_each(|(value, metadata)| {
            self.key_value_analysis((*value).clone(), analysis_ctx, memo, cycle_map, domains)
                .map_err(|e| AnalysisError::assertion_from_graph_error(metadata, e))
        })
    }

    fn negation_analysis(
        &self,
        negative_ctx: &[(&[dir::DirValue], &Metadata)],
        analysis_ctx: &mut AnalysisContext,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        cycle_map: &mut cgraph::CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>> {
        let mut keywise_metadata: FxHashMap<dir::DirKey, Vec<&Metadata>> = FxHashMap::default();
        let mut keywise_negation: FxHashMap<dir::DirKey, FxHashSet<&dir::DirValue>> =
            FxHashMap::default();

        for (values, metadata) in negative_ctx {
            let mut metadata_added = false;

            for dir_value in *values {
                if !metadata_added {
                    keywise_metadata
                        .entry(dir_value.get_key())
                        .or_default()
                        .push(metadata);

                    metadata_added = true;
                }

                keywise_negation
                    .entry(dir_value.get_key())
                    .or_default()
                    .insert(dir_value);
            }
        }

        for (key, negation_set) in keywise_negation {
            let all_metadata = keywise_metadata.remove(&key).unwrap_or_default();
            let first_metadata = all_metadata.first().copied().cloned().unwrap_or_default();

            self.key_analysis(key.clone(), analysis_ctx, memo, cycle_map, domains)
                .map_err(|e| AnalysisError::assertion_from_graph_error(&first_metadata, e))?;

            let mut value_set = if let Some(set) = key.kind.get_value_set() {
                set
            } else {
                continue;
            };

            value_set.retain(|v| !negation_set.contains(v));

            for value in value_set {
                analysis_ctx.insert(value.clone());
                self.value_analysis(value.clone(), analysis_ctx, memo, cycle_map, domains)
                    .map_err(|e| {
                        AnalysisError::negation_from_graph_error(all_metadata.clone(), e)
                    })?;
                analysis_ctx.remove(value);
            }
        }

        Ok(())
    }

    fn perform_context_analysis(
        &self,
        ctx: &types::ConjunctiveContext<'_>,
        memo: &mut cgraph::Memoization<dir::DirValue>,
        domains: Option<&[String]>,
    ) -> Result<(), AnalysisError<dir::DirValue>> {
        let mut analysis_ctx = AnalysisContext::from_dir_values(
            ctx.iter()
                .filter_map(|ctx_val| ctx_val.value.get_assertion().cloned()),
        );

        let positive_ctx = ctx
            .iter()
            .filter_map(|ctx_val| {
                ctx_val
                    .value
                    .get_assertion()
                    .map(|val| (val, ctx_val.metadata))
            })
            .collect::<Vec<_>>();
        self.assertion_analysis(
            &positive_ctx,
            &analysis_ctx,
            memo,
            &mut cgraph::CycleCheck::new(),
            domains,
        )?;

        let negative_ctx = ctx
            .iter()
            .filter_map(|ctx_val| {
                ctx_val
                    .value
                    .get_negation()
                    .map(|vals| (vals, ctx_val.metadata))
            })
            .collect::<Vec<_>>();
        self.negation_analysis(
            &negative_ctx,
            &mut analysis_ctx,
            memo,
            &mut cgraph::CycleCheck::new(),
            domains,
        )?;

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use std::ops::Deref;

    use euclid_macros::knowledge;
    use hyperswitch_constraint_graph::CycleCheck;

    use super::*;
    use crate::{dirval, frontend::dir::enums};

    #[test]
    fn test_strong_positive_relation_success() {
        let graph = knowledge! {
            PaymentMethod(Card) ->> CaptureMethod(Automatic);
            PaymentMethod(not Wallet)
                & PaymentMethod(not PayLater) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_strong_positive_relation_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) ->> CaptureMethod(Automatic);
            PaymentMethod(not Wallet) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([dirval!(CaptureMethod = Automatic)]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_strong_negative_relation_success() {
        let graph = knowledge! {
            PaymentMethod(Card) -> CaptureMethod(Automatic);
            PaymentMethod(not Wallet) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_strong_negative_relation_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) -> CaptureMethod(Automatic);
            PaymentMethod(not Wallet) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Wallet),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_normal_one_of_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) -> CaptureMethod(Automatic);
            PaymentMethod(Wallet) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );
        assert!(matches!(
            *Weak::upgrade(&result.unwrap_err().get_analysis_trace().unwrap())
                .expect("Expected Arc"),
            cgraph::AnalysisTrace::Value {
                predecessors: Some(cgraph::error::ValueTracePredecessor::OneOf(_)),
                ..
            }
        ));
    }

    #[test]
    fn test_all_aggregator_success() {
        let graph = knowledge! {
            PaymentMethod(Card) & PaymentMethod(not Wallet) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentMethod = Card),
                dirval!(CaptureMethod = Automatic),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_all_aggregator_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) & PaymentMethod(not Wallet) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_all_aggregator_mandatory_failure() {
        let graph = knowledge! {
            PaymentMethod(Card) & PaymentMethod(not Wallet) ->> CaptureMethod(Automatic);
        };
        let mut memo = cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
            ]),
            &mut memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(matches!(
            *Weak::upgrade(&result.unwrap_err().get_analysis_trace().unwrap())
                .expect("Expected Arc"),
            cgraph::AnalysisTrace::Value {
                predecessors: Some(cgraph::error::ValueTracePredecessor::Mandatory(_)),
                ..
            }
        ));
    }

    #[test]
    fn test_in_aggregator_success() {
        let graph = knowledge! {
            PaymentMethod(in [Card, Wallet]) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_in_aggregator_failure() {
        let graph = knowledge! {
            PaymentMethod(in [Card, Wallet]) -> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
                dirval!(PaymentMethod = PayLater),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_not_in_aggregator_success() {
        let graph = knowledge! {
            PaymentMethod(not in [Card, Wallet]) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
                dirval!(PaymentMethod = BankRedirect),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_ok());
    }

    #[test]
    fn test_not_in_aggregator_failure() {
        let graph = knowledge! {
            PaymentMethod(not in [Card, Wallet]) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = PayLater),
                dirval!(PaymentMethod = BankRedirect),
                dirval!(PaymentMethod = Card),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        assert!(result.is_err());
    }

    #[test]
    fn test_in_aggregator_failure_trace() {
        let graph = knowledge! {
            PaymentMethod(in [Card, Wallet]) ->> CaptureMethod(Automatic);
        };
        let memo = &mut cgraph::Memoization::new();
        let result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(CaptureMethod = Automatic),
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
                dirval!(PaymentMethod = PayLater),
            ]),
            memo,
            &mut CycleCheck::new(),
            None,
        );

        if let cgraph::AnalysisTrace::Value {
            predecessors: Some(cgraph::error::ValueTracePredecessor::Mandatory(agg_error)),
            ..
        } = Weak::upgrade(&result.unwrap_err().get_analysis_trace().unwrap())
            .expect("Expected arc")
            .deref()
        {
            assert!(matches!(
                *Weak::upgrade(agg_error.deref()).expect("Expected Arc"),
                cgraph::AnalysisTrace::InAggregation {
                    found: Some(dir::DirValue::PaymentMethod(enums::PaymentMethod::PayLater)),
                    ..
                }
            ));
        } else {
            panic!("Failed unwrapping OnlyInAggregation trace from AnalysisTrace");
        }
    }

    #[test]
    fn test_memoization_in_kgraph() {
        let mut builder = cgraph::ConstraintGraphBuilder::new();
        let _node_1 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Wallet)),
            None,
            None::<()>,
        );
        let _node_2 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::BillingCountry(enums::BillingCountry::India)),
            None,
            None::<()>,
        );
        let _node_3 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::BusinessCountry(
                enums::BusinessCountry::UnitedStatesOfAmerica,
            )),
            None,
            None::<()>,
        );
        let mut memo = cgraph::Memoization::new();
        let mut cycle_map = CycleCheck::new();
        let _edge_1 = builder
            .make_edge(
                _node_1,
                _node_2,
                cgraph::Strength::Strong,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_2 = builder
            .make_edge(
                _node_2,
                _node_3,
                cgraph::Strength::Strong,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to an edge");
        let graph = builder.build();
        let _result = graph.key_value_analysis(
            dirval!(BusinessCountry = UnitedStatesOfAmerica),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentMethod = Wallet),
                dirval!(BillingCountry = India),
                dirval!(BusinessCountry = UnitedStatesOfAmerica),
            ]),
            &mut memo,
            &mut cycle_map,
            None,
        );
        let _answer = memo
            .get(&(
                _node_3,
                cgraph::Relation::Positive,
                cgraph::Strength::Strong,
            ))
            .expect("Memoization not workng");
        matches!(_answer, Ok(()));
    }

    #[test]
    fn test_cycle_resolution_in_graph() {
        let mut builder = cgraph::ConstraintGraphBuilder::new();
        let _node_1 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Wallet)),
            None,
            None::<()>,
        );
        let _node_2 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Card)),
            None,
            None::<()>,
        );
        let mut memo = cgraph::Memoization::new();
        let mut cycle_map = CycleCheck::new();
        let _edge_1 = builder
            .make_edge(
                _node_1,
                _node_2,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_2 = builder
            .make_edge(
                _node_2,
                _node_1,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to an edge");
        let graph = builder.build();
        let _result = graph.key_value_analysis(
            dirval!(PaymentMethod = Wallet),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentMethod = Wallet),
                dirval!(PaymentMethod = Card),
            ]),
            &mut memo,
            &mut cycle_map,
            None,
        );

        assert!(_result.is_ok());
    }

    #[test]
    fn test_cycle_resolution_in_graph1() {
        let mut builder = cgraph::ConstraintGraphBuilder::new();
        let _node_1 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::CaptureMethod(
                enums::CaptureMethod::Automatic,
            )),
            None,
            None::<()>,
        );

        let _node_2 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Card)),
            None,
            None::<()>,
        );
        let _node_3 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Wallet)),
            None,
            None::<()>,
        );
        let mut memo = cgraph::Memoization::new();
        let mut cycle_map = CycleCheck::new();

        let _edge_1 = builder
            .make_edge(
                _node_1,
                _node_2,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_2 = builder
            .make_edge(
                _node_1,
                _node_3,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_3 = builder
            .make_edge(
                _node_2,
                _node_1,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_4 = builder
            .make_edge(
                _node_3,
                _node_1,
                cgraph::Strength::Strong,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");

        let graph = builder.build();
        let _result = graph.key_value_analysis(
            dirval!(CaptureMethod = Automatic),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
                dirval!(CaptureMethod = Automatic),
            ]),
            &mut memo,
            &mut cycle_map,
            None,
        );

        assert!(_result.is_ok());
    }

    #[test]
    fn test_cycle_resolution_in_graph2() {
        let mut builder = cgraph::ConstraintGraphBuilder::new();
        let _node_0 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::BillingCountry(
                enums::BillingCountry::Afghanistan,
            )),
            None,
            None::<()>,
        );

        let _node_1 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::CaptureMethod(
                enums::CaptureMethod::Automatic,
            )),
            None,
            None::<()>,
        );

        let _node_2 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Card)),
            None,
            None::<()>,
        );
        let _node_3 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Wallet)),
            None,
            None::<()>,
        );

        let _node_4 = builder.make_value_node(
            cgraph::NodeValue::Value(dir::DirValue::PaymentCurrency(enums::PaymentCurrency::USD)),
            None,
            None::<()>,
        );

        let mut memo = cgraph::Memoization::new();
        let mut cycle_map = CycleCheck::new();

        let _edge_1 = builder
            .make_edge(
                _node_0,
                _node_1,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_2 = builder
            .make_edge(
                _node_1,
                _node_2,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_3 = builder
            .make_edge(
                _node_1,
                _node_3,
                cgraph::Strength::Weak,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_4 = builder
            .make_edge(
                _node_3,
                _node_4,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_5 = builder
            .make_edge(
                _node_2,
                _node_4,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");

        let _edge_6 = builder
            .make_edge(
                _node_4,
                _node_1,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");
        let _edge_7 = builder
            .make_edge(
                _node_4,
                _node_0,
                cgraph::Strength::Normal,
                cgraph::Relation::Positive,
                None::<cgraph::DomainId>,
            )
            .expect("Failed to make an edge");

        let graph = builder.build();
        let _result = graph.key_value_analysis(
            dirval!(BillingCountry = Afghanistan),
            &AnalysisContext::from_dir_values([
                dirval!(PaymentCurrency = USD),
                dirval!(PaymentMethod = Card),
                dirval!(PaymentMethod = Wallet),
                dirval!(CaptureMethod = Automatic),
                dirval!(BillingCountry = Afghanistan),
            ]),
            &mut memo,
            &mut cycle_map,
            None,
        );

        assert!(_result.is_ok());
    }
}


pub mod transformers;

use std::sync::LazyLock;

use api_models::webhooks::IncomingWebhookEvent;
use common_enums::{enums, CallConnectorAction, PaymentAction};
use common_utils::{
    crypto,
    errors::CustomResult,
    ext_traits::{ByteSliceExt, BytesExt, ValueExt},
    request::{Method, Request, RequestBuilder, RequestContent},
    types::{
        AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector, StringMinorUnit,
        StringMinorUnitForConnector,
    },
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    payment_method_data::PaymentMethodData,
    router_data::{AccessToken, ErrorResponse, RouterData},
    router_flow_types::{
        access_token_auth::AccessTokenAuth,
        payments::{
            Authorize, Capture, CreateOrder, PSync, PaymentMethodToken, Session, SetupMandate, Void,
        },
        refunds::{Execute, RSync},
        CompleteAuthorize, CreateConnectorCustomer,
    },
    router_request_types::{
        AccessTokenRequestData, CompleteAuthorizeData, ConnectorCustomerData,
        CreateOrderRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData,
        PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData,
        RefundsData, SetupMandateRequestData,
    },
    router_response_types::{
        ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData,
        SupportedPaymentMethods, SupportedPaymentMethodsExt,
    },
    types::{
        ConnectorCustomerRouterData, CreateOrderRouterData, PaymentsAuthorizeRouterData,
        PaymentsCancelRouterData, PaymentsCaptureRouterData, PaymentsCompleteAuthorizeRouterData,
        PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData,
    },
};
use hyperswitch_interfaces::{
    api::{
        self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse,
        ConnectorSpecifications, ConnectorValidation,
    },
    configs::Connectors,
    disputes::DisputePayload,
    errors,
    events::connector_api_logs::ConnectorEvent,
    types::{self, ConnectorCustomerType, CreateOrderType, Response},
    webhooks::{IncomingWebhook, IncomingWebhookRequestDetails, WebhookContext},
};
use masking::{Mask, PeekInterface};
use router_env::logger;
use transformers as airwallex;

use crate::{
    connectors::airwallex::transformers::AirwallexAuthorizeResponse,
    constants::headers,
    types::{RefreshTokenRouterData, ResponseRouterData},
    utils::{
        self as connector_utils, convert_amount, AccessTokenRequestInfo, ForeignTryFrom,
        PaymentMethodDataType, PaymentsAuthorizeRequestData, RefundsRequestData,
    },
};

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

impl Airwallex {
    pub const fn new() -> &'static Self {
        &Self {
            amount_converter: &StringMajorUnitForConnector,
            amount_converter_to_string_minor: &StringMinorUnitForConnector,
        }
    }
}

impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Airwallex
where
    Self: ConnectorIntegration<Flow, Request, Response>,
{
    fn build_headers(
        &self,
        req: &RouterData<Flow, Request, Response>,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let mut headers = vec![(
            headers::CONTENT_TYPE.to_string(),
            self.get_content_type().to_string().into(),
        )];
        let access_token = req
            .access_token
            .clone()
            .ok_or(errors::ConnectorError::FailedToObtainAuthType)?;

        let auth_header = (
            headers::AUTHORIZATION.to_string(),
            format!("Bearer {}", access_token.token.peek()).into_masked(),
        );

        headers.push(auth_header);
        Ok(headers)
    }
}

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

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

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

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

    fn build_error_response(
        &self,
        res: Response,
        event_builder: Option<&mut ConnectorEvent>,
    ) -> CustomResult<ErrorResponse, errors::ConnectorError> {
        logger::debug!(payu_error_response=?res);

        let status_code = res.status_code;
        let response: Result<airwallex::AirwallexErrorResponse, _> =
            res.response.parse_struct("Airwallex ErrorResponse");

        match response {
            Ok(response) => {
                event_builder.map(|i| i.set_error_response_body(&response));
                router_env::logger::info!(connector_response=?response);
                let network_error_message = response
                    .provider_original_response_code
                    .clone()
                    .and_then(airwallex::map_error_code_to_message);
                Ok(ErrorResponse {
                    status_code,
                    code: response.code,
                    message: response.message,
                    reason: response.source,
                    attempt_status: None,
                    connector_transaction_id: None,
                    connector_response_reference_id: None,
                    network_advice_code: None,
                    network_decline_code: response.provider_original_response_code.clone(),
                    network_error_message,
                    connector_metadata: None,
                })
            }
            Err(error_msg) => {
                event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": status_code})));
                router_env::logger::error!(deserialization_error =? error_msg);
                connector_utils::handle_json_response_deserialization_failure(res, "tesouro")
            }
        }
    }
}

impl ConnectorValidation for Airwallex {
    fn validate_mandate_payment(
        &self,
        pm_type: Option<enums::PaymentMethodType>,
        pm_data: PaymentMethodData,
    ) -> CustomResult<(), errors::ConnectorError> {
        let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]);
        connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id())
    }
}

impl api::Payment for Airwallex {}
impl api::PaymentsCompleteAuthorize for Airwallex {}
impl api::PaymentsCreateOrder for Airwallex {}
impl api::MandateSetup for Airwallex {}
impl ConnectorIntegration<SetupMandate, SetupMandateRequestData, PaymentsResponseData>
    for Airwallex
{
    fn build_request(
        &self,
        _req: &SetupMandateRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Err(
            errors::ConnectorError::NotImplemented("Setup Mandate flow for Airwallex".to_string())
                .into(),
        )
    }
}

impl api::ConnectorCustomer for Airwallex {}

impl ConnectorIntegration<CreateConnectorCustomer, ConnectorCustomerData, PaymentsResponseData>
    for Airwallex
{
    fn get_headers(
        &self,
        req: &ConnectorCustomerRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &ConnectorCustomerRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}",
            self.base_url(connectors),
            "api/v1/pa/customers/create"
        ))
    }

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

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

    fn handle_response(
        &self,
        data: &ConnectorCustomerRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<ConnectorCustomerRouterData, errors::ConnectorError>
    where
        PaymentsResponseData: Clone,
    {
        let response: airwallex::AirwallexCustomerResponse = res
            .response
            .parse_struct("AirwallexCustomerResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)
    }

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

impl api::PaymentToken for Airwallex {}

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

impl api::ConnectorAccessToken for Airwallex {}

impl ConnectorIntegration<AccessTokenAuth, AccessTokenRequestData, AccessToken> for Airwallex {
    fn get_url(
        &self,
        _req: &RefreshTokenRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}",
            self.base_url(connectors),
            "api/v1/authentication/login"
        ))
    }

    fn get_headers(
        &self,
        req: &RefreshTokenRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        let headers = vec![
            (
                headers::X_API_KEY.to_string(),
                req.request.app_id.clone().into_masked(),
            ),
            ("Content-Length".to_string(), "0".to_string().into()),
            (
                "x-client-id".to_string(),
                req.get_request_id()?.into_masked(),
            ),
        ];
        Ok(headers)
    }

    fn build_request(
        &self,
        req: &RefreshTokenRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        let req = Some(
            RequestBuilder::new()
                .method(Method::Post)
                .attach_default_headers()
                .headers(types::RefreshTokenType::get_headers(self, req, connectors)?)
                .url(&types::RefreshTokenType::get_url(self, req, connectors)?)
                .build(),
        );
        logger::debug!(payu_access_token_request=?req);
        Ok(req)
    }
    fn handle_response(
        &self,
        data: &RefreshTokenRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<RefreshTokenRouterData, errors::ConnectorError> {
        logger::debug!(access_token_response=?res);
        let response: airwallex::AirwallexAuthUpdateResponse = res
            .response
            .parse_struct("airwallex AirwallexAuthUpdateResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)
    }

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

impl ConnectorIntegration<CreateOrder, CreateOrderRequestData, PaymentsResponseData> for Airwallex {
    fn get_headers(
        &self,
        req: &CreateOrderRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        _req: &CreateOrderRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}",
            self.base_url(connectors),
            "api/v1/pa/payment_intents/create"
        ))
    }

    fn get_request_body(
        &self,
        req: &CreateOrderRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount = convert_amount(
            self.amount_converter,
            req.request.minor_amount,
            req.request.currency,
        )?;
        let connector_router_data = airwallex::AirwallexRouterData::try_from((amount, req))?;
        let connector_req = airwallex::AirwallexIntentRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &CreateOrderRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<CreateOrderRouterData, errors::ConnectorError> {
        let response: airwallex::AirwallexOrderResponse = res
            .response
            .parse_struct("airwallex AirwallexOrderResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)
    }

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

impl api::PaymentAuthorize for Airwallex {}

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

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

    fn get_url(
        &self,
        req: &PaymentsAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}{}{}",
            self.base_url(connectors),
            "api/v1/pa/payment_intents/",
            req.request.get_order_id()?,
            "/confirm"
        ))
    }

    fn get_request_body(
        &self,
        req: &PaymentsAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount_in_minor_unit = MinorUnit::new(req.request.amount);
        let amount = convert_amount(
            self.amount_converter,
            amount_in_minor_unit,
            req.request.currency,
        )?;
        let connector_router_data = airwallex::AirwallexRouterData::try_from((amount, req))?;
        let connector_req = airwallex::AirwallexPaymentsRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

    fn handle_response(
        &self,
        data: &PaymentsAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsAuthorizeRouterData, errors::ConnectorError> {
        let response: AirwallexAuthorizeResponse = res
            .response
            .parse_struct("AirwallexAuthorizeResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

        event_builder.map(|i| i.set_response_body(&response));
        RouterData::foreign_try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)
    }

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

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

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

    fn get_url(
        &self,
        req: &PaymentsSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let connector_payment_id = req
            .request
            .connector_transaction_id
            .get_connector_transaction_id()
            .change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
        Ok(format!(
            "{}{}{}",
            self.base_url(connectors),
            "api/v1/pa/payment_intents/",
            connector_payment_id,
        ))
    }

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

    fn handle_response(
        &self,
        data: &PaymentsSyncRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
        logger::debug!(payment_sync_response=?res);
        let response: airwallex::AirwallexPaymentsSyncResponse = res
            .response
            .parse_struct("airwallex AirwallexPaymentsSyncResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

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

        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)
    }

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

impl ConnectorIntegration<CompleteAuthorize, CompleteAuthorizeData, PaymentsResponseData>
    for Airwallex
{
    fn get_headers(
        &self,
        req: &PaymentsCompleteAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }
    fn get_content_type(&self) -> &'static str {
        self.common_get_content_type()
    }
    fn get_url(
        &self,
        req: &PaymentsCompleteAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        let connector_payment_id = req
            .request
            .connector_transaction_id
            .clone()
            .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?;
        Ok(format!(
            "{}api/v1/pa/payment_intents/{}/confirm_continue",
            self.base_url(connectors),
            connector_payment_id,
        ))
    }
    fn get_request_body(
        &self,
        req: &PaymentsCompleteAuthorizeRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let req_obj = airwallex::AirwallexCompleteRequest::try_from(req)?;

        Ok(RequestContent::Json(Box::new(req_obj)))
    }
    fn build_request(
        &self,
        req: &PaymentsCompleteAuthorizeRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Option<Request>, errors::ConnectorError> {
        Ok(Some(
            RequestBuilder::new()
                .method(Method::Post)
                .url(&types::PaymentsCompleteAuthorizeType::get_url(
                    self, req, connectors,
                )?)
                .headers(types::PaymentsCompleteAuthorizeType::get_headers(
                    self, req, connectors,
                )?)
                .set_body(types::PaymentsCompleteAuthorizeType::get_request_body(
                    self, req, connectors,
                )?)
                .build(),
        ))
    }
    fn handle_response(
        &self,
        data: &PaymentsCompleteAuthorizeRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
        let response: airwallex::AirwallexPaymentsResponse = res
            .response
            .parse_struct("AirwallexPaymentsResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)
    }

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

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

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

    fn get_url(
        &self,
        req: &PaymentsCaptureRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}{}{}",
            self.base_url(connectors),
            "api/v1/pa/payment_intents/",
            req.request.connector_transaction_id,
            "/capture"
        ))
    }

    fn get_request_body(
        &self,
        req: &PaymentsCaptureRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = airwallex::AirwallexPaymentsCaptureRequest::try_from(req)?;

        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

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

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

impl api::PaymentSession for Airwallex {}

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

impl api::PaymentVoid for Airwallex {}

impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Airwallex {
    fn get_headers(
        &self,
        req: &PaymentsCancelRouterData,
        connectors: &Connectors,
    ) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
        self.build_headers(req, connectors)
    }

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

    fn get_url(
        &self,
        req: &PaymentsCancelRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}{}{}",
            self.base_url(connectors),
            "api/v1/pa/payment_intents/",
            req.request.connector_transaction_id,
            "/cancel"
        ))
    }
    fn get_request_body(
        &self,
        req: &PaymentsCancelRouterData,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let connector_req = airwallex::AirwallexPaymentsCancelRequest::try_from(req)?;

        Ok(RequestContent::Json(Box::new(connector_req)))
    }
    fn handle_response(
        &self,
        data: &PaymentsCancelRouterData,
        event_builder: Option<&mut ConnectorEvent>,
        res: Response,
    ) -> CustomResult<PaymentsCancelRouterData, errors::ConnectorError> {
        let response: airwallex::AirwallexPaymentsResponse = res
            .response
            .parse_struct("Airwallex PaymentsResponse")
            .change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
        event_builder.map(|i| i.set_response_body(&response));
        router_env::logger::info!(connector_response=?response);
        RouterData::try_from(ResponseRouterData {
            response,
            data: data.clone(),
            http_code: res.status_code,
        })
        .change_context(errors::ConnectorError::ResponseHandlingFailed)
    }

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

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

impl api::Refund for Airwallex {}
impl api::RefundExecute for Airwallex {}
impl api::RefundSync for Airwallex {}

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

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

    fn get_url(
        &self,
        _req: &RefundsRouterData<Execute>,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}",
            self.base_url(connectors),
            "api/v1/pa/refunds/create"
        ))
    }

    fn get_request_body(
        &self,
        req: &RefundsRouterData<Execute>,
        _connectors: &Connectors,
    ) -> CustomResult<RequestContent, errors::ConnectorError> {
        let amount_in_minor_unit = MinorUnit::new(req.request.refund_amount);
        let amount = convert_amount(
            self.amount_converter,
            amount_in_minor_unit,
            req.request.currency,
        )?;
        let connector_router_data = airwallex::AirwallexRouterData::try_from((amount, req))?;
        let connector_req = airwallex::AirwallexRefundRequest::try_from(&connector_router_data)?;
        Ok(RequestContent::Json(Box::new(connector_req)))
    }

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

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

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

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

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

    fn get_url(
        &self,
        req: &RefundSyncRouterData,
        connectors: &Connectors,
    ) -> CustomResult<String, errors::ConnectorError> {
        Ok(format!(
            "{}{}{}",
            self.base_url(connectors),
            "/api/v1/pa/refunds/",
            req.request.get_connector_refund_id()?
        ))
    }

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

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

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

#[async_trait::async_trait]
impl IncomingWebhook for Airwallex {
    fn get_webhook_source_verification_algorithm(
        &self,
        _request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
        Ok(Box::new(crypto::HmacSha256))
    }

    fn get_webhook_source_verification_signature(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let security_header = request
            .headers
            .get("x-signature")
            .map(|header_value| {
                header_value
                    .to_str()
                    .map(String::from)
                    .map_err(|_| errors::ConnectorError::WebhookSignatureNotFound)
            })
            .ok_or(errors::ConnectorError::WebhookSignatureNotFound)??;

        hex::decode(security_header)
            .change_context(errors::ConnectorError::WebhookSignatureNotFound)
    }

    fn get_webhook_source_verification_message(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        _merchant_id: &common_utils::id_type::MerchantId,
        _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
    ) -> CustomResult<Vec<u8>, errors::ConnectorError> {
        let timestamp = request
            .headers
            .get("x-timestamp")
            .map(|header_value| {
                header_value
                    .to_str()
                    .map(String::from)
                    .map_err(|_| errors::ConnectorError::WebhookSignatureNotFound)
            })
            .ok_or(errors::ConnectorError::WebhookSignatureNotFound)??;

        Ok(format!("{}{}", timestamp, String::from_utf8_lossy(request.body)).into_bytes())
    }

    fn get_webhook_object_reference_id(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
        let details: airwallex::AirwallexWebhookData = request
            .body
            .parse_struct("airwallexWebhookData")
            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;

        if airwallex::is_transaction_event(&details.name) {
            Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
                api_models::payments::PaymentIdType::ConnectorTransactionId(
                    details
                        .source_id
                        .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?,
                ),
            ))
        } else if airwallex::is_refund_event(&details.name) {
            Ok(api_models::webhooks::ObjectReferenceId::RefundId(
                api_models::webhooks::RefundIdType::ConnectorRefundId(
                    details
                        .source_id
                        .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?,
                ),
            ))
        } else if airwallex::is_dispute_event(&details.name) {
            let dispute_details: airwallex::AirwallexDisputeObject = details
                .data
                .object
                .parse_value("AirwallexDisputeObject")
                .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;

            Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
                api_models::payments::PaymentIdType::ConnectorTransactionId(
                    dispute_details.payment_intent_id,
                ),
            ))
        } else {
            Err(report!(errors::ConnectorError::WebhookEventTypeNotFound))
        }
    }

    fn get_webhook_event_type(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        _context: Option<&WebhookContext>,
    ) -> CustomResult<IncomingWebhookEvent, errors::ConnectorError> {
        let details: airwallex::AirwallexWebhookData = request
            .body
            .parse_struct("airwallexWebhookData")
            .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
        Ok(IncomingWebhookEvent::try_from(details.name)?)
    }

    fn get_webhook_resource_object(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
    ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
        let details: airwallex::AirwallexWebhookObjectResource = request
            .body
            .parse_struct("AirwallexWebhookObjectResource")
            .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;

        Ok(Box::new(details.data.object))
    }

    fn get_dispute_details(
        &self,
        request: &IncomingWebhookRequestDetails<'_>,
        _context: Option<&WebhookContext>,
    ) -> CustomResult<DisputePayload, errors::ConnectorError> {
        let details: airwallex::AirwallexWebhookData = request
            .body
            .parse_struct("airwallexWebhookData")
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        let dispute_details: airwallex::AirwallexDisputeObject = details
            .data
            .object
            .parse_value("AirwallexDisputeObject")
            .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
        let amount = convert_amount(
            self.amount_converter_to_string_minor,
            dispute_details.dispute_amount,
            dispute_details.dispute_currency,
        )?;
        Ok(DisputePayload {
            amount,
            currency: dispute_details.dispute_currency,
            dispute_stage: api_models::enums::DisputeStage::from(dispute_details.stage.clone()),
            connector_dispute_id: dispute_details.dispute_id,
            connector_reason: dispute_details.dispute_reason_type,
            connector_reason_code: dispute_details.dispute_original_reason_code,
            challenge_required_by: None,
            connector_status: dispute_details.status.to_string(),
            created_at: dispute_details.created_at,
            updated_at: dispute_details.updated_at,
        })
    }
}

impl ConnectorRedirectResponse for Airwallex {
    fn get_flow_type(
        &self,
        _query_params: &str,
        _json_payload: Option<serde_json::Value>,
        action: PaymentAction,
    ) -> CustomResult<CallConnectorAction, errors::ConnectorError> {
        match action {
            PaymentAction::PSync
            | PaymentAction::CompleteAuthorize
            | PaymentAction::PaymentAuthenticateCompleteAuthorize => {
                Ok(CallConnectorAction::Trigger)
            }
        }
    }
}

static AIRWALLEX_SUPPORTED_PAYMENT_METHODS: LazyLock<SupportedPaymentMethods> =
    LazyLock::new(|| {
        let supported_capture_methods = vec![
            enums::CaptureMethod::Automatic,
            enums::CaptureMethod::Manual,
            enums::CaptureMethod::SequentialAutomatic,
        ];

        let supported_capture_methods_redirect = vec![
            enums::CaptureMethod::Automatic,
            enums::CaptureMethod::SequentialAutomatic,
        ];

        let supported_card_network = vec![
            common_enums::CardNetwork::Visa,
            common_enums::CardNetwork::Mastercard,
            common_enums::CardNetwork::AmericanExpress,
            common_enums::CardNetwork::JCB,
            common_enums::CardNetwork::DinersClub,
            common_enums::CardNetwork::UnionPay,
            common_enums::CardNetwork::Discover,
        ];

        let mut airwallex_supported_payment_methods = SupportedPaymentMethods::new();

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::Card,
            enums::PaymentMethodType::Credit,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::Supported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods.clone(),
                specific_features: Some(
                    api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({
                        api_models::feature_matrix::CardSpecificFeatures {
                            three_ds: common_enums::FeatureStatus::Supported,
                            no_three_ds: common_enums::FeatureStatus::Supported,
                            supported_card_networks: supported_card_network.clone(),
                        }
                    }),
                ),
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::Card,
            enums::PaymentMethodType::Debit,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::Supported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods.clone(),
                specific_features: Some(
                    api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({
                        api_models::feature_matrix::CardSpecificFeatures {
                            three_ds: common_enums::FeatureStatus::Supported,
                            no_three_ds: common_enums::FeatureStatus::Supported,
                            supported_card_networks: supported_card_network.clone(),
                        }
                    }),
                ),
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::Wallet,
            enums::PaymentMethodType::GooglePay,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::NotSupported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods.clone(),
                specific_features: None,
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::Wallet,
            enums::PaymentMethodType::Paypal,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::NotSupported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods_redirect.clone(),
                specific_features: None,
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::Wallet,
            enums::PaymentMethodType::Skrill,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::NotSupported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods_redirect.clone(),
                specific_features: None,
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::PayLater,
            enums::PaymentMethodType::Klarna,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::NotSupported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods.clone(),
                specific_features: None,
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::PayLater,
            enums::PaymentMethodType::Atome,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::NotSupported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods_redirect.clone(),
                specific_features: None,
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::BankRedirect,
            enums::PaymentMethodType::Trustly,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::NotSupported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods_redirect.clone(),
                specific_features: None,
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::BankRedirect,
            enums::PaymentMethodType::Blik,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::NotSupported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods_redirect.clone(),
                specific_features: None,
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::BankRedirect,
            enums::PaymentMethodType::Ideal,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::NotSupported,
                refunds: enums::FeatureStatus::Supported,
                supported_capture_methods: supported_capture_methods_redirect.clone(),
                specific_features: None,
            },
        );

        airwallex_supported_payment_methods.add(
            enums::PaymentMethod::BankTransfer,
            enums::PaymentMethodType::IndonesianBankTransfer,
            PaymentMethodDetails {
                mandates: enums::FeatureStatus::NotSupported,
                refunds: enums::FeatureStatus::NotSupported,
                supported_capture_methods: supported_capture_methods_redirect.clone(),
                specific_features: None,
            },
        );

        airwallex_supported_payment_methods
    });

static AIRWALLEX_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo {
    display_name: "Airwallex",
    description: "Airwallex is a multinational financial technology company offering financial services and software as a service (SaaS)",
    connector_type: enums::HyperswitchConnectorCategory::PaymentGateway,
    integration_status: enums::ConnectorIntegrationStatus::Sandbox,
};

static AIRWALLEX_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 3] = [
    enums::EventClass::Payments,
    enums::EventClass::Refunds,
    enums::EventClass::Disputes,
];

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

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

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

    #[cfg(feature = "v1")]
    fn should_call_connector_customer(
        &self,
        payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
    ) -> bool {
        matches!(
            payment_attempt.setup_future_usage_applied,
            Some(enums::FutureUsage::OffSession)
        ) && payment_attempt.customer_acceptance.is_some()
            && matches!(
                payment_attempt.payment_method,
                Some(enums::PaymentMethod::Card)
            )
    }
}