// File: crates/storage_impl/src/lib.rs
// Module: storage_impl
// Public functions: 7
// Public structs: 1

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

use diesel_models as store;
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    behaviour::{Conversion, ReverseConversion},
    merchant_key_store::MerchantKeyStore,
};
use masking::StrongSecret;
use redis::{kv_store::RedisConnInterface, pub_sub::PubSubInterface, RedisStore};
mod address;
pub mod callback_mapper;
pub mod cards_info;
pub mod config;
pub mod connection;
pub mod customers;
pub mod database;
pub mod errors;
pub mod kv_router_store;
pub mod lookup;
pub mod mandate;
pub mod metrics;
pub mod mock_db;
pub mod payment_method;
pub mod payments;
#[cfg(feature = "payouts")]
pub mod payouts;
pub mod redis;
pub mod refund;
mod reverse_lookup;
pub mod utils;

use common_utils::{errors::CustomResult, types::keymanager::KeyManagerState};
use database::store::PgPool;
pub mod tokenization;
#[cfg(not(feature = "payouts"))]
use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface};
pub use mock_db::MockDb;
use redis_interface::{errors::RedisError, RedisConnectionPool, SaddReply};

#[cfg(not(feature = "payouts"))]
pub use crate::database::store::Store;
pub use crate::{database::store::DatabaseStore, errors::StorageError};

#[derive(Debug, Clone)]
pub struct RouterStore<T: DatabaseStore> {
    db_store: T,
    cache_store: Arc<RedisStore>,
    master_encryption_key: StrongSecret<Vec<u8>>,
    pub request_id: Option<String>,
}

#[async_trait::async_trait]
impl<T: DatabaseStore> DatabaseStore for RouterStore<T>
where
    T::Config: Send,
{
    type Config = (
        T::Config,
        redis_interface::RedisSettings,
        StrongSecret<Vec<u8>>,
        tokio::sync::oneshot::Sender<()>,
        &'static str,
    );
    async fn new(
        config: Self::Config,
        tenant_config: &dyn config::TenantConfig,
        test_transaction: bool,
    ) -> error_stack::Result<Self, StorageError> {
        let (db_conf, cache_conf, encryption_key, cache_error_signal, inmemory_cache_stream) =
            config;
        if test_transaction {
            Self::test_store(db_conf, tenant_config, &cache_conf, encryption_key)
                .await
                .attach_printable("failed to create test router store")
        } else {
            Self::from_config(
                db_conf,
                tenant_config,
                encryption_key,
                Self::cache_store(&cache_conf, cache_error_signal).await?,
                inmemory_cache_stream,
            )
            .await
            .attach_printable("failed to create store")
        }
    }
    fn get_master_pool(&self) -> &PgPool {
        self.db_store.get_master_pool()
    }
    fn get_replica_pool(&self) -> &PgPool {
        self.db_store.get_replica_pool()
    }

    fn get_accounts_master_pool(&self) -> &PgPool {
        self.db_store.get_accounts_master_pool()
    }

    fn get_accounts_replica_pool(&self) -> &PgPool {
        self.db_store.get_accounts_replica_pool()
    }
}

impl<T: DatabaseStore> RedisConnInterface for RouterStore<T> {
    fn get_redis_conn(&self) -> error_stack::Result<Arc<RedisConnectionPool>, RedisError> {
        self.cache_store.get_redis_conn()
    }
}

impl<T: DatabaseStore> RouterStore<T> {
    pub async fn from_config(
        db_conf: T::Config,
        tenant_config: &dyn config::TenantConfig,
        encryption_key: StrongSecret<Vec<u8>>,
        cache_store: Arc<RedisStore>,
        inmemory_cache_stream: &str,
    ) -> error_stack::Result<Self, StorageError> {
        let db_store = T::new(db_conf, tenant_config, false).await?;
        let redis_conn = cache_store.redis_conn.clone();
        let cache_store = Arc::new(RedisStore {
            redis_conn: Arc::new(RedisConnectionPool::clone(
                &redis_conn,
                tenant_config.get_redis_key_prefix(),
            )),
        });
        cache_store
            .redis_conn
            .subscribe(inmemory_cache_stream)
            .await
            .change_context(StorageError::InitializationError)
            .attach_printable("Failed to subscribe to inmemory cache stream")?;

        Ok(Self {
            db_store,
            cache_store,
            master_encryption_key: encryption_key,
            request_id: None,
        })
    }

    pub async fn cache_store(
        cache_conf: &redis_interface::RedisSettings,
        cache_error_signal: tokio::sync::oneshot::Sender<()>,
    ) -> error_stack::Result<Arc<RedisStore>, StorageError> {
        let cache_store = RedisStore::new(cache_conf)
            .await
            .change_context(StorageError::InitializationError)
            .attach_printable("Failed to create cache store")?;
        cache_store.set_error_callback(cache_error_signal);
        Ok(Arc::new(cache_store))
    }

    pub fn master_key(&self) -> &StrongSecret<Vec<u8>> {
        &self.master_encryption_key
    }

    pub async fn call_database<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        execute_query: R,
    ) -> error_stack::Result<D, StorageError>
    where
        D: Debug + Sync + Conversion,
        R: futures::Future<Output = error_stack::Result<M, diesel_models::errors::DatabaseError>>
            + Send,
        M: ReverseConversion<D>,
    {
        execute_query
            .await
            .map_err(|error| {
                let new_err = diesel_error_to_data_error(*error.current_context());
                error.change_context(new_err)
            })?
            .convert(
                state,
                key_store.key.get_inner(),
                key_store.merchant_id.clone().into(),
            )
            .await
            .change_context(StorageError::DecryptionError)
    }

    pub async fn find_optional_resource<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        execute_query_fut: R,
    ) -> error_stack::Result<Option<D>, StorageError>
    where
        D: Debug + Sync + Conversion,
        R: futures::Future<
                Output = error_stack::Result<Option<M>, diesel_models::errors::DatabaseError>,
            > + Send,
        M: ReverseConversion<D>,
    {
        match execute_query_fut.await.map_err(|error| {
            let new_err = diesel_error_to_data_error(*error.current_context());
            error.change_context(new_err)
        })? {
            Some(resource) => Ok(Some(
                resource
                    .convert(
                        state,
                        key_store.key.get_inner(),
                        key_store.merchant_id.clone().into(),
                    )
                    .await
                    .change_context(StorageError::DecryptionError)?,
            )),
            None => Ok(None),
        }
    }

    pub async fn find_resources<D, R, M>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        execute_query: R,
    ) -> error_stack::Result<Vec<D>, StorageError>
    where
        D: Debug + Sync + Conversion,
        R: futures::Future<
                Output = error_stack::Result<Vec<M>, diesel_models::errors::DatabaseError>,
            > + Send,
        M: ReverseConversion<D>,
    {
        let resource_futures = execute_query
            .await
            .map_err(|error| {
                let new_err = diesel_error_to_data_error(*error.current_context());
                error.change_context(new_err)
            })?
            .into_iter()
            .map(|resource| async {
                resource
                    .convert(
                        state,
                        key_store.key.get_inner(),
                        key_store.merchant_id.clone().into(),
                    )
                    .await
                    .change_context(StorageError::DecryptionError)
            })
            .collect::<Vec<_>>();

        let resources = futures::future::try_join_all(resource_futures).await?;

        Ok(resources)
    }

    /// # Panics
    ///
    /// Will panic if `CONNECTOR_AUTH_FILE_PATH` is not set
    pub async fn test_store(
        db_conf: T::Config,
        tenant_config: &dyn config::TenantConfig,
        cache_conf: &redis_interface::RedisSettings,
        encryption_key: StrongSecret<Vec<u8>>,
    ) -> error_stack::Result<Self, StorageError> {
        // TODO: create an error enum and return proper error here
        let db_store = T::new(db_conf, tenant_config, true).await?;
        let cache_store = RedisStore::new(cache_conf)
            .await
            .change_context(StorageError::InitializationError)
            .attach_printable("failed to create redis cache")?;
        Ok(Self {
            db_store,
            cache_store: Arc::new(cache_store),
            master_encryption_key: encryption_key,
            request_id: None,
        })
    }
}

// TODO: This should not be used beyond this crate
// Remove the pub modified once StorageScheme usage is completed
pub trait DataModelExt {
    type StorageModel;
    fn to_storage_model(self) -> Self::StorageModel;
    fn from_storage_model(storage_model: Self::StorageModel) -> Self;
}

pub(crate) fn diesel_error_to_data_error(
    diesel_error: diesel_models::errors::DatabaseError,
) -> StorageError {
    match diesel_error {
        diesel_models::errors::DatabaseError::DatabaseConnectionError => {
            StorageError::DatabaseConnectionError
        }
        diesel_models::errors::DatabaseError::NotFound => {
            StorageError::ValueNotFound("Value not found".to_string())
        }
        diesel_models::errors::DatabaseError::UniqueViolation => StorageError::DuplicateValue {
            entity: "entity ",
            key: None,
        },
        _ => StorageError::DatabaseError(error_stack::report!(diesel_error)),
    }
}

#[async_trait::async_trait]
pub trait UniqueConstraints {
    fn unique_constraints(&self) -> Vec<String>;
    fn table_name(&self) -> &str;
    async fn check_for_constraints(
        &self,
        redis_conn: &Arc<RedisConnectionPool>,
    ) -> CustomResult<(), RedisError> {
        let constraints = self.unique_constraints();
        let sadd_result = redis_conn
            .sadd(
                &format!("unique_constraint:{}", self.table_name()).into(),
                constraints,
            )
            .await?;

        match sadd_result {
            SaddReply::KeyNotSet => Err(error_stack::report!(RedisError::SetAddMembersFailed)),
            SaddReply::KeySet => Ok(()),
        }
    }
}

impl UniqueConstraints for diesel_models::Address {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!("address_{}", self.address_id)]
    }
    fn table_name(&self) -> &str {
        "Address"
    }
}

#[cfg(feature = "v2")]
impl UniqueConstraints for diesel_models::PaymentIntent {
    fn unique_constraints(&self) -> Vec<String> {
        vec![self.id.get_string_repr().to_owned()]
    }

    fn table_name(&self) -> &str {
        "PaymentIntent"
    }
}

#[cfg(feature = "v1")]
impl UniqueConstraints for diesel_models::PaymentIntent {
    #[cfg(feature = "v1")]
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!(
            "pi_{}_{}",
            self.merchant_id.get_string_repr(),
            self.payment_id.get_string_repr()
        )]
    }

    #[cfg(feature = "v2")]
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!("pi_{}", self.id.get_string_repr())]
    }

    fn table_name(&self) -> &str {
        "PaymentIntent"
    }
}

#[cfg(feature = "v1")]
impl UniqueConstraints for diesel_models::PaymentAttempt {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!(
            "pa_{}_{}_{}",
            self.merchant_id.get_string_repr(),
            self.payment_id.get_string_repr(),
            self.attempt_id
        )]
    }
    fn table_name(&self) -> &str {
        "PaymentAttempt"
    }
}

#[cfg(feature = "v2")]
impl UniqueConstraints for diesel_models::PaymentAttempt {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!("pa_{}", self.id.get_string_repr())]
    }
    fn table_name(&self) -> &str {
        "PaymentAttempt"
    }
}

#[cfg(feature = "v1")]
impl UniqueConstraints for diesel_models::Refund {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!(
            "refund_{}_{}",
            self.merchant_id.get_string_repr(),
            self.refund_id
        )]
    }
    fn table_name(&self) -> &str {
        "Refund"
    }
}

#[cfg(feature = "v2")]
impl UniqueConstraints for diesel_models::Refund {
    fn unique_constraints(&self) -> Vec<String> {
        vec![self.id.get_string_repr().to_owned()]
    }
    fn table_name(&self) -> &str {
        "Refund"
    }
}

impl UniqueConstraints for diesel_models::ReverseLookup {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!("reverselookup_{}", self.lookup_id)]
    }
    fn table_name(&self) -> &str {
        "ReverseLookup"
    }
}

#[cfg(feature = "payouts")]
impl UniqueConstraints for diesel_models::Payouts {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!(
            "po_{}_{}",
            self.merchant_id.get_string_repr(),
            self.payout_id.get_string_repr()
        )]
    }
    fn table_name(&self) -> &str {
        "Payouts"
    }
}

#[cfg(feature = "payouts")]
impl UniqueConstraints for diesel_models::PayoutAttempt {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!(
            "poa_{}_{}",
            self.merchant_id.get_string_repr(),
            self.payout_attempt_id
        )]
    }
    fn table_name(&self) -> &str {
        "PayoutAttempt"
    }
}

#[cfg(feature = "v1")]
impl UniqueConstraints for diesel_models::PaymentMethod {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!("paymentmethod_{}", self.payment_method_id)]
    }
    fn table_name(&self) -> &str {
        "PaymentMethod"
    }
}

#[cfg(feature = "v2")]
impl UniqueConstraints for diesel_models::PaymentMethod {
    fn unique_constraints(&self) -> Vec<String> {
        vec![self.id.get_string_repr().to_owned()]
    }
    fn table_name(&self) -> &str {
        "PaymentMethod"
    }
}

impl UniqueConstraints for diesel_models::Mandate {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!(
            "mand_{}_{}",
            self.merchant_id.get_string_repr(),
            self.mandate_id
        )]
    }
    fn table_name(&self) -> &str {
        "Mandate"
    }
}

#[cfg(feature = "v1")]
impl UniqueConstraints for diesel_models::Customer {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!(
            "customer_{}_{}",
            self.customer_id.get_string_repr(),
            self.merchant_id.get_string_repr(),
        )]
    }
    fn table_name(&self) -> &str {
        "Customer"
    }
}

#[cfg(feature = "v2")]
impl UniqueConstraints for diesel_models::Customer {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!("customer_{}", self.id.get_string_repr())]
    }
    fn table_name(&self) -> &str {
        "Customer"
    }
}

#[cfg(not(feature = "payouts"))]
impl<T: DatabaseStore> PayoutAttemptInterface for RouterStore<T> {}
#[cfg(not(feature = "payouts"))]
impl<T: DatabaseStore> PayoutsInterface for RouterStore<T> {}

#[cfg(all(feature = "v2", feature = "tokenization_v2"))]
impl UniqueConstraints for diesel_models::tokenization::Tokenization {
    fn unique_constraints(&self) -> Vec<String> {
        vec![format!("id_{}", self.id.get_string_repr())]
    }

    fn table_name(&self) -> &str {
        "tokenization"
    }
}


// Struct: NetworkTokenDetailsResponse
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Implementations: 0

pub struct NetworkTokenDetailsResponse


// Struct: PaymentMethodUpdate
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Implementations: 0

pub struct PaymentMethodUpdate


// File: crates/api_models/src/analytics/sdk_events.rs
// Module: api_models
// Public functions: 1
// Public structs: 13

use std::{
    collections::hash_map::DefaultHasher,
    hash::{Hash, Hasher},
};

use super::{NameDescription, TimeRange};

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SdkEventsRequest {
    pub payment_id: common_utils::id_type::PaymentId,
    pub time_range: TimeRange,
}

#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct SdkEventFilters {
    #[serde(default)]
    pub payment_method: Vec<String>,
    #[serde(default)]
    pub platform: Vec<String>,
    #[serde(default)]
    pub browser_name: Vec<String>,
    #[serde(default)]
    pub source: Vec<String>,
    #[serde(default)]
    pub component: Vec<String>,
    #[serde(default)]
    pub payment_experience: Vec<String>,
}

#[derive(
    Debug,
    serde::Serialize,
    serde::Deserialize,
    strum::AsRefStr,
    PartialEq,
    PartialOrd,
    Eq,
    Ord,
    strum::Display,
    strum::EnumIter,
    Clone,
    Copy,
)]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum SdkEventDimensions {
    // Do not change the order of these enums
    // Consult the Dashboard FE folks since these also affects the order of metrics on FE
    PaymentMethod,
    Platform,
    BrowserName,
    Source,
    Component,
    PaymentExperience,
}

#[derive(
    Clone,
    Debug,
    Hash,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumIter,
    strum::AsRefStr,
)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum SdkEventMetrics {
    PaymentAttempts,
    PaymentMethodsCallCount,
    SdkRenderedCount,
    SdkInitiatedCount,
    PaymentMethodSelectedCount,
    PaymentDataFilledCount,
    AveragePaymentTime,
    LoadTime,
}

#[derive(
    Clone,
    Debug,
    Hash,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    strum::Display,
    strum::EnumIter,
    strum::AsRefStr,
)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum SdkEventNames {
    OrcaElementsCalled,
    AppRendered,
    PaymentMethodChanged,
    PaymentDataFilled,
    PaymentAttempt,
    PaymentMethodsCall,
    ConfirmCall,
    SessionsCall,
    CustomerPaymentMethodsCall,
    RedirectingUser,
    DisplayBankTransferInfoPage,
    DisplayQrCodeInfoPage,
    AuthenticationCall,
    AuthenticationCallInit,
    ThreeDsMethodCall,
    ThreeDsMethodResult,
    ThreeDsMethod,
    LoaderChanged,
    DisplayThreeDsSdk,
    ThreeDsSdkInit,
    AreqParamsGeneration,
    ChallengePresented,
    ChallengeComplete,
}

pub mod metric_behaviour {
    pub struct PaymentAttempts;
    pub struct PaymentMethodsCallCount;
    pub struct SdkRenderedCount;
    pub struct SdkInitiatedCount;
    pub struct PaymentMethodSelectedCount;
    pub struct PaymentDataFilledCount;
    pub struct AveragePaymentTime;
    pub struct LoadTime;
}

impl From<SdkEventMetrics> for NameDescription {
    fn from(value: SdkEventMetrics) -> Self {
        Self {
            name: value.to_string(),
            desc: String::new(),
        }
    }
}

impl From<SdkEventDimensions> for NameDescription {
    fn from(value: SdkEventDimensions) -> Self {
        Self {
            name: value.to_string(),
            desc: String::new(),
        }
    }
}

#[derive(Debug, serde::Serialize, Eq)]
pub struct SdkEventMetricsBucketIdentifier {
    pub payment_method: Option<String>,
    pub platform: Option<String>,
    pub browser_name: Option<String>,
    pub source: Option<String>,
    pub component: Option<String>,
    pub payment_experience: Option<String>,
    pub time_bucket: Option<String>,
}

impl SdkEventMetricsBucketIdentifier {
    pub fn new(
        payment_method: Option<String>,
        platform: Option<String>,
        browser_name: Option<String>,
        source: Option<String>,
        component: Option<String>,
        payment_experience: Option<String>,
        time_bucket: Option<String>,
    ) -> Self {
        Self {
            payment_method,
            platform,
            browser_name,
            source,
            component,
            payment_experience,
            time_bucket,
        }
    }
}

impl Hash for SdkEventMetricsBucketIdentifier {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.payment_method.hash(state);
        self.platform.hash(state);
        self.browser_name.hash(state);
        self.source.hash(state);
        self.component.hash(state);
        self.payment_experience.hash(state);
        self.time_bucket.hash(state);
    }
}

impl PartialEq for SdkEventMetricsBucketIdentifier {
    fn eq(&self, other: &Self) -> bool {
        let mut left = DefaultHasher::new();
        self.hash(&mut left);
        let mut right = DefaultHasher::new();
        other.hash(&mut right);
        left.finish() == right.finish()
    }
}

#[derive(Debug, serde::Serialize)]
pub struct SdkEventMetricsBucketValue {
    pub payment_attempts: Option<u64>,
    pub payment_methods_call_count: Option<u64>,
    pub average_payment_time: Option<u64>,
    pub load_time: Option<u64>,
    pub sdk_rendered_count: Option<u64>,
    pub sdk_initiated_count: Option<u64>,
    pub payment_method_selected_count: Option<u64>,
    pub payment_data_filled_count: Option<u64>,
}

#[derive(Debug, serde::Serialize)]
pub struct MetricsBucketResponse {
    #[serde(flatten)]
    pub values: SdkEventMetricsBucketValue,
    #[serde(flatten)]
    pub dimensions: SdkEventMetricsBucketIdentifier,
}


// Struct: SantanderBoletoPaymentsResponse
// File: crates/hyperswitch_connectors/src/connectors/santander/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct SantanderBoletoPaymentsResponse


// Function: get_token_vault_core
// File: crates/router/src/core/tokenization.rs
// Module: router

pub fn get_token_vault_core(
    state: SessionState,
    merchant_account: &domain::MerchantAccount,
    merchant_key_store: &domain::MerchantKeyStore,
    query: id_type::GlobalTokenId,
) -> CustomResult<serde_json::Value, errors::ApiErrorResponse>


// Trait: ConnectorCommonExt
// File: crates/pm_auth/src/types/api.rs
// Module: pm_auth

pub trait ConnectorCommonExt<Flow, Req, Resp>: ConnectorCommon + ConnectorIntegration<Flow, Req, Resp>


// Implementation: impl Boku
// File: crates/hyperswitch_connectors/src/connectors/boku.rs
// Module: hyperswitch_connectors
// Methods: 1 total (1 public)

impl Boku


// File: crates/analytics/src/auth_events/metrics/challenge_flow_count.rs
// Module: analytics

use std::collections::HashSet;

use api_models::analytics::{
    auth_events::{AuthEventDimensions, AuthEventFilters, AuthEventMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_enums::DecoupledAuthenticationType;
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::AuthEventMetricRow;
use crate::{
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
    AuthInfo,
};

#[derive(Default)]
pub(super) struct ChallengeFlowCount;

#[async_trait::async_trait]
impl<T> super::AuthEventMetric<T> for ChallengeFlowCount
where
    T: AnalyticsDataSource + super::AuthEventMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        auth: &AuthInfo,
        dimensions: &[AuthEventDimensions],
        filters: &AuthEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::Authentications);
        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }
        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;

        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;

        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;

        query_builder
            .add_filter_clause(
                "authentication_type",
                DecoupledAuthenticationType::Challenge,
            )
            .switch()?;
        filters.set_filter_clause(&mut query_builder).switch()?;
        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;
        auth.set_filter_clause(&mut query_builder).switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }
        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<AuthEventMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    AuthEventMetricsBucketIdentifier::new(
                        i.authentication_status.as_ref().map(|i| i.0),
                        i.trans_status.as_ref().map(|i| i.0.clone()),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.error_message.clone(),
                        i.authentication_connector.as_ref().map(|i| i.0),
                        i.message_version.clone(),
                        i.acs_reference_number.clone(),
                        i.mcc.clone(),
                        i.currency.as_ref().map(|i| i.0),
                        i.merchant_country.clone(),
                        i.billing_country.clone(),
                        i.shipping_country.clone(),
                        i.issuer_country.clone(),
                        i.earliest_supported_version.clone(),
                        i.latest_supported_version.clone(),
                        i.whitelist_decision,
                        i.device_manufacturer.clone(),
                        i.device_type.clone(),
                        i.device_brand.clone(),
                        i.device_os.clone(),
                        i.device_display.clone(),
                        i.browser_name.clone(),
                        i.browser_version.clone(),
                        i.issuer_id.clone(),
                        i.scheme_name.clone(),
                        i.exemption_requested,
                        i.exemption_accepted,
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(AuthEventMetricsBucketIdentifier, AuthEventMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// Struct: ConsultaOperacionesResponse
// File: crates/hyperswitch_connectors/src/connectors/redsys/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ConsultaOperacionesResponse


// Implementation: impl DashboardMetadataNew
// File: crates/diesel_models/src/query/dashboard_metadata.rs
// Module: diesel_models
// Methods: 1 total (0 public)

impl DashboardMetadataNew


// Implementation: impl GsmValidation for for PayoutData
// File: crates/router/src/core/payouts/retry.rs
// Module: router
// Methods: 1 total (0 public)

impl GsmValidation for for PayoutData


// Implementation: impl Responder
// File: crates/router/src/routes/cards_info.rs
// Module: router
// Methods: 0 total (0 public)

impl Responder


// Implementation: impl api::RefundExecute for for Dwolla
// File: crates/hyperswitch_connectors/src/connectors/dwolla.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::RefundExecute for for Dwolla


// Struct: RapydPaymentsResponse
// File: crates/hyperswitch_connectors/src/connectors/rapyd/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct RapydPaymentsResponse


// Implementation: impl IncomingWebhook for for Zsl
// File: crates/hyperswitch_connectors/src/connectors/zsl.rs
// Module: hyperswitch_connectors
// Methods: 5 total (0 public)

impl IncomingWebhook for for Zsl


// Implementation: impl api::ConnectorAccessToken for for Nexinets
// File: crates/hyperswitch_connectors/src/connectors/nexinets.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::ConnectorAccessToken for for Nexinets


// Struct: UasDynamicData
// File: crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct UasDynamicData


// Struct: AffirmRefundRequest
// File: crates/hyperswitch_connectors/src/connectors/affirm/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct AffirmRefundRequest


// Struct: MessageExtensionAttribute
// File: crates/hyperswitch_domain_models/src/router_request_types/authentication.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct MessageExtensionAttribute


// Struct: OpayoAuthType
// File: crates/hyperswitch_connectors/src/connectors/opayo/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct OpayoAuthType


// File: crates/router/src/db/blocklist_fingerprint.rs
// Module: router

use error_stack::report;
use router_env::{instrument, tracing};
use storage_impl::MockDb;

use super::Store;
use crate::{
    connection,
    core::errors::{self, CustomResult},
    db::kafka_store::KafkaStore,
    types::storage,
};

#[async_trait::async_trait]
pub trait BlocklistFingerprintInterface {
    async fn insert_blocklist_fingerprint_entry(
        &self,
        pm_fingerprint_new: storage::BlocklistFingerprintNew,
    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError>;

    async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        fingerprint_id: &str,
    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError>;
}

#[async_trait::async_trait]
impl BlocklistFingerprintInterface for Store {
    #[instrument(skip_all)]
    async fn insert_blocklist_fingerprint_entry(
        &self,
        pm_fingerprint_new: storage::BlocklistFingerprintNew,
    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        pm_fingerprint_new
            .insert(&conn)
            .await
            .map_err(|error| report!(errors::StorageError::from(error)))
    }

    #[instrument(skip_all)]
    async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        fingerprint_id: &str,
    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
        let conn = connection::pg_connection_write(self).await?;
        storage::BlocklistFingerprint::find_by_merchant_id_fingerprint_id(
            &conn,
            merchant_id,
            fingerprint_id,
        )
        .await
        .map_err(|error| report!(errors::StorageError::from(error)))
    }
}

#[async_trait::async_trait]
impl BlocklistFingerprintInterface for MockDb {
    async fn insert_blocklist_fingerprint_entry(
        &self,
        _pm_fingerprint_new: storage::BlocklistFingerprintNew,
    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
        Err(errors::StorageError::MockDbError)?
    }

    async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
        &self,
        _merchant_id: &common_utils::id_type::MerchantId,
        _fingerprint_id: &str,
    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
        Err(errors::StorageError::MockDbError)?
    }
}

#[async_trait::async_trait]
impl BlocklistFingerprintInterface for KafkaStore {
    #[instrument(skip_all)]
    async fn insert_blocklist_fingerprint_entry(
        &self,
        pm_fingerprint_new: storage::BlocklistFingerprintNew,
    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
        self.diesel_store
            .insert_blocklist_fingerprint_entry(pm_fingerprint_new)
            .await
    }

    #[instrument(skip_all)]
    async fn find_blocklist_fingerprint_by_merchant_id_fingerprint_id(
        &self,
        merchant_id: &common_utils::id_type::MerchantId,
        fingerprint: &str,
    ) -> CustomResult<storage::BlocklistFingerprint, errors::StorageError> {
        self.diesel_store
            .find_blocklist_fingerprint_by_merchant_id_fingerprint_id(merchant_id, fingerprint)
            .await
    }
}


// Implementation: impl ConnectorOnboarding
// File: crates/router/src/routes/app.rs
// Module: router
// Methods: 1 total (1 public)

impl ConnectorOnboarding


// Function: get_max_acs_protocol_version_if_available
// File: crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs
// Module: hyperswitch_connectors

pub fn get_max_acs_protocol_version_if_available(&self) -> Option<AcsProtocolVersion>


// Implementation: impl Payment for for Signifyd
// File: crates/hyperswitch_connectors/src/connectors/signifyd.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl Payment for for Signifyd


// Struct: ConsumerAuthenticationInformation
// File: crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ConsumerAuthenticationInformation


// Struct: TopN
// File: crates/analytics/src/query.rs
// Module: analytics
// Implementations: 0

pub struct TopN


// Struct: RazorpayErrorResponse
// File: crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct RazorpayErrorResponse


// Struct: RefundFlowData
// File: crates/hyperswitch_domain_models/src/router_data_v2/flow_common_types.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct RefundFlowData


// Function: toggle_blocklist_guard
// File: crates/router/src/routes/blocklist.rs
// Module: router

pub fn toggle_blocklist_guard(
    state: web::Data<AppState>,
    req: HttpRequest,
    query_payload: web::Query<api_blocklist::ToggleBlocklistQuery>,
) -> HttpResponse


// Implementation: impl api::PaymentVoid for for Phonepe
// File: crates/hyperswitch_connectors/src/connectors/phonepe.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentVoid for for Phonepe


// Implementation: impl ConnectorValidation for for Barclaycard
// File: crates/hyperswitch_connectors/src/connectors/barclaycard.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl ConnectorValidation for for Barclaycard


// Struct: ErrorDetails
// File: crates/hyperswitch_connectors/src/connectors/authorizedotnet/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ErrorDetails


// Function: find_resource
// File: crates/storage_impl/src/mock_db.rs
// Module: storage_impl

// Documentation: Returns an option of the resource if it exists

pub fn find_resource<D, R>(
        &self,
        state: &KeyManagerState,
        key_store: &MerchantKeyStore,
        resources: MutexGuard<'_, Vec<D>>,
        filter_fn: impl Fn(&&D) -> bool,
    ) -> CustomResult<Option<R>, StorageError>
    where
        D: Sync + ReverseConversion<R> + Clone,
        R: Conversion,


// Implementation: impl ResponsePaymentMethodIntermediate
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Methods: 1 total (1 public)

impl ResponsePaymentMethodIntermediate


// Module Structure
// File: crates/router/src/services/authorization.rs
// Module: router

// Public submodules:
pub mod info;
pub mod permission_groups;
pub mod permissions;
pub mod roles;


// Struct: RedisTokenManager
// File: crates/router/src/types/storage/revenue_recovery_redis_operation.rs
// Module: router
// Implementations: 1

// Documentation: Redis-based token management struct

pub struct RedisTokenManager


// Function: create_vault_token_core
// File: crates/router/src/core/tokenization.rs
// Module: router

pub fn create_vault_token_core(
    state: SessionState,
    merchant_account: &domain::MerchantAccount,
    merchant_key_store: &domain::MerchantKeyStore,
    req: api_models::tokenization::GenericTokenizationRequest,
) -> RouterResponse<api_models::tokenization::GenericTokenizationResponse>


// Struct: VerifyConnectorData
// File: crates/router/src/types/api/verify_connector.rs
// Module: router
// Implementations: 1

pub struct VerifyConnectorData


// Implementation: impl IncomingWebhook for for Forte
// File: crates/hyperswitch_connectors/src/connectors/forte.rs
// Module: hyperswitch_connectors
// Methods: 3 total (0 public)

impl IncomingWebhook for for Forte


// Function: check_if_already_exists_in_db
// File: crates/router/src/types/domain/user.rs
// Module: router

pub fn check_if_already_exists_in_db(&self, state: SessionState) -> UserResult<()>


// Struct: AuthServiceQueryParam
// File: crates/pm_auth/src/types.rs
// Module: pm_auth
// Implementations: 0

pub struct AuthServiceQueryParam


// Struct: TrustpayCreateIntentRequest
// File: crates/hyperswitch_connectors/src/connectors/trustpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct TrustpayCreateIntentRequest


// Struct: StraightThroughAlgorithmTypeSingle
// File: crates/router/src/core/routing.rs
// Module: router
// Implementations: 1
// Traits: GetRoutableConnectorsForChoice

pub struct StraightThroughAlgorithmTypeSingle


// Struct: PlacetopayAuth
// File: crates/hyperswitch_connectors/src/connectors/placetopay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PlacetopayAuth


// Struct: LineageContext
// File: crates/common_utils/src/types/user/core.rs
// Module: common_utils
// Implementations: 0

pub struct LineageContext


// Trait: ToResponse
// File: crates/router/src/core/payments/transformers.rs
// Module: router

pub trait ToResponse<F, D, Op>


// Struct: NetworkTokenMetaDataUpdateBody
// File: crates/router/src/types/payment_methods.rs
// Module: router
// Implementations: 0

pub struct NetworkTokenMetaDataUpdateBody


// Implementation: impl FromEncoded for for Vec
// File: crates/external_services/src/hashicorp_vault/core.rs
// Module: external_services
// Methods: 1 total (0 public)

impl FromEncoded for for Vec


// Implementation: impl KafkaRefund
// File: crates/router/src/services/kafka/refund.rs
// Module: router
// Methods: 1 total (1 public)

impl KafkaRefund


// Implementation: impl common_utils::events::ApiEventMetric for for GetTokenDataRequest
// File: crates/api_models/src/payment_methods.rs
// Module: api_models
// Methods: 0 total (0 public)

impl common_utils::events::ApiEventMetric for for GetTokenDataRequest


// Struct: Billing
// File: crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct Billing


// Implementation: impl api::PaymentCapture for for Hipay
// File: crates/hyperswitch_connectors/src/connectors/hipay.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentCapture for for Hipay


// Struct: SubscriptionItem
// File: crates/hyperswitch_domain_models/src/router_request_types/subscriptions.rs
// Module: hyperswitch_domain_models
// Implementations: 0

pub struct SubscriptionItem


// Implementation: impl api::PaymentCapture for for Affirm
// File: crates/hyperswitch_connectors/src/connectors/affirm.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentCapture for for Affirm


// File: crates/router/tests/connectors/bluesnap.rs
// Module: router

use std::str::FromStr;

use common_utils::pii::Email;
use hyperswitch_domain_models::address::{Address, AddressDetails};
use masking::Secret;
use router::types::{self, domain, storage::enums, ConnectorAuthType, PaymentAddress};

use crate::{
    connector_auth,
    utils::{self, ConnectorActions, PaymentInfo},
};

#[derive(Clone, Copy)]
struct BluesnapTest;
impl ConnectorActions for BluesnapTest {}
static CONNECTOR: BluesnapTest = BluesnapTest {};
impl utils::Connector for BluesnapTest {
    fn get_data(&self) -> types::api::ConnectorData {
        use router::connector::Bluesnap;
        utils::construct_connector_data_old(
            Box::new(Bluesnap::new()),
            types::Connector::Bluesnap,
            types::api::GetToken::Connector,
            None,
        )
    }

    fn get_auth_token(&self) -> ConnectorAuthType {
        utils::to_connector_auth_type(
            connector_auth::ConnectorAuthentication::new()
                .bluesnap
                .expect("Missing connector authentication configuration")
                .into(),
        )
    }

    fn get_name(&self) -> String {
        "bluesnap".to_string()
    }
}
fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
    Some(types::PaymentsAuthorizeData {
        email: Some(Email::from_str("test@gmail.com").unwrap()),
        ..utils::PaymentAuthorizeType::default().0
    })
}
fn get_payment_info() -> Option<PaymentInfo> {
    Some(PaymentInfo {
        address: Some(PaymentAddress::new(
            None,
            Some(Address {
                address: Some(AddressDetails {
                    first_name: Some(Secret::new("joseph".to_string())),
                    last_name: Some(Secret::new("Doe".to_string())),
                    ..Default::default()
                }),
                phone: None,
                email: None,
            }),
            None,
            None,
        )),
        ..Default::default()
    })
}

// Cards Positive Tests
// Creates a payment using the manual capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_only_authorize_payment() {
    let response = CONNECTOR
        .authorize_payment(payment_method_details(), get_payment_info())
        .await
        .expect("Authorize payment response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized);
}

// Captures a payment using the manual capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(payment_method_details(), None, get_payment_info())
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Partially captures a payment using the manual capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_partially_capture_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_capture_payment(
            payment_method_details(),
            Some(types::PaymentsCaptureData {
                amount_to_capture: 50,
                ..utils::PaymentCaptureType::default().0
            }),
            get_payment_info(),
        )
        .await
        .expect("Capture payment response");
    assert_eq!(response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the manual capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_sync_authorized_payment() {
    let authorize_response = CONNECTOR
        .authorize_payment(payment_method_details(), get_payment_info())
        .await
        .expect("Authorize payment response");
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Authorized,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                ..Default::default()
            }),
            None,
        )
        .await
        .expect("PSync response");
    assert_eq!(response.status, enums::AttemptStatus::Authorized,);
}

// Voids a payment using the manual capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_void_authorized_payment() {
    let response = CONNECTOR
        .authorize_and_void_payment(
            payment_method_details(),
            Some(types::PaymentsCancelData {
                connector_transaction_id: String::from(""),
                cancellation_reason: Some("requested_by_customer".to_string()),
                ..Default::default()
            }),
            get_payment_info(),
        )
        .await
        .expect("Void payment response");
    assert_eq!(response.status, enums::AttemptStatus::Voided);
}

// Refunds a payment using the manual capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(payment_method_details(), None, None, get_payment_info())
        .await
        .unwrap();
    let rsync_response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            response.response.unwrap().connector_refund_id,
            None,
            None,
        )
        .await
        .unwrap();
    assert_eq!(
        rsync_response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the manual capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_partially_refund_manually_captured_payment() {
    let response = CONNECTOR
        .capture_payment_and_refund(
            payment_method_details(),
            None,
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_payment_info(),
        )
        .await
        .unwrap();
    let rsync_response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            response.response.unwrap().connector_refund_id,
            None,
            None,
        )
        .await
        .unwrap();
    assert_eq!(
        rsync_response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Synchronizes a refund using the manual capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_sync_manually_captured_refund() {
    let refund_response = CONNECTOR
        .capture_payment_and_refund(payment_method_details(), None, None, get_payment_info())
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            None,
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates a payment using the automatic capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_make_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
}

// Synchronizes a payment using the automatic capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_sync_auto_captured_payment() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let response = CONNECTOR
        .psync_retry_till_status_matches(
            enums::AttemptStatus::Charged,
            Some(types::PaymentsSyncData {
                connector_transaction_id: types::ResponseId::ConnectorTransactionId(
                    txn_id.unwrap(),
                ),
                ..Default::default()
            }),
            None,
        )
        .await
        .unwrap();
    assert_eq!(response.status, enums::AttemptStatus::Charged,);
}

// Refunds a payment using the automatic capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_refund_auto_captured_payment() {
    let response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_payment_info())
        .await
        .unwrap();
    let rsync_response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            response.response.unwrap().connector_refund_id,
            None,
            None,
        )
        .await
        .unwrap();
    assert_eq!(
        rsync_response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Partially refunds a payment using the automatic capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_partially_refund_succeeded_payment() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 50,
                ..utils::PaymentRefundType::default().0
            }),
            get_payment_info(),
        )
        .await
        .unwrap();
    let rsync_response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            None,
        )
        .await
        .unwrap();
    assert_eq!(
        rsync_response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_refund_succeeded_payment_multiple_times() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let transaction_id = utils::get_connector_transaction_id(authorize_response.response).unwrap();
    for _x in 0..2 {
        tokio::time::sleep(std::time::Duration::from_secs(5)).await; // to avoid 404 error
        let refund_response = CONNECTOR
            .refund_payment(
                transaction_id.clone(),
                Some(types::RefundsData {
                    refund_amount: 50,
                    ..utils::PaymentRefundType::default().0
                }),
                None,
            )
            .await
            .unwrap();
        let rsync_response = CONNECTOR
            .rsync_retry_till_status_matches(
                enums::RefundStatus::Success,
                refund_response.response.unwrap().connector_refund_id,
                None,
                None,
            )
            .await
            .unwrap();
        assert_eq!(
            rsync_response.response.unwrap().refund_status,
            enums::RefundStatus::Success,
        );
    }
}

// Synchronizes a refund using the automatic capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_sync_refund() {
    let refund_response = CONNECTOR
        .make_payment_and_refund(payment_method_details(), None, get_payment_info())
        .await
        .unwrap();
    let response = CONNECTOR
        .rsync_retry_till_status_matches(
            enums::RefundStatus::Success,
            refund_response.response.unwrap().connector_refund_id,
            None,
            None,
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap().refund_status,
        enums::RefundStatus::Success,
    );
}

// Creates a payment with incorrect CVC.

#[serial_test::serial]
#[actix_web::test]
async fn should_fail_payment_for_incorrect_cvc() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                email: Some(Email::from_str("test@gmail.com").unwrap()),
                payment_method_data: domain::PaymentMethodData::Card(domain::Card {
                    card_cvc: Secret::new("12345".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "VALIDATION_GENERAL_FAILURE".to_string(),
    );
}

// Creates a payment with incorrect expiry month.

#[serial_test::serial]
#[actix_web::test]
async fn should_fail_payment_for_invalid_exp_month() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                email: Some(Email::from_str("test@gmail.com").unwrap()),
                payment_method_data: domain::PaymentMethodData::Card(domain::Card {
                    card_exp_month: Secret::new("20".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "VALIDATION_GENERAL_FAILURE".to_string(),
    );
}

// Creates a payment with incorrect expiry year.

#[serial_test::serial]
#[actix_web::test]
async fn should_fail_payment_for_incorrect_expiry_year() {
    let response = CONNECTOR
        .make_payment(
            Some(types::PaymentsAuthorizeData {
                email: Some(Email::from_str("test@gmail.com").unwrap()),
                payment_method_data: domain::PaymentMethodData::Card(domain::Card {
                    card_exp_year: Secret::new("2000".to_string()),
                    ..utils::CCardType::default().0
                }),
                ..utils::PaymentAuthorizeType::default().0
            }),
            get_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "VALIDATION_GENERAL_FAILURE".to_string(),
    );
}

// Voids a payment using automatic capture flow (Non 3DS).

#[serial_test::serial]
#[actix_web::test]
async fn should_fail_void_payment_for_auto_capture() {
    let authorize_response = CONNECTOR
        .make_payment(payment_method_details(), get_payment_info())
        .await
        .unwrap();
    assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
    let txn_id = utils::get_connector_transaction_id(authorize_response.response);
    assert_ne!(txn_id, None, "Empty connector transaction id");
    let void_response = CONNECTOR
        .void_payment(txn_id.unwrap(), None, None)
        .await
        .unwrap();
    assert_eq!(
        void_response.response.unwrap_err().message,
        "TRANSACTION_ALREADY_CAPTURED"
    );
}

// Captures a payment using invalid connector payment id.

#[serial_test::serial]
#[actix_web::test]
async fn should_fail_capture_for_invalid_payment() {
    let capture_response = CONNECTOR
        .capture_payment("123456789".to_string(), None, None)
        .await
        .unwrap();

    capture_response
        .response
        .unwrap_err()
        .message
        .contains("is not authorized to view transaction");
}

// Refunds a payment with refund amount higher than payment amount.

#[serial_test::serial]
#[actix_web::test]
async fn should_fail_for_refund_amount_higher_than_payment_amount() {
    let response = CONNECTOR
        .make_payment_and_refund(
            payment_method_details(),
            Some(types::RefundsData {
                refund_amount: 150,
                ..utils::PaymentRefundType::default().0
            }),
            get_payment_info(),
        )
        .await
        .unwrap();
    assert_eq!(
        response.response.unwrap_err().message,
        "REFUND_MAX_AMOUNT_FAILURE",
    );
}

// Connector dependent test cases goes here

// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests


// Function: create_invoice_record_back_job
// File: crates/router/src/core/subscription.rs
// Module: router

pub fn create_invoice_record_back_job(
        &self,
        // _invoice: &subscription_types::Invoice,
        _payment_response: &subscription_types::PaymentResponseData,
    ) -> errors::RouterResult<()>


// Struct: Shift4WebhookObjectEventType
// File: crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct Shift4WebhookObjectEventType


// Function: update_by_customer_id_merchant_id
// File: crates/diesel_models/src/query/customers.rs
// Module: diesel_models

pub fn update_by_customer_id_merchant_id(
        conn: &PgPooledConn,
        customer_id: id_type::CustomerId,
        merchant_id: id_type::MerchantId,
        customer: CustomerUpdateInternal,
    ) -> StorageResult<Self>


// Implementation: impl api::ConnectorAccessToken for for Vgs
// File: crates/hyperswitch_connectors/src/connectors/vgs.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::ConnectorAccessToken for for Vgs


// Implementation: impl Responder
// File: crates/router/src/routes/api_keys.rs
// Module: router
// Methods: 0 total (0 public)

impl Responder


// Implementation: impl RoutingDecisionData
// File: crates/router/src/core/routing/helpers.rs
// Module: router
// Methods: 2 total (2 public)

impl RoutingDecisionData


// Struct: PayeezyRefundRequest
// File: crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct PayeezyRefundRequest


// Function: payments_create_and_confirm_intent
// File: crates/openapi/src/routes/payments.rs
// Module: openapi

pub fn payments_create_and_confirm_intent()


// Implementation: impl KeyManagerState
// File: crates/common_utils/src/types/keymanager.rs
// Module: common_utils
// Methods: 1 total (1 public)

impl KeyManagerState


// Struct: NuveiPartialApproval
// File: crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct NuveiPartialApproval


// Implementation: impl Responder
// File: crates/router/src/routes/verification.rs
// Module: router
// Methods: 0 total (0 public)

impl Responder


// Implementation: impl api::RefundSync for for Worldpay
// File: crates/hyperswitch_connectors/src/connectors/worldpay.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::RefundSync for for Worldpay


// File: crates/hyperswitch_constraint_graph/src/graph.rs
// Module: hyperswitch_constraint_graph
// Public functions: 5
// Public structs: 1

use std::sync::{Arc, Weak};

use rustc_hash::{FxHashMap, FxHashSet};

use crate::{
    builder,
    dense_map::DenseMap,
    error::{self, AnalysisTrace, GraphError},
    types::{
        CheckingContext, CycleCheck, DomainId, DomainIdentifier, DomainInfo, Edge, EdgeId,
        Memoization, Metadata, Node, NodeId, NodeType, NodeValue, Relation, RelationResolution,
        Strength, ValueNode,
    },
};

#[derive(Debug)]
struct CheckNodeContext<'a, V: ValueNode, C: CheckingContext<Value = V>> {
    ctx: &'a C,
    node: &'a Node<V>,
    node_id: NodeId,
    relation: Relation,
    strength: Strength,
    memo: &'a mut Memoization<V>,
    cycle_map: &'a mut CycleCheck,
    domains: Option<&'a [DomainId]>,
}

#[derive(Debug)]
pub struct ConstraintGraph<V: ValueNode> {
    pub domain: DenseMap<DomainId, DomainInfo>,
    pub domain_identifier_map: FxHashMap<DomainIdentifier, DomainId>,
    pub nodes: DenseMap<NodeId, Node<V>>,
    pub edges: DenseMap<EdgeId, Edge>,
    pub value_map: FxHashMap<NodeValue<V>, NodeId>,
    pub node_info: DenseMap<NodeId, Option<&'static str>>,
    pub node_metadata: DenseMap<NodeId, Option<Arc<dyn Metadata>>>,
}

impl<V> ConstraintGraph<V>
where
    V: ValueNode,
{
    fn get_predecessor_edges_by_domain(
        &self,
        node_id: NodeId,
        domains: Option<&[DomainId]>,
    ) -> Result<Vec<&Edge>, GraphError<V>> {
        let node = self.nodes.get(node_id).ok_or(GraphError::NodeNotFound)?;
        let mut final_list = Vec::new();
        for &pred in &node.preds {
            let edge = self.edges.get(pred).ok_or(GraphError::EdgeNotFound)?;
            if let Some((domain_id, domains)) = edge.domain.zip(domains) {
                if domains.contains(&domain_id) {
                    final_list.push(edge);
                }
            } else if edge.domain.is_none() {
                final_list.push(edge);
            }
        }

        Ok(final_list)
    }

    #[allow(clippy::too_many_arguments)]
    pub fn check_node<C>(
        &self,
        ctx: &C,
        node_id: NodeId,
        relation: Relation,
        strength: Strength,
        memo: &mut Memoization<V>,
        cycle_map: &mut CycleCheck,
        domains: Option<&[String]>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let domains = domains
            .map(|domain_idents| {
                domain_idents
                    .iter()
                    .map(|domain_ident| {
                        self.domain_identifier_map
                            .get(&DomainIdentifier::new(domain_ident.to_string()))
                            .copied()
                            .ok_or(GraphError::DomainNotFound)
                    })
                    .collect::<Result<Vec<_>, _>>()
            })
            .transpose()?;

        self.check_node_inner(
            ctx,
            node_id,
            relation,
            strength,
            memo,
            cycle_map,
            domains.as_deref(),
        )
    }

    #[allow(clippy::too_many_arguments)]
    pub fn check_node_inner<C>(
        &self,
        ctx: &C,
        node_id: NodeId,
        relation: Relation,
        strength: Strength,
        memo: &mut Memoization<V>,
        cycle_map: &mut CycleCheck,
        domains: Option<&[DomainId]>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let node = self.nodes.get(node_id).ok_or(GraphError::NodeNotFound)?;

        if let Some(already_memo) = memo.get(&(node_id, relation, strength)) {
            already_memo
                .clone()
                .map_err(|err| GraphError::AnalysisError(Arc::downgrade(&err)))
        } else if let Some((initial_strength, initial_relation)) = cycle_map.get(&node_id).copied()
        {
            let strength_relation = Strength::get_resolved_strength(initial_strength, strength);
            let relation_resolve =
                RelationResolution::get_resolved_relation(initial_relation, relation.into());
            cycle_map.entry(node_id).and_modify(|value| {
                value.0 = strength_relation;
                value.1 = relation_resolve
            });
            Ok(())
        } else {
            let check_node_context = CheckNodeContext {
                node,
                node_id,
                relation,
                strength,
                memo,
                cycle_map,
                ctx,
                domains,
            };

            match &node.node_type {
                NodeType::AllAggregator => self.validate_all_aggregator(check_node_context),

                NodeType::AnyAggregator => self.validate_any_aggregator(check_node_context),

                NodeType::InAggregator(expected) => {
                    self.validate_in_aggregator(check_node_context, expected)
                }
                NodeType::Value(val) => self.validate_value_node(check_node_context, val),
            }
        }
    }

    fn validate_all_aggregator<C>(
        &self,
        vald: CheckNodeContext<'_, V, C>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let mut unsatisfied = Vec::<Weak<AnalysisTrace<V>>>::new();

        for edge in self.get_predecessor_edges_by_domain(vald.node_id, vald.domains)? {
            vald.cycle_map
                .insert(vald.node_id, (vald.strength, vald.relation.into()));
            if let Err(e) = self.check_node_inner(
                vald.ctx,
                edge.pred,
                edge.relation,
                edge.strength,
                vald.memo,
                vald.cycle_map,
                vald.domains,
            ) {
                unsatisfied.push(e.get_analysis_trace()?);
            }
            if let Some((_resolved_strength, resolved_relation)) =
                vald.cycle_map.remove(&vald.node_id)
            {
                if resolved_relation == RelationResolution::Contradiction {
                    let err = Arc::new(AnalysisTrace::Contradiction {
                        relation: resolved_relation,
                    });
                    vald.memo.insert(
                        (vald.node_id, vald.relation, vald.strength),
                        Err(Arc::clone(&err)),
                    );
                    return Err(GraphError::AnalysisError(Arc::downgrade(&err)));
                }
            }
        }

        if !unsatisfied.is_empty() {
            let err = Arc::new(AnalysisTrace::AllAggregation {
                unsatisfied,
                info: self.node_info.get(vald.node_id).copied().flatten(),
                metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
            });

            vald.memo.insert(
                (vald.node_id, vald.relation, vald.strength),
                Err(Arc::clone(&err)),
            );
            Err(GraphError::AnalysisError(Arc::downgrade(&err)))
        } else {
            vald.memo
                .insert((vald.node_id, vald.relation, vald.strength), Ok(()));

            Ok(())
        }
    }

    fn validate_any_aggregator<C>(
        &self,
        vald: CheckNodeContext<'_, V, C>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let mut unsatisfied = Vec::<Weak<AnalysisTrace<V>>>::new();
        let mut matched_one = false;

        for edge in self.get_predecessor_edges_by_domain(vald.node_id, vald.domains)? {
            vald.cycle_map
                .insert(vald.node_id, (vald.strength, vald.relation.into()));
            if let Err(e) = self.check_node_inner(
                vald.ctx,
                edge.pred,
                edge.relation,
                edge.strength,
                vald.memo,
                vald.cycle_map,
                vald.domains,
            ) {
                unsatisfied.push(e.get_analysis_trace()?);
            } else {
                matched_one = true;
            }
            if let Some((_resolved_strength, resolved_relation)) =
                vald.cycle_map.remove(&vald.node_id)
            {
                if resolved_relation == RelationResolution::Contradiction {
                    let err = Arc::new(AnalysisTrace::Contradiction {
                        relation: resolved_relation,
                    });
                    vald.memo.insert(
                        (vald.node_id, vald.relation, vald.strength),
                        Err(Arc::clone(&err)),
                    );

                    return Err(GraphError::AnalysisError(Arc::downgrade(&err)));
                }
            }
        }

        if matched_one || vald.node.preds.is_empty() {
            vald.memo
                .insert((vald.node_id, vald.relation, vald.strength), Ok(()));
            Ok(())
        } else {
            let err = Arc::new(AnalysisTrace::AnyAggregation {
                unsatisfied: unsatisfied.clone(),
                info: self.node_info.get(vald.node_id).copied().flatten(),
                metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
            });

            vald.memo.insert(
                (vald.node_id, vald.relation, vald.strength),
                Err(Arc::clone(&err)),
            );
            Err(GraphError::AnalysisError(Arc::downgrade(&err)))
        }
    }

    fn validate_in_aggregator<C>(
        &self,
        vald: CheckNodeContext<'_, V, C>,
        expected: &FxHashSet<V>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let the_key = expected
            .iter()
            .next()
            .ok_or_else(|| GraphError::MalformedGraph {
                reason: "An OnlyIn aggregator node must have at least one expected value"
                    .to_string(),
            })?
            .get_key();

        let ctx_vals = if let Some(vals) = vald.ctx.get_values_by_key(&the_key) {
            vals
        } else {
            return if let Strength::Weak = vald.strength {
                vald.memo
                    .insert((vald.node_id, vald.relation, vald.strength), Ok(()));
                Ok(())
            } else {
                let err = Arc::new(AnalysisTrace::InAggregation {
                    expected: expected.iter().cloned().collect(),
                    found: None,
                    relation: vald.relation,
                    info: self.node_info.get(vald.node_id).copied().flatten(),
                    metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
                });

                vald.memo.insert(
                    (vald.node_id, vald.relation, vald.strength),
                    Err(Arc::clone(&err)),
                );
                Err(GraphError::AnalysisError(Arc::downgrade(&err)))
            };
        };

        let relation_bool: bool = vald.relation.into();
        for ctx_value in ctx_vals {
            if expected.contains(&ctx_value) != relation_bool {
                let err = Arc::new(AnalysisTrace::InAggregation {
                    expected: expected.iter().cloned().collect(),
                    found: Some(ctx_value.clone()),
                    relation: vald.relation,
                    info: self.node_info.get(vald.node_id).copied().flatten(),
                    metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
                });

                vald.memo.insert(
                    (vald.node_id, vald.relation, vald.strength),
                    Err(Arc::clone(&err)),
                );
                Err(GraphError::AnalysisError(Arc::downgrade(&err)))?;
            }
        }

        vald.memo
            .insert((vald.node_id, vald.relation, vald.strength), Ok(()));
        Ok(())
    }

    fn validate_value_node<C>(
        &self,
        vald: CheckNodeContext<'_, V, C>,
        val: &NodeValue<V>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let mut errors = Vec::<Weak<AnalysisTrace<V>>>::new();
        let mut matched_one = false;

        self.context_analysis(
            vald.node_id,
            vald.relation,
            vald.strength,
            vald.ctx,
            val,
            vald.memo,
        )?;

        for edge in self.get_predecessor_edges_by_domain(vald.node_id, vald.domains)? {
            vald.cycle_map
                .insert(vald.node_id, (vald.strength, vald.relation.into()));
            let result = self.check_node_inner(
                vald.ctx,
                edge.pred,
                edge.relation,
                edge.strength,
                vald.memo,
                vald.cycle_map,
                vald.domains,
            );

            if let Some((resolved_strength, resolved_relation)) =
                vald.cycle_map.remove(&vald.node_id)
            {
                if resolved_relation == RelationResolution::Contradiction {
                    let err = Arc::new(AnalysisTrace::Contradiction {
                        relation: resolved_relation,
                    });
                    vald.memo.insert(
                        (vald.node_id, vald.relation, vald.strength),
                        Err(Arc::clone(&err)),
                    );
                    return Err(GraphError::AnalysisError(Arc::downgrade(&err)));
                } else if resolved_strength != vald.strength {
                    self.context_analysis(
                        vald.node_id,
                        vald.relation,
                        resolved_strength,
                        vald.ctx,
                        val,
                        vald.memo,
                    )?
                }
            }
            match (edge.strength, result) {
                (Strength::Strong, Err(trace)) => {
                    let err = Arc::new(AnalysisTrace::Value {
                        value: val.clone(),
                        relation: vald.relation,
                        info: self.node_info.get(vald.node_id).copied().flatten(),
                        metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
                        predecessors: Some(error::ValueTracePredecessor::Mandatory(Box::new(
                            trace.get_analysis_trace()?,
                        ))),
                    });
                    vald.memo.insert(
                        (vald.node_id, vald.relation, vald.strength),
                        Err(Arc::clone(&err)),
                    );
                    Err(GraphError::AnalysisError(Arc::downgrade(&err)))?;
                }

                (Strength::Strong, Ok(_)) => {
                    matched_one = true;
                }

                (Strength::Normal | Strength::Weak, Err(trace)) => {
                    errors.push(trace.get_analysis_trace()?);
                }

                (Strength::Normal | Strength::Weak, Ok(_)) => {
                    matched_one = true;
                }
            }
        }

        if matched_one || vald.node.preds.is_empty() {
            vald.memo
                .insert((vald.node_id, vald.relation, vald.strength), Ok(()));
            Ok(())
        } else {
            let err = Arc::new(AnalysisTrace::Value {
                value: val.clone(),
                relation: vald.relation,
                info: self.node_info.get(vald.node_id).copied().flatten(),
                metadata: self.node_metadata.get(vald.node_id).cloned().flatten(),
                predecessors: Some(error::ValueTracePredecessor::OneOf(errors.clone())),
            });

            vald.memo.insert(
                (vald.node_id, vald.relation, vald.strength),
                Err(Arc::clone(&err)),
            );
            Err(GraphError::AnalysisError(Arc::downgrade(&err)))
        }
    }

    fn context_analysis<C>(
        &self,
        node_id: NodeId,
        relation: Relation,
        strength: Strength,
        ctx: &C,
        val: &NodeValue<V>,
        memo: &mut Memoization<V>,
    ) -> Result<(), GraphError<V>>
    where
        C: CheckingContext<Value = V>,
    {
        let in_context = ctx.check_presence(val, strength);
        let relation_bool: bool = relation.into();
        if in_context != relation_bool {
            let err = Arc::new(AnalysisTrace::Value {
                value: val.clone(),
                relation,
                predecessors: None,
                info: self.node_info.get(node_id).copied().flatten(),
                metadata: self.node_metadata.get(node_id).cloned().flatten(),
            });
            memo.insert((node_id, relation, strength), Err(Arc::clone(&err)));
            Err(GraphError::AnalysisError(Arc::downgrade(&err)))?;
        }
        if !relation_bool {
            memo.insert((node_id, relation, strength), Ok(()));
            return Ok(());
        }
        Ok(())
    }

    pub fn combine(g1: &Self, g2: &Self) -> Result<Self, GraphError<V>> {
        let mut node_builder = builder::ConstraintGraphBuilder::new();
        let mut g1_old2new_id = DenseMap::<NodeId, NodeId>::new();
        let mut g2_old2new_id = DenseMap::<NodeId, NodeId>::new();
        let mut g1_old2new_domain_id = DenseMap::<DomainId, DomainId>::new();
        let mut g2_old2new_domain_id = DenseMap::<DomainId, DomainId>::new();

        let add_domain = |node_builder: &mut builder::ConstraintGraphBuilder<V>,
                          domain: DomainInfo|
         -> Result<DomainId, GraphError<V>> {
            node_builder.make_domain(
                domain.domain_identifier.into_inner(),
                &domain.domain_description,
            )
        };

        let add_node = |node_builder: &mut builder::ConstraintGraphBuilder<V>,
                        node: &Node<V>|
         -> Result<NodeId, GraphError<V>> {
            match &node.node_type {
                NodeType::Value(node_value) => {
                    Ok(node_builder.make_value_node(node_value.clone(), None, None::<()>))
                }

                NodeType::AllAggregator => {
                    Ok(node_builder.make_all_aggregator(&[], None, None::<()>, None)?)
                }

                NodeType::AnyAggregator => {
                    Ok(node_builder.make_any_aggregator(&[], None, None::<()>, None)?)
                }

                NodeType::InAggregator(expected) => Ok(node_builder.make_in_aggregator(
                    expected.iter().cloned().collect(),
                    None,
                    None::<()>,
                )?),
            }
        };

        for (_old_domain_id, domain) in g1.domain.iter() {
            let new_domain_id = add_domain(&mut node_builder, domain.clone())?;
            g1_old2new_domain_id.push(new_domain_id);
        }

        for (_old_domain_id, domain) in g2.domain.iter() {
            let new_domain_id = add_domain(&mut node_builder, domain.clone())?;
            g2_old2new_domain_id.push(new_domain_id);
        }

        for (_old_node_id, node) in g1.nodes.iter() {
            let new_node_id = add_node(&mut node_builder, node)?;
            g1_old2new_id.push(new_node_id);
        }

        for (_old_node_id, node) in g2.nodes.iter() {
            let new_node_id = add_node(&mut node_builder, node)?;
            g2_old2new_id.push(new_node_id);
        }

        for edge in g1.edges.values() {
            let new_pred_id = g1_old2new_id
                .get(edge.pred)
                .ok_or(GraphError::NodeNotFound)?;
            let new_succ_id = g1_old2new_id
                .get(edge.succ)
                .ok_or(GraphError::NodeNotFound)?;
            let domain_ident = edge
                .domain
                .map(|domain_id| g1.domain.get(domain_id).ok_or(GraphError::DomainNotFound))
                .transpose()?
                .map(|domain| domain.domain_identifier.clone());

            node_builder.make_edge(
                *new_pred_id,
                *new_succ_id,
                edge.strength,
                edge.relation,
                domain_ident,
            )?;
        }

        for edge in g2.edges.values() {
            let new_pred_id = g2_old2new_id
                .get(edge.pred)
                .ok_or(GraphError::NodeNotFound)?;
            let new_succ_id = g2_old2new_id
                .get(edge.succ)
                .ok_or(GraphError::NodeNotFound)?;
            let domain_ident = edge
                .domain
                .map(|domain_id| g2.domain.get(domain_id).ok_or(GraphError::DomainNotFound))
                .transpose()?
                .map(|domain| domain.domain_identifier.clone());

            node_builder.make_edge(
                *new_pred_id,
                *new_succ_id,
                edge.strength,
                edge.relation,
                domain_ident,
            )?;
        }

        Ok(node_builder.build())
    }
}

#[cfg(feature = "viz")]
mod viz {
    use graphviz_rust::{
        dot_generator::*,
        dot_structures::*,
        printer::{DotPrinter, PrinterContext},
    };

    use crate::{dense_map::EntityId, types, ConstraintGraph, NodeViz, ValueNode};

    fn get_node_id(node_id: types::NodeId) -> String {
        format!("N{}", node_id.get_id())
    }

    impl<V> ConstraintGraph<V>
    where
        V: ValueNode + NodeViz,
        <V as ValueNode>::Key: NodeViz,
    {
        fn get_node_label(node: &types::Node<V>) -> String {
            let label = match &node.node_type {
                types::NodeType::Value(types::NodeValue::Key(key)) => format!("any {}", key.viz()),
                types::NodeType::Value(types::NodeValue::Value(val)) => {
                    format!("{} = {}", val.get_key().viz(), val.viz())
                }
                types::NodeType::AllAggregator => "&&".to_string(),
                types::NodeType::AnyAggregator => "| |".to_string(),
                types::NodeType::InAggregator(agg) => {
                    let key = if let Some(val) = agg.iter().next() {
                        val.get_key().viz()
                    } else {
                        return "empty in".to_string();
                    };

                    let nodes = agg.iter().map(NodeViz::viz).collect::<Vec<_>>();
                    format!("{key} in [{}]", nodes.join(", "))
                }
            };

            format!("\"{label}\"")
        }

        fn build_node(cg_node_id: types::NodeId, cg_node: &types::Node<V>) -> Node {
            let viz_node_id = get_node_id(cg_node_id);
            let viz_node_label = Self::get_node_label(cg_node);

            node!(viz_node_id; attr!("label", viz_node_label))
        }

        fn build_edge(cg_edge: &types::Edge) -> Edge {
            let pred_vertex = get_node_id(cg_edge.pred);
            let succ_vertex = get_node_id(cg_edge.succ);
            let arrowhead = match cg_edge.strength {
                types::Strength::Weak => "onormal",
                types::Strength::Normal => "normal",
                types::Strength::Strong => "normalnormal",
            };
            let color = match cg_edge.relation {
                types::Relation::Positive => "blue",
                types::Relation::Negative => "red",
            };

            edge!(
                node_id!(pred_vertex) => node_id!(succ_vertex);
                attr!("arrowhead", arrowhead),
                attr!("color", color)
            )
        }

        pub fn get_viz_digraph(&self) -> Graph {
            graph!(
                strict di id!("constraint_graph"),
                self.nodes
                    .iter()
                    .map(|(node_id, node)| Self::build_node(node_id, node))
                    .map(Stmt::Node)
                    .chain(self.edges.values().map(Self::build_edge).map(Stmt::Edge))
                    .collect::<Vec<_>>()
            )
        }

        pub fn get_viz_digraph_string(&self) -> String {
            let mut ctx = PrinterContext::default();
            let digraph = self.get_viz_digraph();
            digraph.print(&mut ctx)
        }
    }
}