// File: crates/redis_interface/src/commands.rs
// Module: redis_interface
// Public functions: 51

//! An interface to abstract the `fred` commands
//!
//! The folder provides generic functions for providing serialization
//! and deserialization while calling redis.
//! It also includes instruments to provide tracing.

use std::fmt::Debug;

use common_utils::{
    errors::CustomResult,
    ext_traits::{AsyncExt, ByteSliceExt, Encode, StringExt},
    fp_utils,
};
use error_stack::{report, ResultExt};
use fred::{
    interfaces::{HashesInterface, KeysInterface, ListInterface, SetsInterface, StreamsInterface},
    prelude::{LuaInterface, RedisErrorKind},
    types::{
        Expiration, FromRedis, MultipleIDs, MultipleKeys, MultipleOrderedPairs, MultipleStrings,
        MultipleValues, RedisMap, RedisValue, ScanType, Scanner, SetOptions, XCap, XReadResponse,
    },
};
use futures::StreamExt;
use tracing::instrument;

use crate::{
    errors,
    types::{
        DelReply, HsetnxReply, MsetnxReply, RedisEntryId, RedisKey, SaddReply, SetGetReply,
        SetnxReply,
    },
};

impl super::RedisConnectionPool {
    pub fn add_prefix(&self, key: &str) -> String {
        if self.key_prefix.is_empty() {
            key.to_string()
        } else {
            format!("{}:{}", self.key_prefix, key)
        }
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn set_key<V>(&self, key: &RedisKey, value: V) -> CustomResult<(), errors::RedisError>
    where
        V: TryInto<RedisValue> + Debug + Send + Sync,
        V::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        self.pool
            .set(
                key.tenant_aware_key(self),
                value,
                Some(Expiration::EX(self.config.default_ttl.into())),
                None,
                false,
            )
            .await
            .change_context(errors::RedisError::SetFailed)
    }

    pub async fn set_key_without_modifying_ttl<V>(
        &self,
        key: &RedisKey,
        value: V,
    ) -> CustomResult<(), errors::RedisError>
    where
        V: TryInto<RedisValue> + Debug + Send + Sync,
        V::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        self.pool
            .set(
                key.tenant_aware_key(self),
                value,
                Some(Expiration::KEEPTTL),
                None,
                false,
            )
            .await
            .change_context(errors::RedisError::SetFailed)
    }

    pub async fn set_multiple_keys_if_not_exist<V>(
        &self,
        value: V,
    ) -> CustomResult<MsetnxReply, errors::RedisError>
    where
        V: TryInto<RedisMap> + Debug + Send + Sync,
        V::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        self.pool
            .msetnx(value)
            .await
            .change_context(errors::RedisError::SetFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn serialize_and_set_key_if_not_exist<V>(
        &self,
        key: &RedisKey,
        value: V,
        ttl: Option<i64>,
    ) -> CustomResult<SetnxReply, errors::RedisError>
    where
        V: serde::Serialize + Debug,
    {
        let serialized = value
            .encode_to_vec()
            .change_context(errors::RedisError::JsonSerializationFailed)?;
        self.set_key_if_not_exists_with_expiry(key, serialized.as_slice(), ttl)
            .await
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn serialize_and_set_key<V>(
        &self,
        key: &RedisKey,
        value: V,
    ) -> CustomResult<(), errors::RedisError>
    where
        V: serde::Serialize + Debug,
    {
        let serialized = value
            .encode_to_vec()
            .change_context(errors::RedisError::JsonSerializationFailed)?;

        self.set_key(key, serialized.as_slice()).await
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn serialize_and_set_key_without_modifying_ttl<V>(
        &self,
        key: &RedisKey,
        value: V,
    ) -> CustomResult<(), errors::RedisError>
    where
        V: serde::Serialize + Debug,
    {
        let serialized = value
            .encode_to_vec()
            .change_context(errors::RedisError::JsonSerializationFailed)?;

        self.set_key_without_modifying_ttl(key, serialized.as_slice())
            .await
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn serialize_and_set_key_with_expiry<V>(
        &self,
        key: &RedisKey,
        value: V,
        seconds: i64,
    ) -> CustomResult<(), errors::RedisError>
    where
        V: serde::Serialize + Debug,
    {
        let serialized = value
            .encode_to_vec()
            .change_context(errors::RedisError::JsonSerializationFailed)?;

        self.pool
            .set(
                key.tenant_aware_key(self),
                serialized.as_slice(),
                Some(Expiration::EX(seconds)),
                None,
                false,
            )
            .await
            .change_context(errors::RedisError::SetExFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn get_key<V>(&self, key: &RedisKey) -> CustomResult<V, errors::RedisError>
    where
        V: FromRedis + Unpin + Send + 'static,
    {
        match self
            .pool
            .get(key.tenant_aware_key(self))
            .await
            .change_context(errors::RedisError::GetFailed)
        {
            Ok(v) => Ok(v),
            Err(_err) => {
                #[cfg(not(feature = "multitenancy_fallback"))]
                {
                    Err(_err)
                }

                #[cfg(feature = "multitenancy_fallback")]
                {
                    self.pool
                        .get(key.tenant_unaware_key(self))
                        .await
                        .change_context(errors::RedisError::GetFailed)
                }
            }
        }
    }

    #[instrument(level = "DEBUG", skip(self))]
    async fn get_multiple_keys_with_mget<V>(
        &self,
        keys: &[RedisKey],
    ) -> CustomResult<Vec<Option<V>>, errors::RedisError>
    where
        V: FromRedis + Unpin + Send + 'static,
    {
        if keys.is_empty() {
            return Ok(Vec::new());
        }

        let tenant_aware_keys: Vec<String> =
            keys.iter().map(|key| key.tenant_aware_key(self)).collect();
        self.pool
            .mget(tenant_aware_keys)
            .await
            .change_context(errors::RedisError::GetFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    async fn get_multiple_keys_with_parallel_get<V>(
        &self,
        keys: &[RedisKey],
    ) -> CustomResult<Vec<Option<V>>, errors::RedisError>
    where
        V: FromRedis + Unpin + Send + 'static,
    {
        if keys.is_empty() {
            return Ok(Vec::new());
        }
        let tenant_aware_keys: Vec<String> =
            keys.iter().map(|key| key.tenant_aware_key(self)).collect();

        let futures = tenant_aware_keys
            .iter()
            .map(|redis_key| self.pool.get::<Option<V>, _>(redis_key));

        let results = futures::future::try_join_all(futures)
            .await
            .change_context(errors::RedisError::GetFailed)
            .attach_printable("Failed to get keys in cluster mode")?;

        Ok(results)
    }

    /// Helper method to encapsulate the logic for choosing between cluster and non-cluster modes
    #[instrument(level = "DEBUG", skip(self))]
    async fn get_keys_by_mode<V>(
        &self,
        keys: &[RedisKey],
    ) -> CustomResult<Vec<Option<V>>, errors::RedisError>
    where
        V: FromRedis + Unpin + Send + 'static,
    {
        if self.config.cluster_enabled {
            // Use individual GET commands for cluster mode to avoid CROSSSLOT errors
            self.get_multiple_keys_with_parallel_get(keys).await
        } else {
            // Use MGET for non-cluster mode for better performance
            self.get_multiple_keys_with_mget(keys).await
        }
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn get_multiple_keys<V>(
        &self,
        keys: &[RedisKey],
    ) -> CustomResult<Vec<Option<V>>, errors::RedisError>
    where
        V: FromRedis + Unpin + Send + 'static,
    {
        if keys.is_empty() {
            return Ok(Vec::new());
        }

        match self.get_keys_by_mode(keys).await {
            Ok(values) => Ok(values),
            Err(_err) => {
                #[cfg(not(feature = "multitenancy_fallback"))]
                {
                    Err(_err)
                }

                #[cfg(feature = "multitenancy_fallback")]
                {
                    let tenant_unaware_keys: Vec<RedisKey> = keys
                        .iter()
                        .map(|key| key.tenant_unaware_key(self).into())
                        .collect();

                    self.get_keys_by_mode(&tenant_unaware_keys).await
                }
            }
        }
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn exists<V>(&self, key: &RedisKey) -> CustomResult<bool, errors::RedisError>
    where
        V: Into<MultipleKeys> + Unpin + Send + 'static,
    {
        match self
            .pool
            .exists(key.tenant_aware_key(self))
            .await
            .change_context(errors::RedisError::GetFailed)
        {
            Ok(v) => Ok(v),
            Err(_err) => {
                #[cfg(not(feature = "multitenancy_fallback"))]
                {
                    Err(_err)
                }

                #[cfg(feature = "multitenancy_fallback")]
                {
                    self.pool
                        .exists(key.tenant_unaware_key(self))
                        .await
                        .change_context(errors::RedisError::GetFailed)
                }
            }
        }
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn get_and_deserialize_key<T>(
        &self,
        key: &RedisKey,
        type_name: &'static str,
    ) -> CustomResult<T, errors::RedisError>
    where
        T: serde::de::DeserializeOwned,
    {
        let value_bytes = self.get_key::<Vec<u8>>(key).await?;

        fp_utils::when(value_bytes.is_empty(), || Err(errors::RedisError::NotFound))?;

        value_bytes
            .parse_struct(type_name)
            .change_context(errors::RedisError::JsonDeserializationFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn get_and_deserialize_multiple_keys<T>(
        &self,
        keys: &[RedisKey],
        type_name: &'static str,
    ) -> CustomResult<Vec<Option<T>>, errors::RedisError>
    where
        T: serde::de::DeserializeOwned,
    {
        let value_bytes_vec = self.get_multiple_keys::<Vec<u8>>(keys).await?;

        let mut results = Vec::with_capacity(value_bytes_vec.len());
        for value_bytes_opt in value_bytes_vec {
            match value_bytes_opt {
                Some(value_bytes) => {
                    if value_bytes.is_empty() {
                        results.push(None);
                    } else {
                        let parsed = value_bytes
                            .parse_struct(type_name)
                            .change_context(errors::RedisError::JsonDeserializationFailed)?;
                        results.push(Some(parsed));
                    }
                }
                None => results.push(None),
            }
        }

        Ok(results)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn delete_key(&self, key: &RedisKey) -> CustomResult<DelReply, errors::RedisError> {
        match self
            .pool
            .del(key.tenant_aware_key(self))
            .await
            .change_context(errors::RedisError::DeleteFailed)
        {
            Ok(v) => Ok(v),
            Err(_err) => {
                #[cfg(not(feature = "multitenancy_fallback"))]
                {
                    Err(_err)
                }

                #[cfg(feature = "multitenancy_fallback")]
                {
                    self.pool
                        .del(key.tenant_unaware_key(self))
                        .await
                        .change_context(errors::RedisError::DeleteFailed)
                }
            }
        }
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn delete_multiple_keys(
        &self,
        keys: &[RedisKey],
    ) -> CustomResult<Vec<DelReply>, errors::RedisError> {
        let futures = keys.iter().map(|key| self.delete_key(key));

        let del_result = futures::future::try_join_all(futures)
            .await
            .change_context(errors::RedisError::DeleteFailed)?;

        Ok(del_result)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn set_key_with_expiry<V>(
        &self,
        key: &RedisKey,
        value: V,
        seconds: i64,
    ) -> CustomResult<(), errors::RedisError>
    where
        V: TryInto<RedisValue> + Debug + Send + Sync,
        V::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        self.pool
            .set(
                key.tenant_aware_key(self),
                value,
                Some(Expiration::EX(seconds)),
                None,
                false,
            )
            .await
            .change_context(errors::RedisError::SetExFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn set_key_if_not_exists_with_expiry<V>(
        &self,
        key: &RedisKey,
        value: V,
        seconds: Option<i64>,
    ) -> CustomResult<SetnxReply, errors::RedisError>
    where
        V: TryInto<RedisValue> + Debug + Send + Sync,
        V::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        self.pool
            .set(
                key.tenant_aware_key(self),
                value,
                Some(Expiration::EX(
                    seconds.unwrap_or(self.config.default_ttl.into()),
                )),
                Some(SetOptions::NX),
                false,
            )
            .await
            .change_context(errors::RedisError::SetFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn set_expiry(
        &self,
        key: &RedisKey,
        seconds: i64,
    ) -> CustomResult<(), errors::RedisError> {
        self.pool
            .expire(key.tenant_aware_key(self), seconds)
            .await
            .change_context(errors::RedisError::SetExpiryFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn set_expire_at(
        &self,
        key: &RedisKey,
        timestamp: i64,
    ) -> CustomResult<(), errors::RedisError> {
        self.pool
            .expire_at(key.tenant_aware_key(self), timestamp)
            .await
            .change_context(errors::RedisError::SetExpiryFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn set_hash_fields<V>(
        &self,
        key: &RedisKey,
        values: V,
        ttl: Option<i64>,
    ) -> CustomResult<(), errors::RedisError>
    where
        V: TryInto<RedisMap> + Debug + Send + Sync,
        V::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        let output: Result<(), _> = self
            .pool
            .hset(key.tenant_aware_key(self), values)
            .await
            .change_context(errors::RedisError::SetHashFailed);
        // setting expiry for the key
        output
            .async_and_then(|_| {
                self.set_expiry(key, ttl.unwrap_or(self.config.default_hash_ttl.into()))
            })
            .await
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn set_hash_field_if_not_exist<V>(
        &self,
        key: &RedisKey,
        field: &str,
        value: V,
        ttl: Option<u32>,
    ) -> CustomResult<HsetnxReply, errors::RedisError>
    where
        V: TryInto<RedisValue> + Debug + Send + Sync,
        V::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        let output: Result<HsetnxReply, _> = self
            .pool
            .hsetnx(key.tenant_aware_key(self), field, value)
            .await
            .change_context(errors::RedisError::SetHashFieldFailed);

        output
            .async_and_then(|inner| async {
                self.set_expiry(key, ttl.unwrap_or(self.config.default_hash_ttl).into())
                    .await?;
                Ok(inner)
            })
            .await
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn serialize_and_set_hash_field_if_not_exist<V>(
        &self,
        key: &RedisKey,
        field: &str,
        value: V,
        ttl: Option<u32>,
    ) -> CustomResult<HsetnxReply, errors::RedisError>
    where
        V: serde::Serialize + Debug,
    {
        let serialized = value
            .encode_to_vec()
            .change_context(errors::RedisError::JsonSerializationFailed)?;

        self.set_hash_field_if_not_exist(key, field, serialized.as_slice(), ttl)
            .await
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn serialize_and_set_multiple_hash_field_if_not_exist<V>(
        &self,
        kv: &[(&RedisKey, V)],
        field: &str,
        ttl: Option<u32>,
    ) -> CustomResult<Vec<HsetnxReply>, errors::RedisError>
    where
        V: serde::Serialize + Debug,
    {
        let mut hsetnx: Vec<HsetnxReply> = Vec::with_capacity(kv.len());
        for (key, val) in kv {
            hsetnx.push(
                self.serialize_and_set_hash_field_if_not_exist(key, field, val, ttl)
                    .await?,
            );
        }
        Ok(hsetnx)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn increment_fields_in_hash<T>(
        &self,
        key: &RedisKey,
        fields_to_increment: &[(T, i64)],
    ) -> CustomResult<Vec<usize>, errors::RedisError>
    where
        T: Debug + ToString,
    {
        let mut values_after_increment = Vec::with_capacity(fields_to_increment.len());
        for (field, increment) in fields_to_increment.iter() {
            values_after_increment.push(
                self.pool
                    .hincrby(key.tenant_aware_key(self), field.to_string(), *increment)
                    .await
                    .change_context(errors::RedisError::IncrementHashFieldFailed)?,
            )
        }

        Ok(values_after_increment)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn hscan(
        &self,
        key: &RedisKey,
        pattern: &str,
        count: Option<u32>,
    ) -> CustomResult<Vec<String>, errors::RedisError> {
        Ok(self
            .pool
            .next()
            .hscan::<&str, &str>(&key.tenant_aware_key(self), pattern, count)
            .filter_map(|value| async move {
                match value {
                    Ok(mut v) => {
                        let v = v.take_results()?;

                        let v: Vec<String> =
                            v.iter().filter_map(|(_, val)| val.as_string()).collect();
                        Some(futures::stream::iter(v))
                    }
                    Err(err) => {
                        tracing::error!(redis_err=?err, "Redis error while executing hscan command");
                        None
                    }
                }
            })
            .flatten()
            .collect::<Vec<_>>()
            .await)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn scan(
        &self,
        pattern: &RedisKey,
        count: Option<u32>,
        scan_type: Option<ScanType>,
    ) -> CustomResult<Vec<String>, errors::RedisError> {
        Ok(self
            .pool
            .next()
            .scan(pattern.tenant_aware_key(self), count, scan_type)
            .filter_map(|value| async move {
                match value {
                    Ok(mut v) => {
                        let v = v.take_results()?;

                        let v: Vec<String> =
                            v.into_iter().filter_map(|val| val.into_string()).collect();
                        Some(futures::stream::iter(v))
                    }
                    Err(err) => {
                        tracing::error!(redis_err=?err, "Redis error while executing scan command");
                        None
                    }
                }
            })
            .flatten()
            .collect::<Vec<_>>()
            .await)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn hscan_and_deserialize<T>(
        &self,
        key: &RedisKey,
        pattern: &str,
        count: Option<u32>,
    ) -> CustomResult<Vec<T>, errors::RedisError>
    where
        T: serde::de::DeserializeOwned,
    {
        let redis_results = self.hscan(key, pattern, count).await?;
        Ok(redis_results
            .iter()
            .filter_map(|v| {
                let r: T = v.parse_struct(std::any::type_name::<T>()).ok()?;
                Some(r)
            })
            .collect())
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn get_hash_field<V>(
        &self,
        key: &RedisKey,
        field: &str,
    ) -> CustomResult<V, errors::RedisError>
    where
        V: FromRedis + Unpin + Send + 'static,
    {
        match self
            .pool
            .hget(key.tenant_aware_key(self), field)
            .await
            .change_context(errors::RedisError::GetHashFieldFailed)
        {
            Ok(v) => Ok(v),
            Err(_err) => {
                #[cfg(feature = "multitenancy_fallback")]
                {
                    self.pool
                        .hget(key.tenant_unaware_key(self), field)
                        .await
                        .change_context(errors::RedisError::GetHashFieldFailed)
                }

                #[cfg(not(feature = "multitenancy_fallback"))]
                {
                    Err(_err)
                }
            }
        }
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn get_hash_fields<V>(&self, key: &RedisKey) -> CustomResult<V, errors::RedisError>
    where
        V: FromRedis + Unpin + Send + 'static,
    {
        match self
            .pool
            .hgetall(key.tenant_aware_key(self))
            .await
            .change_context(errors::RedisError::GetHashFieldFailed)
        {
            Ok(v) => Ok(v),
            Err(_err) => {
                #[cfg(feature = "multitenancy_fallback")]
                {
                    self.pool
                        .hgetall(key.tenant_unaware_key(self))
                        .await
                        .change_context(errors::RedisError::GetHashFieldFailed)
                }

                #[cfg(not(feature = "multitenancy_fallback"))]
                {
                    Err(_err)
                }
            }
        }
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn get_hash_field_and_deserialize<V>(
        &self,
        key: &RedisKey,
        field: &str,
        type_name: &'static str,
    ) -> CustomResult<V, errors::RedisError>
    where
        V: serde::de::DeserializeOwned,
    {
        let value_bytes = self.get_hash_field::<Vec<u8>>(key, field).await?;

        if value_bytes.is_empty() {
            return Err(errors::RedisError::NotFound.into());
        }

        value_bytes
            .parse_struct(type_name)
            .change_context(errors::RedisError::JsonDeserializationFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn sadd<V>(
        &self,
        key: &RedisKey,
        members: V,
    ) -> CustomResult<SaddReply, errors::RedisError>
    where
        V: TryInto<MultipleValues> + Debug + Send,
        V::Error: Into<fred::error::RedisError> + Send,
    {
        self.pool
            .sadd(key.tenant_aware_key(self), members)
            .await
            .change_context(errors::RedisError::SetAddMembersFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn stream_append_entry<F>(
        &self,
        stream: &RedisKey,
        entry_id: &RedisEntryId,
        fields: F,
    ) -> CustomResult<(), errors::RedisError>
    where
        F: TryInto<MultipleOrderedPairs> + Debug + Send + Sync,
        F::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        self.pool
            .xadd(stream.tenant_aware_key(self), false, None, entry_id, fields)
            .await
            .change_context(errors::RedisError::StreamAppendFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn stream_delete_entries<Ids>(
        &self,
        stream: &RedisKey,
        ids: Ids,
    ) -> CustomResult<usize, errors::RedisError>
    where
        Ids: Into<MultipleStrings> + Debug + Send + Sync,
    {
        self.pool
            .xdel(stream.tenant_aware_key(self), ids)
            .await
            .change_context(errors::RedisError::StreamDeleteFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn stream_trim_entries<C>(
        &self,
        stream: &RedisKey,
        xcap: C,
    ) -> CustomResult<usize, errors::RedisError>
    where
        C: TryInto<XCap> + Debug + Send + Sync,
        C::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        self.pool
            .xtrim(stream.tenant_aware_key(self), xcap)
            .await
            .change_context(errors::RedisError::StreamTrimFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn stream_acknowledge_entries<Ids>(
        &self,
        stream: &RedisKey,
        group: &str,
        ids: Ids,
    ) -> CustomResult<usize, errors::RedisError>
    where
        Ids: Into<MultipleIDs> + Debug + Send + Sync,
    {
        self.pool
            .xack(stream.tenant_aware_key(self), group, ids)
            .await
            .change_context(errors::RedisError::StreamAcknowledgeFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn stream_get_length(
        &self,
        stream: &RedisKey,
    ) -> CustomResult<usize, errors::RedisError> {
        self.pool
            .xlen(stream.tenant_aware_key(self))
            .await
            .change_context(errors::RedisError::GetLengthFailed)
    }

    pub fn get_keys_with_prefix<K>(&self, keys: K) -> MultipleKeys
    where
        K: Into<MultipleKeys> + Debug + Send + Sync,
    {
        let multiple_keys: MultipleKeys = keys.into();
        let res = multiple_keys
            .inner()
            .iter()
            .filter_map(|key| key.as_str().map(RedisKey::from))
            .map(|k: RedisKey| k.tenant_aware_key(self))
            .collect::<Vec<_>>();

        MultipleKeys::from(res)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn stream_read_entries<K, Ids>(
        &self,
        streams: K,
        ids: Ids,
        read_count: Option<u64>,
    ) -> CustomResult<XReadResponse<String, String, String, String>, errors::RedisError>
    where
        K: Into<MultipleKeys> + Debug + Send + Sync,
        Ids: Into<MultipleIDs> + Debug + Send + Sync,
    {
        let strms = self.get_keys_with_prefix(streams);
        self.pool
            .xread_map(
                Some(read_count.unwrap_or(self.config.default_stream_read_count)),
                None,
                strms,
                ids,
            )
            .await
            .map_err(|err| match err.kind() {
                RedisErrorKind::NotFound | RedisErrorKind::Parse => {
                    report!(err).change_context(errors::RedisError::StreamEmptyOrNotAvailable)
                }
                _ => report!(err).change_context(errors::RedisError::StreamReadFailed),
            })
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn stream_read_with_options<K, Ids>(
        &self,
        streams: K,
        ids: Ids,
        count: Option<u64>,
        block: Option<u64>,          // timeout in milliseconds
        group: Option<(&str, &str)>, // (group_name, consumer_name)
    ) -> CustomResult<XReadResponse<String, String, String, Option<String>>, errors::RedisError>
    where
        K: Into<MultipleKeys> + Debug + Send + Sync,
        Ids: Into<MultipleIDs> + Debug + Send + Sync,
    {
        match group {
            Some((group_name, consumer_name)) => {
                self.pool
                    .xreadgroup_map(
                        group_name,
                        consumer_name,
                        count,
                        block,
                        false,
                        self.get_keys_with_prefix(streams),
                        ids,
                    )
                    .await
            }
            None => {
                self.pool
                    .xread_map(count, block, self.get_keys_with_prefix(streams), ids)
                    .await
            }
        }
        .map_err(|err| match err.kind() {
            RedisErrorKind::NotFound | RedisErrorKind::Parse => {
                report!(err).change_context(errors::RedisError::StreamEmptyOrNotAvailable)
            }
            _ => report!(err).change_context(errors::RedisError::StreamReadFailed),
        })
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn append_elements_to_list<V>(
        &self,
        key: &RedisKey,
        elements: V,
    ) -> CustomResult<(), errors::RedisError>
    where
        V: TryInto<MultipleValues> + Debug + Send,
        V::Error: Into<fred::error::RedisError> + Send,
    {
        self.pool
            .rpush(key.tenant_aware_key(self), elements)
            .await
            .change_context(errors::RedisError::AppendElementsToListFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn get_list_elements(
        &self,
        key: &RedisKey,
        start: i64,
        stop: i64,
    ) -> CustomResult<Vec<String>, errors::RedisError> {
        self.pool
            .lrange(key.tenant_aware_key(self), start, stop)
            .await
            .change_context(errors::RedisError::GetListElementsFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn get_list_length(&self, key: &RedisKey) -> CustomResult<usize, errors::RedisError> {
        self.pool
            .llen(key.tenant_aware_key(self))
            .await
            .change_context(errors::RedisError::GetListLengthFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn lpop_list_elements(
        &self,
        key: &RedisKey,
        count: Option<usize>,
    ) -> CustomResult<Vec<String>, errors::RedisError> {
        self.pool
            .lpop(key.tenant_aware_key(self), count)
            .await
            .change_context(errors::RedisError::PopListElementsFailed)
    }

    //                                              Consumer Group API

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn consumer_group_create(
        &self,
        stream: &RedisKey,
        group: &str,
        id: &RedisEntryId,
    ) -> CustomResult<(), errors::RedisError> {
        if matches!(
            id,
            RedisEntryId::AutoGeneratedID | RedisEntryId::UndeliveredEntryID
        ) {
            // FIXME: Replace with utils::when
            Err(errors::RedisError::InvalidRedisEntryId)?;
        }

        self.pool
            .xgroup_create(stream.tenant_aware_key(self), group, id, true)
            .await
            .change_context(errors::RedisError::ConsumerGroupCreateFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn consumer_group_destroy(
        &self,
        stream: &RedisKey,
        group: &str,
    ) -> CustomResult<usize, errors::RedisError> {
        self.pool
            .xgroup_destroy(stream.tenant_aware_key(self), group)
            .await
            .change_context(errors::RedisError::ConsumerGroupDestroyFailed)
    }

    // the number of pending messages that the consumer had before it was deleted
    #[instrument(level = "DEBUG", skip(self))]
    pub async fn consumer_group_delete_consumer(
        &self,
        stream: &RedisKey,
        group: &str,
        consumer: &str,
    ) -> CustomResult<usize, errors::RedisError> {
        self.pool
            .xgroup_delconsumer(stream.tenant_aware_key(self), group, consumer)
            .await
            .change_context(errors::RedisError::ConsumerGroupRemoveConsumerFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn consumer_group_set_last_id(
        &self,
        stream: &RedisKey,
        group: &str,
        id: &RedisEntryId,
    ) -> CustomResult<String, errors::RedisError> {
        self.pool
            .xgroup_setid(stream.tenant_aware_key(self), group, id)
            .await
            .change_context(errors::RedisError::ConsumerGroupSetIdFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn consumer_group_set_message_owner<Ids, R>(
        &self,
        stream: &RedisKey,
        group: &str,
        consumer: &str,
        min_idle_time: u64,
        ids: Ids,
    ) -> CustomResult<R, errors::RedisError>
    where
        Ids: Into<MultipleIDs> + Debug + Send + Sync,
        R: FromRedis + Unpin + Send + 'static,
    {
        self.pool
            .xclaim(
                stream.tenant_aware_key(self),
                group,
                consumer,
                min_idle_time,
                ids,
                None,
                None,
                None,
                false,
                false,
            )
            .await
            .change_context(errors::RedisError::ConsumerGroupClaimFailed)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn evaluate_redis_script<V, T>(
        &self,
        lua_script: &'static str,
        key: Vec<String>,
        values: V,
    ) -> CustomResult<T, errors::RedisError>
    where
        V: TryInto<MultipleValues> + Debug + Send + Sync,
        V::Error: Into<fred::error::RedisError> + Send + Sync,
        T: serde::de::DeserializeOwned + FromRedis,
    {
        let val: T = self
            .pool
            .eval(lua_script, key, values)
            .await
            .change_context(errors::RedisError::IncrementHashFieldFailed)?;
        Ok(val)
    }

    #[instrument(level = "DEBUG", skip(self))]
    pub async fn set_multiple_keys_if_not_exists_and_get_values<V>(
        &self,
        keys: &[(RedisKey, V)],
        ttl: Option<i64>,
    ) -> CustomResult<Vec<SetGetReply<V>>, errors::RedisError>
    where
        V: TryInto<RedisValue>
            + Debug
            + FromRedis
            + ToOwned<Owned = V>
            + Send
            + Sync
            + serde::de::DeserializeOwned,
        V::Error: Into<fred::error::RedisError> + Send + Sync,
    {
        let futures = keys.iter().map(|(key, value)| {
            self.set_key_if_not_exists_and_get_value(key, (*value).to_owned(), ttl)
        });

        let del_result = futures::future::try_join_all(futures)
            .await
            .change_context(errors::RedisError::SetFailed)?;

        Ok(del_result)
    }

    /// Sets a value in Redis if not already present, and returns the value (either existing or newly set).
    /// This operation is atomic using Redis transactions.
    #[instrument(level = "DEBUG", skip(self))]
    pub async fn set_key_if_not_exists_and_get_value<V>(
        &self,
        key: &RedisKey,
        value: V,
        ttl: Option<i64>,
    ) -> CustomResult<SetGetReply<V>, errors::RedisError>
    where


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

impl common_utils::events::ApiEventMetric for for SchedulerHealthCheckResponse


    let address: Option<&AddressDetails> = item
        .get_optional_billing()
        .and_then(|billing_details| billing_details.address.as_ref());

    if let Some(address) = address {
        // mandatory fields check
        address.get_first_name()?;
        item.request.get_email_required()?;
        item.get_billing_country()?;
    }

    let (is_rebilling, additional_params, user_token_id) =
        match item.request.is_customer_initiated_mandate_payment() {
            true => {
                (
                    Some("0".to_string()), // In case of first installment, rebilling should be 0
                    Some(V2AdditionalParams {
                        rebill_expiry: Some(
                            time::OffsetDateTime::now_utc()
                                .replace_year(time::OffsetDateTime::now_utc().year() + 5)
                                .map_err(|_| errors::ConnectorError::DateFormattingFailed)?
                                .date()
                                .format(&time::macros::format_description!("[year][month][day]"))
                                .map_err(|_| errors::ConnectorError::DateFormattingFailed)?,
                        ),
                        rebill_frequency: Some("0".to_string()),
                        challenge_window_size: Some(CHALLENGE_WINDOW_SIZE.to_string()),
                        challenge_preference: Some(CHALLENGE_PREFERENCE.to_string()),
                    }),
                    item.request.get_customer_id_required(),
                )
            }
            // non mandate transactions
            false => (
                None,
                Some(V2AdditionalParams {
                    rebill_expiry: None,
                    rebill_frequency: None,
                    challenge_window_size: Some(CHALLENGE_WINDOW_SIZE.to_string()),
                    challenge_preference: Some(CHALLENGE_PREFERENCE.to_string()),
                }),
                None,
            ),
        };
    let three_d = if let Some(auth_data) = item.request.get_auth_data()? {
        Some(ThreeD {
            external_mpi: Some(ExternalMpi {
                eci: auth_data.eci,
                cavv: auth_data.cavv,
                ds_trans_id: auth_data.ds_trans_id,
                challenge_preference: None,
                exemption_request_reason: None,
            }),
            ..Default::default()
        })
    } else if item.is_three_ds() {
        let browser_details = match &browser_information {
            Some(browser_info) => Some(BrowserDetails {
                accept_header: browser_info.get_accept_header()?,
                ip: browser_info.get_ip_address()?,
                java_enabled: browser_info.get_java_enabled()?.to_string().to_uppercase(),
                java_script_enabled: browser_info
                    .get_java_script_enabled()?
                    .to_string()
                    .to_uppercase(),
                language: browser_info.get_language()?,
                screen_height: browser_info.get_screen_height()?,
                screen_width: browser_info.get_screen_width()?,
                color_depth: browser_info.get_color_depth()?,
                user_agent: browser_info.get_user_agent()?,
                time_zone: browser_info.get_time_zone()?,
            }),
            None => None,
        };
        Some(ThreeD {
            browser_details,
            v2_additional_params: additional_params,
            notification_url: item.request.get_complete_authorize_url().clone(),
            merchant_url: Some(item.request.get_return_url_required()?),
            platform_type: Some(PlatformType::Browser),
            method_completion_ind: Some(MethodCompletion::Unavailable),
            ..Default::default()
        })
    } else {
        None
    };
    let is_moto = item.request.get_is_moto();
    Ok(NuveiPaymentsRequest {
        related_transaction_id,
        is_rebilling,
        user_token_id,
        device_details: DeviceDetails::foreign_try_from(&item.request.get_browser_info().clone())?,
        payment_option: PaymentOption::from(NuveiCardDetails {
            card: card_details.clone(),
            three_d,
            card_holder_name: item.get_optional_billing_full_name(),
            stored_credentials: item.request.get_is_stored_credential(),
        }),
        is_moto,
        ..Default::default()
    })
}
impl From<NuveiCardDetails> for PaymentOption {
    fn from(card_details: NuveiCardDetails) -> Self {
        let card = card_details.card;
        Self {
            card: Some(Card {
                card_number: Some(card.card_number),
                card_holder_name: card_details.card_holder_name,
                expiration_month: Some(card.card_exp_month),
                expiration_year: Some(card.card_exp_year),
                three_d: card_details.three_d,
                cvv: Some(card.card_cvc),
                stored_credentials: card_details.stored_credentials,
                ..Default::default()
            }),
            ..Default::default()
        }
    }
}

impl TryFrom<(&types::PaymentsCompleteAuthorizeRouterData, Secret<String>)>
    for NuveiPaymentsRequest
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        data: (&types::PaymentsCompleteAuthorizeRouterData, Secret<String>),
    ) -> Result<Self, Self::Error> {
        let item = data.0;
        let request_data = match item.request.payment_method_data.clone() {
            Some(PaymentMethodData::Card(card)) => {
                let device_details = DeviceDetails::foreign_try_from(&item.request.browser_info)?;
                Ok(Self {
                    payment_option: PaymentOption::from(NuveiCardDetails {
                        card,
                        three_d: None,
                        card_holder_name: item.get_optional_billing_full_name(),
                        stored_credentials: StoredCredentialMode::get_optional_stored_credential(
                            item.request.is_stored_credential,
                        ),
                    }),
                    device_details,
                    ..Default::default()
                })
            }
            _ => Err(errors::ConnectorError::NotImplemented(
                utils::get_unimplemented_payment_method_error_message("nuvei"),
            )),
        }?;
        let request = Self::try_from(NuveiPaymentRequestData {
            amount: convert_amount(
                NUVEI_AMOUNT_CONVERTOR,
                item.request.minor_amount,
                item.request.currency,
            )?,
            currency: item.request.currency,
            connector_auth_type: item.connector_auth_type.clone(),
            client_request_id: item.connector_request_reference_id.clone(),
            session_token: data.1,
            capture_method: item.request.capture_method,
            ..Default::default()
        })?;
        Ok(Self {
            related_transaction_id: item.request.connector_transaction_id.clone(),
            payment_option: request_data.payment_option,
            device_details: request_data.device_details,
            ..request
        })
    }
}

impl TryFrom<NuveiPaymentRequestData> for NuveiPaymentsRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(request: NuveiPaymentRequestData) -> Result<Self, Self::Error> {
        let session_token = request.session_token;
        fp_utils::when(session_token.clone().expose().is_empty(), || {
            Err(errors::ConnectorError::FailedToObtainAuthType)
        })?;
        let connector_meta: NuveiAuthType = NuveiAuthType::try_from(&request.connector_auth_type)?;
        let merchant_id = connector_meta.merchant_id;
        let merchant_site_id = connector_meta.merchant_site_id;
        let client_request_id = request.client_request_id;
        let time_stamp =
            date_time::format_date(date_time::now(), date_time::DateFormat::YYYYMMDDHHmmss)
                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let merchant_secret = connector_meta.merchant_secret;
        let transaction_type = TransactionType::get_from_capture_method_and_amount_string(
            request.capture_method.unwrap_or_default(),
            &request.amount.get_amount_as_string(),
        );
        Ok(Self {
            merchant_id: merchant_id.clone(),
            merchant_site_id: merchant_site_id.clone(),
            client_request_id: Secret::new(client_request_id.clone()),
            time_stamp: time_stamp.clone(),
            session_token,
            transaction_type,
            checksum: Secret::new(encode_payload(&[
                merchant_id.peek(),
                merchant_site_id.peek(),
                &client_request_id,
                &request.amount.get_amount_as_string(),
                &request.currency.to_string(),
                &time_stamp,
                merchant_secret.peek(),
            ])?),
            amount: request.amount,
            user_token_id: None,
            currency: request.currency,
            ..Default::default()
        })
    }
}

impl TryFrom<NuveiPaymentRequestData> for NuveiPaymentFlowRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(request: NuveiPaymentRequestData) -> Result<Self, Self::Error> {
        let connector_meta: NuveiAuthType = NuveiAuthType::try_from(&request.connector_auth_type)?;
        let merchant_id = connector_meta.merchant_id;
        let merchant_site_id = connector_meta.merchant_site_id;
        let client_request_id = request.client_request_id;
        let time_stamp =
            date_time::format_date(date_time::now(), date_time::DateFormat::YYYYMMDDHHmmss)
                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let merchant_secret = connector_meta.merchant_secret;
        Ok(Self {
            merchant_id: merchant_id.to_owned(),
            merchant_site_id: merchant_site_id.to_owned(),
            client_request_id: client_request_id.clone(),
            time_stamp: time_stamp.clone(),
            checksum: Secret::new(encode_payload(&[
                merchant_id.peek(),
                merchant_site_id.peek(),
                &client_request_id,
                &request.amount.get_amount_as_string(),
                &request.currency.to_string(),
                &request.related_transaction_id.clone().unwrap_or_default(),
                &time_stamp,
                merchant_secret.peek(),
            ])?),
            amount: request.amount,
            currency: request.currency,
            related_transaction_id: request.related_transaction_id,
        })
    }
}

#[derive(Debug, Clone, Default)]
pub struct NuveiPaymentRequestData {
    pub amount: StringMajorUnit,
    pub currency: enums::Currency,
    pub related_transaction_id: Option<String>,
    pub client_request_id: String,
    pub connector_auth_type: ConnectorAuthType,
    pub session_token: Secret<String>,
    pub capture_method: Option<CaptureMethod>,
}

impl TryFrom<&types::PaymentsCaptureRouterData> for NuveiPaymentFlowRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
        Self::try_from(NuveiPaymentRequestData {
            client_request_id: item.connector_request_reference_id.clone(),
            connector_auth_type: item.connector_auth_type.clone(),
            amount: convert_amount(
                NUVEI_AMOUNT_CONVERTOR,
                item.request.minor_amount_to_capture,
                item.request.currency,
            )?,
            currency: item.request.currency,
            related_transaction_id: Some(item.request.connector_transaction_id.clone()),
            ..Default::default()
        })
    }
}
impl TryFrom<&types::RefundExecuteRouterData> for NuveiPaymentFlowRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::RefundExecuteRouterData) -> Result<Self, Self::Error> {
        Self::try_from(NuveiPaymentRequestData {
            client_request_id: item.connector_request_reference_id.clone(),
            connector_auth_type: item.connector_auth_type.clone(),
            amount: convert_amount(
                NUVEI_AMOUNT_CONVERTOR,
                item.request.minor_refund_amount,
                item.request.currency,
            )?,
            currency: item.request.currency,
            related_transaction_id: Some(item.request.connector_transaction_id.clone()),
            ..Default::default()
        })
    }
}

impl TryFrom<&types::PaymentsSyncRouterData> for NuveiPaymentSyncRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(value: &types::PaymentsSyncRouterData) -> Result<Self, Self::Error> {
        let connector_meta: NuveiAuthType = NuveiAuthType::try_from(&value.connector_auth_type)?;
        let merchant_id = connector_meta.merchant_id.clone();
        let merchant_site_id = connector_meta.merchant_site_id.clone();
        let merchant_secret = connector_meta.merchant_secret.clone();
        let time_stamp =
            date_time::format_date(date_time::now(), date_time::DateFormat::YYYYMMDDHHmmss)
                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let transaction_id = value
            .request
            .connector_transaction_id
            .clone()
            .get_connector_transaction_id()
            .change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
        let checksum = Secret::new(encode_payload(&[
            merchant_id.peek(),
            merchant_site_id.peek(),
            &transaction_id,
            &time_stamp,
            merchant_secret.peek(),
        ])?);

        Ok(Self {
            merchant_id,
            merchant_site_id,
            time_stamp,
            checksum,
            transaction_id,
        })
    }
}

#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct NuveiVoidRequest {
    pub merchant_id: Secret<String>,
    pub merchant_site_id: Secret<String>,
    pub client_unique_id: String,
    pub related_transaction_id: String,
    pub time_stamp: String,
    pub checksum: Secret<String>,
    pub client_request_id: String,
}

impl TryFrom<&types::PaymentsCancelPostCaptureRouterData> for NuveiVoidRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCancelPostCaptureRouterData) -> Result<Self, Self::Error> {
        let connector_meta: NuveiAuthType = NuveiAuthType::try_from(&item.connector_auth_type)?;
        let merchant_id = connector_meta.merchant_id.clone();
        let merchant_site_id = connector_meta.merchant_site_id.clone();
        let merchant_secret = connector_meta.merchant_secret.clone();
        let client_unique_id = item.connector_request_reference_id.clone();
        let related_transaction_id = item.request.connector_transaction_id.clone();
        let client_request_id = item.connector_request_reference_id.clone();
        let time_stamp =
            date_time::format_date(date_time::now(), date_time::DateFormat::YYYYMMDDHHmmss)
                .change_context(errors::ConnectorError::RequestEncodingFailed)?;
        let checksum = Secret::new(encode_payload(&[
            merchant_id.peek(),
            merchant_site_id.peek(),
            &client_request_id,
            &client_unique_id,
            "", // amount (empty for void)
            "", // currency (empty for void)
            &related_transaction_id,
            "", // authCode (empty)
            "", // comment (empty)
            &time_stamp,
            merchant_secret.peek(),
        ])?);

        Ok(Self {
            merchant_id,
            merchant_site_id,
            client_unique_id,
            related_transaction_id,
            time_stamp,
            checksum,
            client_request_id,
        })
    }
}

impl TryFrom<&types::PaymentsCancelRouterData> for NuveiPaymentFlowRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
        Self::try_from(NuveiPaymentRequestData {
            client_request_id: item.connector_request_reference_id.clone(),
            connector_auth_type: item.connector_auth_type.clone(),
            amount: convert_amount(
                NUVEI_AMOUNT_CONVERTOR,
                item.request
                    .minor_amount
                    .ok_or_else(missing_field_err("amount"))?,
                item.request.get_currency()?,
            )?,
            currency: item.request.get_currency()?,
            related_transaction_id: Some(item.request.connector_transaction_id.clone()),
            ..Default::default()
        })
    }
}

// Auth Struct
pub struct NuveiAuthType {
    pub(super) merchant_id: Secret<String>,
    pub(super) merchant_site_id: Secret<String>,
    pub(super) merchant_secret: Secret<String>,
}

impl TryFrom<&ConnectorAuthType> for NuveiAuthType {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> {
        if let ConnectorAuthType::SignatureKey {
            api_key,
            key1,
            api_secret,
        } = auth_type
        {
            Ok(Self {
                merchant_id: api_key.to_owned(),
                merchant_site_id: key1.to_owned(),
                merchant_secret: api_secret.to_owned(),
            })
        } else {
            Err(errors::ConnectorError::FailedToObtainAuthType)?
        }
    }
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiPayoutRequest {
    pub merchant_id: Secret<String>,
    pub merchant_site_id: Secret<String>,
    pub client_request_id: String,
    pub client_unique_id: String,
    pub amount: StringMajorUnit,
    pub currency: String,
    pub user_token_id: CustomerId,
    pub time_stamp: String,
    pub checksum: Secret<String>,
    pub card_data: NuveiPayoutCardData,
    pub url_details: NuveiPayoutUrlDetails,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiPayoutUrlDetails {
    pub notification_url: String,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiPayoutCardData {
    pub card_number: cards::CardNumber,
    pub card_holder_name: Secret<String>,
    pub expiration_month: Secret<String>,
    pub expiration_year: Secret<String>,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum NuveiPayoutResponse {
    NuveiPayoutSuccessResponse(NuveiPayoutSuccessResponse),
    NuveiPayoutErrorResponse(NuveiPayoutErrorResponse),
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiPayoutSuccessResponse {
    pub transaction_id: String,
    pub user_token_id: CustomerId,
    pub transaction_status: NuveiTransactionStatus,
    pub client_unique_id: String,
}

#[cfg(feature = "payouts")]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiPayoutErrorResponse {
    pub status: NuveiPaymentStatus,
    pub err_code: i64,
    pub reason: Option<String>,
}

#[cfg(feature = "payouts")]
impl TryFrom<api_models::payouts::PayoutMethodData> for NuveiPayoutCardData {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        payout_method_data: api_models::payouts::PayoutMethodData,
    ) -> Result<Self, Self::Error> {
        match payout_method_data {
            api_models::payouts::PayoutMethodData::Card(card_data) => Ok(Self {
                card_number: card_data.card_number,
                card_holder_name: card_data.card_holder_name.ok_or(
                    errors::ConnectorError::MissingRequiredField {
                        field_name: "customer_id",
                    },
                )?,
                expiration_month: card_data.expiry_month,
                expiration_year: card_data.expiry_year,
            }),
            api_models::payouts::PayoutMethodData::Bank(_)
            | api_models::payouts::PayoutMethodData::Wallet(_) => {
                Err(errors::ConnectorError::NotImplemented(
                    "Selected Payout Method is not implemented for Nuvei".to_string(),
                )
                .into())
            }
        }
    }
}

#[cfg(feature = "payouts")]
impl<F> TryFrom<&types::PayoutsRouterData<F>> for NuveiPayoutRequest {
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(item: &types::PayoutsRouterData<F>) -> Result<Self, Self::Error> {
        let connector_auth: NuveiAuthType = NuveiAuthType::try_from(&item.connector_auth_type)?;

        let amount = convert_amount(
            NUVEI_AMOUNT_CONVERTOR,
            item.request.minor_amount,
            item.request.destination_currency,
        )?;

        let time_stamp =
            date_time::format_date(date_time::now(), date_time::DateFormat::YYYYMMDDHHmmss)
                .change_context(errors::ConnectorError::RequestEncodingFailed)?;

        let checksum = encode_payload(&[
            connector_auth.merchant_id.peek(),
            connector_auth.merchant_site_id.peek(),
            &item.connector_request_reference_id,
            &amount.get_amount_as_string(),
            &item.request.destination_currency.to_string(),
            &time_stamp,
            connector_auth.merchant_secret.peek(),
        ])?;

        let payout_method_data = item.get_payout_method_data()?;

        let card_data = NuveiPayoutCardData::try_from(payout_method_data)?;

        let customer_details = item.request.get_customer_details()?;

        let url_details = NuveiPayoutUrlDetails {
            notification_url: item.request.get_webhook_url()?,
        };

        Ok(Self {
            merchant_id: connector_auth.merchant_id,
            merchant_site_id: connector_auth.merchant_site_id,
            client_request_id: item.connector_request_reference_id.clone(),
            client_unique_id: item.connector_request_reference_id.clone(),
            amount,
            currency: item.request.destination_currency.to_string(),
            user_token_id: customer_details.customer_id.clone().ok_or(
                errors::ConnectorError::MissingRequiredField {
                    field_name: "customer_id",
                },
            )?,
            time_stamp,
            checksum: Secret::new(checksum),
            card_data,
            url_details,
        })
    }
}

#[cfg(feature = "payouts")]
impl TryFrom<PayoutsResponseRouterData<PoFulfill, NuveiPayoutResponse>>
    for types::PayoutsRouterData<PoFulfill>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: PayoutsResponseRouterData<PoFulfill, NuveiPayoutResponse>,
    ) -> Result<Self, Self::Error> {
        let response = &item.response;

        match response {
            NuveiPayoutResponse::NuveiPayoutSuccessResponse(response_data) => Ok(Self {
                response: Ok(PayoutsResponseData {
                    status: Some(enums::PayoutStatus::from(
                        response_data.transaction_status.clone(),
                    )),
                    connector_payout_id: Some(response_data.transaction_id.clone()),
                    payout_eligible: None,
                    should_add_next_step_to_process_tracker: false,
                    error_code: None,
                    error_message: None,
                }),
                ..item.data
            }),
            NuveiPayoutResponse::NuveiPayoutErrorResponse(error_response_data) => Ok(Self {
                response: Ok(PayoutsResponseData {
                    status: Some(enums::PayoutStatus::from(
                        error_response_data.status.clone(),
                    )),
                    connector_payout_id: None,
                    payout_eligible: None,
                    should_add_next_step_to_process_tracker: false,
                    error_code: Some(error_response_data.err_code.to_string()),
                    error_message: error_response_data.reason.clone(),
                }),
                ..item.data
            }),
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum NuveiPaymentStatus {
    Success,
    Failed,
    Error,
    #[default]
    Processing,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "UPPERCASE")]
pub enum NuveiTransactionStatus {
    #[serde(alias = "Approved", alias = "APPROVED")]
    Approved,
    #[serde(alias = "Declined", alias = "DECLINED")]
    Declined,
    #[serde(alias = "Filter Error", alias = "ERROR", alias = "Error")]
    Error,
    #[serde(alias = "Redirect", alias = "REDIRECT")]
    Redirect,
    #[serde(alias = "Pending", alias = "PENDING")]
    Pending,
    #[serde(alias = "Processing", alias = "PROCESSING")]
    #[default]
    Processing,
}

impl From<NuveiTransactionStatus> for enums::AttemptStatus {
    fn from(item: NuveiTransactionStatus) -> Self {
        match item {
            NuveiTransactionStatus::Approved => Self::Charged,
            NuveiTransactionStatus::Declined | NuveiTransactionStatus::Error => Self::Failure,
            _ => Self::Pending,
        }
    }
}

#[cfg(feature = "payouts")]
impl From<NuveiTransactionStatus> for enums::PayoutStatus {
    fn from(item: NuveiTransactionStatus) -> Self {
        match item {
            NuveiTransactionStatus::Approved => Self::Success,
            NuveiTransactionStatus::Declined | NuveiTransactionStatus::Error => Self::Failed,
            NuveiTransactionStatus::Processing | NuveiTransactionStatus::Pending => Self::Pending,
            NuveiTransactionStatus::Redirect => Self::Ineligible,
        }
    }
}

#[cfg(feature = "payouts")]
impl From<NuveiPaymentStatus> for enums::PayoutStatus {
    fn from(item: NuveiPaymentStatus) -> Self {
        match item {
            NuveiPaymentStatus::Success => Self::Success,
            NuveiPaymentStatus::Failed | NuveiPaymentStatus::Error => Self::Failed,
            NuveiPaymentStatus::Processing => Self::Pending,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiPartialApproval {
    pub requested_amount: StringMajorUnit,
    pub requested_currency: enums::Currency,
    pub processed_amount: StringMajorUnit,
    pub processed_currency: enums::Currency,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiPaymentsResponse {
    pub order_id: Option<String>,
    pub user_token_id: Option<Secret<String>>,
    pub payment_option: Option<PaymentOption>,
    pub transaction_status: Option<NuveiTransactionStatus>,
    pub gw_error_code: Option<i64>,
    pub gw_error_reason: Option<String>,
    pub gw_extended_error_code: Option<i64>,
    pub issuer_decline_code: Option<String>,
    pub issuer_decline_reason: Option<String>,
    pub transaction_type: Option<NuveiTransactionType>,
    pub transaction_id: Option<String>,
    pub external_transaction_id: Option<String>,
    pub auth_code: Option<String>,
    pub custom_data: Option<String>,
    pub fraud_details: Option<FraudDetails>,
    // NTID
    pub external_scheme_transaction_id: Option<Secret<String>>,
    pub session_token: Option<Secret<String>>,
    pub partial_approval: Option<NuveiPartialApproval>,
    //The ID of the transaction in the merchant’s system.
    pub client_unique_id: Option<String>,
    pub internal_request_id: Option<i64>,
    pub status: NuveiPaymentStatus,
    pub err_code: Option<i64>,
    pub reason: Option<String>,
    pub merchant_id: Option<Secret<String>>,
    pub merchant_site_id: Option<Secret<String>>,
    pub version: Option<String>,
    pub client_request_id: Option<String>,
    pub merchant_advice_code: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiTxnPartialApproval {
    requested_amount: Option<StringMajorUnit>,
    requested_currency: Option<enums::Currency>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiTransactionSyncResponseDetails {
    gw_error_code: Option<i64>,
    gw_error_reason: Option<String>,
    gw_extended_error_code: Option<i64>,
    transaction_id: Option<String>,
    // Status of the payment
    transaction_status: Option<NuveiTransactionStatus>,
    transaction_type: Option<NuveiTransactionType>,
    auth_code: Option<String>,
    processed_amount: Option<StringMajorUnit>,
    processed_currency: Option<enums::Currency>,
    acquiring_bank_name: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NuveiTransactionSyncResponse {
    pub payment_option: Option<PaymentOption>,
    pub partial_approval: Option<NuveiTxnPartialApproval>,
    pub is_currency_converted: Option<bool>,
    pub transaction_details: Option<NuveiTransactionSyncResponseDetails>,
    pub fraud_details: Option<FraudDetails>,
    pub client_unique_id: Option<String>,
    pub internal_request_id: Option<i64>,
    // API response status
    pub status: NuveiPaymentStatus,
    pub err_code: Option<i64>,
    pub reason: Option<String>,
    pub merchant_id: Option<Secret<String>>,
    pub merchant_site_id: Option<Secret<String>>,
    pub version: Option<String>,
    pub client_request_id: Option<String>,
    pub merchant_advice_code: Option<String>,
}
impl NuveiTransactionSyncResponse {
    pub fn get_partial_approval(&self) -> Option<NuveiPartialApproval> {
        match &self.partial_approval {
            Some(partial_approval) => match (
                partial_approval.requested_amount.clone(),
                partial_approval.requested_currency,
                self.transaction_details
                    .as_ref()
                    .and_then(|txn| txn.processed_amount.clone()),
                self.transaction_details
                    .as_ref()
                    .and_then(|txn| txn.processed_currency),
            ) {
                (
                    Some(requested_amount),
                    Some(requested_currency),
                    Some(processed_amount),
                    Some(processed_currency),
                ) => Some(NuveiPartialApproval {
                    requested_amount,
                    requested_currency,
                    processed_amount,
                    processed_currency,
                }),
                _ => None,
            },
            None => None,
        }
    }
}

pub fn get_amount_captured(
    partial_approval_data: Option<NuveiPartialApproval>,
    transaction_type: Option<NuveiTransactionType>,
) -> Result<(Option<i64>, Option<MinorUnit>), error_stack::Report<errors::ConnectorError>> {
    match partial_approval_data {
        Some(partial_approval) => {
            let amount = utils::convert_back_amount_to_minor_units(
                NUVEI_AMOUNT_CONVERTOR,
                partial_approval.processed_amount.clone(),
                partial_approval.processed_currency,
            )?;
            match transaction_type {
                None => Ok((None, None)),
                Some(NuveiTransactionType::Sale) => {
                    Ok((Some(MinorUnit::get_amount_as_i64(amount)), None))
                }
                Some(NuveiTransactionType::Auth) => Ok((None, Some(amount))),
                Some(NuveiTransactionType::Auth3D) => {
                    Ok((Some(MinorUnit::get_amount_as_i64(amount)), None))
                }
                Some(NuveiTransactionType::InitAuth3D) => Ok((None, Some(amount))),
                Some(NuveiTransactionType::Credit) => Ok((None, None)),
                Some(NuveiTransactionType::Void) => Ok((None, None)),
                Some(NuveiTransactionType::Settle) => Ok((None, None)),
            }
        }
        None => Ok((None, None)),
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum NuveiTransactionType {
    Auth,
    Sale,
    Credit,
    Auth3D,
    InitAuth3D,
    Settle,
    Void,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FraudDetails {
    pub final_decision: String,
}

fn get_payment_status(
    amount: Option<i64>,
    is_post_capture_void: bool,
    transaction_type: Option<NuveiTransactionType>,
    transaction_status: Option<NuveiTransactionStatus>,
    status: NuveiPaymentStatus,
) -> enums::AttemptStatus {
    // ZERO dollar authorization
    if amount == Some(0) && transaction_type == Some(NuveiTransactionType::Auth) {
        return match transaction_status {
            Some(NuveiTransactionStatus::Approved) => enums::AttemptStatus::Charged,
            Some(NuveiTransactionStatus::Declined) | Some(NuveiTransactionStatus::Error) => {
                enums::AttemptStatus::AuthorizationFailed
            }
            Some(NuveiTransactionStatus::Pending) | Some(NuveiTransactionStatus::Processing) => {
                enums::AttemptStatus::Pending
            }
            Some(NuveiTransactionStatus::Redirect) => enums::AttemptStatus::AuthenticationPending,
            None => match status {
                NuveiPaymentStatus::Failed | NuveiPaymentStatus::Error => {
                    enums::AttemptStatus::Failure
                }
                _ => enums::AttemptStatus::Pending,
            },
        };
    }

    match transaction_status {
        Some(status) => match status {
            NuveiTransactionStatus::Approved => match transaction_type {
                Some(NuveiTransactionType::InitAuth3D) | Some(NuveiTransactionType::Auth) => {
                    enums::AttemptStatus::Authorized
                }
                Some(NuveiTransactionType::Sale) | Some(NuveiTransactionType::Settle) => {
                    enums::AttemptStatus::Charged
                }
                Some(NuveiTransactionType::Void) if is_post_capture_void => {
                    enums::AttemptStatus::VoidedPostCharge
                }
                Some(NuveiTransactionType::Void) => enums::AttemptStatus::Voided,
                Some(NuveiTransactionType::Auth3D) => enums::AttemptStatus::AuthenticationPending,
                _ => enums::AttemptStatus::Pending,
            },
            NuveiTransactionStatus::Declined | NuveiTransactionStatus::Error => {
                match transaction_type {
                    Some(NuveiTransactionType::Auth) => enums::AttemptStatus::AuthorizationFailed,
                    Some(NuveiTransactionType::Void) => enums::AttemptStatus::VoidFailed,
                    Some(NuveiTransactionType::Auth3D) | Some(NuveiTransactionType::InitAuth3D) => {
                        enums::AttemptStatus::AuthenticationFailed
                    }
                    _ => enums::AttemptStatus::Failure,
                }
            }
            NuveiTransactionStatus::Processing | NuveiTransactionStatus::Pending => {
                enums::AttemptStatus::Pending
            }
            NuveiTransactionStatus::Redirect => enums::AttemptStatus::AuthenticationPending,
        },
        None => match status {
            NuveiPaymentStatus::Failed | NuveiPaymentStatus::Error => enums::AttemptStatus::Failure,
            _ => enums::AttemptStatus::Pending,
        },
    }
}

#[derive(Debug)]
struct ErrorResponseParams {
    http_code: u16,
    status: NuveiPaymentStatus,
    err_code: Option<i64>,
    err_msg: Option<String>,
    merchant_advice_code: Option<String>,
    gw_error_code: Option<i64>,
    gw_error_reason: Option<String>,
    transaction_status: Option<NuveiTransactionStatus>,
    transaction_id: Option<String>,
}

fn build_error_response(params: ErrorResponseParams) -> Option<ErrorResponse> {
    match params.status {
        NuveiPaymentStatus::Error => Some(get_error_response(
            params.err_code,
            params.err_msg.clone(),
            params.http_code,
            params.merchant_advice_code.clone(),
            params.gw_error_code.map(|code| code.to_string()),
            params.gw_error_reason.clone(),
            params.transaction_id.clone(),
        )),

        _ => {
            let err = Some(get_error_response(
                params.gw_error_code,
                params.gw_error_reason.clone(),
                params.http_code,
                params.merchant_advice_code,
                params.gw_error_code.map(|e| e.to_string()),
                params.gw_error_reason.clone(),
                params.transaction_id.clone(),
            ));

            match params.transaction_status {
                Some(NuveiTransactionStatus::Error) | Some(NuveiTransactionStatus::Declined) => err,
                _ => match params
                    .gw_error_reason
                    .as_ref()
                    .map(|r| r.eq("Missing argument"))
                {
                    Some(true) => err,
                    _ => None,
                },
            }
        }
    }
}

pub trait NuveiPaymentsGenericResponse {
    fn is_post_capture_void() -> bool {
        false
    }
}

impl NuveiPaymentsGenericResponse for CompleteAuthorize {}
impl NuveiPaymentsGenericResponse for Void {}
impl NuveiPaymentsGenericResponse for PSync {}
impl NuveiPaymentsGenericResponse for Capture {}
impl NuveiPaymentsGenericResponse for PostCaptureVoid {
    fn is_post_capture_void() -> bool {
        true
    }
}

impl
    TryFrom<
        ResponseRouterData<
            SetupMandate,
            NuveiPaymentsResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    > for RouterData<SetupMandate, SetupMandateRequestData, PaymentsResponseData>
{
    type Error = error_stack::Report<errors::ConnectorError>;
    fn try_from(
        item: ResponseRouterData<
            SetupMandate,
            NuveiPaymentsResponse,
            SetupMandateRequestData,
            PaymentsResponseData,
        >,
    ) -> Result<Self, Self::Error> {
        let amount = item.data.request.amount;
        let response = &item.response;
        let (status, redirection_data, connector_response_data) = process_nuvei_payment_response(
            NuveiPaymentResponseData::new(amount, false, item.data.payment_method, response),
        )?;

        let (amount_captured, minor_amount_capturable) = get_amount_captured(
            response.partial_approval.clone(),
            response.transaction_type.clone(),
        )?;

        let ip_address = item
            .data
            .request
            .browser_info
            .as_ref()
            .ok_or_else(|| errors::ConnectorError::MissingRequiredField {
                field_name: "browser_info",
            })?
            .ip_address
            .as_ref()
            .ok_or_else(|| errors::ConnectorError::MissingRequiredField {
                field_name: "browser_info.ip_address",
            })?
            .to_string();
        let response = &item.response;

        Ok(Self {
            status,
            response: if let Some(err) = build_error_response(ErrorResponseParams {
                http_code: item.http_code,
                status: response.status.clone(),
                err_code: response.err_code,
                err_msg: response.reason.clone(),
                merchant_advice_code: response.merchant_advice_code.clone(),


// Struct: ItaubankRefundRequest
// File: crates/hyperswitch_connectors/src/connectors/itaubank/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ItaubankRefundRequest


// Struct: ApplepayVerifiedDomainsResponse
// File: crates/api_models/src/verifications.rs
// Module: api_models
// Implementations: 0

pub struct ApplepayVerifiedDomainsResponse


// Function: non_enum_error
// File: crates/router_derive/src/macros/helpers.rs
// Module: router_derive

pub fn non_enum_error() -> syn::Error


// Function: call_locker_api
// File: crates/router/src/core/payment_methods/cards.rs
// Module: router

pub fn call_locker_api<T>(
    state: &routes::SessionState,
    request: Request,
    flow_name: &str,
    locker_choice: Option<api_enums::LockerChoice>,
) -> errors::CustomResult<T, errors::VaultError>
where
    T: serde::de::DeserializeOwned,


// Implementation: impl PaymentMethodsState
// File: crates/payment_methods/src/state.rs
// Module: payment_methods
// Methods: 1 total (0 public)

impl PaymentMethodsState


// Function: list_de_euclid_routing_algorithms
// File: crates/router/src/core/payments/routing/utils.rs
// Module: router

pub fn list_de_euclid_routing_algorithms(
    state: &SessionState,
    routing_list_request: ListRountingAlgorithmsRequest,
) -> RoutingResult<Vec<api_routing::RoutingDictionaryRecord>>


// Implementation: impl ConnectorCommon for for Globepay
// File: crates/hyperswitch_connectors/src/connectors/globepay.rs
// Module: hyperswitch_connectors
// Methods: 4 total (0 public)

impl ConnectorCommon for for Globepay


// Implementation: impl Default for for Database
// File: crates/drainer/src/settings.rs
// Module: drainer
// Methods: 1 total (0 public)

impl Default for for Database


// Struct: StripeDisputeResponse
// File: crates/router/src/compatibility/stripe/webhooks.rs
// Module: router
// Implementations: 0

pub struct StripeDisputeResponse


// File: crates/hyperswitch_domain_models/src/disputes.rs
// Module: hyperswitch_domain_models
// Public structs: 1

use crate::errors;

pub struct DisputeListConstraints {
    pub dispute_id: Option<String>,
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    pub limit: Option<u32>,
    pub offset: Option<u32>,
    pub profile_id: Option<Vec<common_utils::id_type::ProfileId>>,
    pub dispute_status: Option<Vec<common_enums::DisputeStatus>>,
    pub dispute_stage: Option<Vec<common_enums::DisputeStage>>,
    pub reason: Option<String>,
    pub connector: Option<Vec<String>>,
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    pub currency: Option<Vec<common_enums::Currency>>,
    pub time_range: Option<common_utils::types::TimeRange>,
}

impl
    TryFrom<(
        api_models::disputes::DisputeListGetConstraints,
        Option<Vec<common_utils::id_type::ProfileId>>,
    )> for DisputeListConstraints
{
    type Error = error_stack::Report<errors::api_error_response::ApiErrorResponse>;
    fn try_from(
        (value, auth_profile_id_list): (
            api_models::disputes::DisputeListGetConstraints,
            Option<Vec<common_utils::id_type::ProfileId>>,
        ),
    ) -> Result<Self, Self::Error> {
        let api_models::disputes::DisputeListGetConstraints {
            dispute_id,
            payment_id,
            limit,
            offset,
            profile_id,
            dispute_status,
            dispute_stage,
            reason,
            connector,
            merchant_connector_id,
            currency,
            time_range,
        } = value;
        let profile_id_from_request_body = profile_id;
        // Match both the profile ID from the request body and the list of authenticated profile IDs coming from auth layer
        let profile_id_list = match (profile_id_from_request_body, auth_profile_id_list) {
            (None, None) => None,
            // Case when the request body profile ID is None, but authenticated profile IDs are available, return the auth list
            (None, Some(auth_profile_id_list)) => Some(auth_profile_id_list),
            // Case when the request body profile ID is provided, but the auth list is None, create a vector with the request body profile ID
            (Some(profile_id_from_request_body), None) => Some(vec![profile_id_from_request_body]),
            (Some(profile_id_from_request_body), Some(auth_profile_id_list)) => {
                // Check if the profile ID from the request body is present in the authenticated profile ID list
                let profile_id_from_request_body_is_available_in_auth_profile_id_list =
                    auth_profile_id_list.contains(&profile_id_from_request_body);

                if profile_id_from_request_body_is_available_in_auth_profile_id_list {
                    Some(vec![profile_id_from_request_body])
                } else {
                    // If the profile ID is not valid, return an error indicating access is not available
                    return Err(error_stack::Report::new(
                        errors::api_error_response::ApiErrorResponse::PreconditionFailed {
                            message: format!(
                                "Access not available for the given profile_id {profile_id_from_request_body:?}",

                            ),
                        },
                    ));
                }
            }
        };

        Ok(Self {
            dispute_id,
            payment_id,
            limit,
            offset,
            profile_id: profile_id_list,
            dispute_status,
            dispute_stage,
            reason,
            connector,
            merchant_connector_id,
            currency,
            time_range,
        })
    }
}


// Implementation: impl ClientSecretFetch for for PaymentMethodCreate
// File: crates/router/src/services/authentication.rs
// Module: router
// Methods: 1 total (0 public)

impl ClientSecretFetch for for PaymentMethodCreate


// Implementation: impl RetryFeatureData
// File: crates/common_types/src/domain.rs
// Module: common_types
// Methods: 4 total (4 public)

impl RetryFeatureData


// Function: update_with_attempt_id
// File: crates/diesel_models/src/query/payout_attempt.rs
// Module: diesel_models

pub fn update_with_attempt_id(
        self,
        conn: &PgPooledConn,
        payout_attempt_update: PayoutAttemptUpdate,
    ) -> StorageResult<Self>


// Struct: ProfileLevel
// File: crates/router/src/types/domain/user.rs
// Module: router
// Implementations: 0

pub struct ProfileLevel


// Function: new
// File: crates/router/src/routes/dummy_connector/types.rs
// Module: router

pub fn new(
        status: DummyConnectorStatus,
        id: String,
        currency: Currency,
        created: PrimitiveDateTime,
        payment_amount: i64,
        refund_amount: i64,
    ) -> Self


// Struct: ProcessTracker
// File: crates/router/src/routes/app.rs
// Module: router
// Implementations: 1

pub struct ProcessTracker


// Function: construct_payment_router_data_for_authorize
// File: crates/router/src/core/payments/transformers.rs
// Module: router

pub fn construct_payment_router_data_for_authorize<'a>(
    state: &'a SessionState,
    payment_data: hyperswitch_domain_models::payments::PaymentConfirmData<api::Authorize>,
    connector_id: &str,
    merchant_context: &domain::MerchantContext,
    customer: &'a Option<domain::Customer>,
    merchant_connector_account: &domain::MerchantConnectorAccountTypeDetails,
    _merchant_recipient_data: Option<types::MerchantRecipientData>,
    header_payload: Option<hyperswitch_domain_models::payments::HeaderPayload>,
) -> RouterResult<types::PaymentsAuthorizeRouterData>


// File: crates/openapi/src/routes/customers.rs
// Module: openapi
// Public functions: 10

/// Customers - Create
///
/// Creates a customer object and stores the customer details to be reused for future payments.
/// Incase the customer already exists in the system, this API will respond with the customer details.
#[utoipa::path(
    post,
    path = "/customers",
    request_body  (
        content = CustomerRequest,
        examples  (( "Update name and email of a customer" =(
        value =json!( {
            "email": "guest@example.com",
            "name": "John Doe"
        })
        )))
    ),
    responses(
        (status = 200, description = "Customer Created", body = CustomerResponse),
        (status = 400, description = "Invalid data")

    ),
    tag = "Customers",
    operation_id = "Create a Customer",
    security(("api_key" = []))
)]
#[cfg(feature = "v1")]
pub async fn customers_create() {}

/// Customers - Retrieve
///
/// Retrieves a customer's details.
#[utoipa::path(
    get,
    path = "/customers/{customer_id}",
    params (("customer_id" = String, Path, description = "The unique identifier for the Customer")),
    responses(
        (status = 200, description = "Customer Retrieved", body = CustomerResponse),
        (status = 404, description = "Customer was not found")
    ),
    tag = "Customers",
    operation_id = "Retrieve a Customer",
    security(("api_key" = []), ("ephemeral_key" = []))
)]
#[cfg(feature = "v1")]
pub async fn customers_retrieve() {}

/// Customers - Update
///
/// Updates the customer's details in a customer object.
#[utoipa::path(
    post,
    path = "/customers/{customer_id}",
    request_body (
        content = CustomerUpdateRequest,
        examples  (( "Update name and email of a customer" =(
        value =json!( {
            "email": "guest@example.com",
            "name": "John Doe"
        })
        )))
    ),
    params (("customer_id" = String, Path, description = "The unique identifier for the Customer")),
    responses(
        (status = 200, description = "Customer was Updated", body = CustomerResponse),
        (status = 404, description = "Customer was not found")
    ),
    tag = "Customers",
    operation_id = "Update a Customer",
    security(("api_key" = []))
)]
#[cfg(feature = "v1")]
pub async fn customers_update() {}

/// Customers - Delete
///
/// Delete a customer record.
#[utoipa::path(
    delete,
    path = "/customers/{customer_id}",
    params (("customer_id" = String, Path, description = "The unique identifier for the Customer")),
    responses(
        (status = 200, description = "Customer was Deleted", body = CustomerDeleteResponse),
        (status = 404, description = "Customer was not found")
    ),
    tag = "Customers",
    operation_id = "Delete a Customer",
    security(("api_key" = []))
)]
#[cfg(feature = "v1")]
pub async fn customers_delete() {}

/// Customers - List
///
/// Lists all the customers for a particular merchant id.
#[utoipa::path(
    get,
    path = "/customers/list",
    params (("offset" = Option<u32>, Query, description = "Offset for pagination"),
    ("limit" = Option<u16>, Query, description = "Limit for pagination")),
    responses(
        (status = 200, description = "Customers retrieved", body = Vec<CustomerResponse>),
        (status = 400, description = "Invalid Data"),
    ),
    tag = "Customers",
    operation_id = "List all Customers for a Merchant",
    security(("api_key" = []))
)]
#[cfg(feature = "v1")]
pub async fn customers_list() {}

/// Customers - Create
///
/// Creates a customer object and stores the customer details to be reused for future payments.
/// Incase the customer already exists in the system, this API will respond with the customer details.
#[utoipa::path(
    post,
    path = "/v2/customers",
    request_body  (
        content = CustomerRequest,
        examples  (( "Create a customer with name and email" =(
        value =json!( {
            "email": "guest@example.com",
            "name": "John Doe"
        })
        )))
    ),
    responses(
        (status = 200, description = "Customer Created", body = CustomerResponse),
        (status = 400, description = "Invalid data")

    ),
    tag = "Customers",
    operation_id = "Create a Customer",
    security(("api_key" = []))
)]
#[cfg(feature = "v2")]
pub async fn customers_create() {}

/// Customers - Retrieve
///
/// Retrieves a customer's details.
#[utoipa::path(
    get,
    path = "/v2/customers/{id}",
    params (("id" = String, Path, description = "The unique identifier for the Customer")),
    responses(
        (status = 200, description = "Customer Retrieved", body = CustomerResponse),
        (status = 404, description = "Customer was not found")
    ),
    tag = "Customers",
    operation_id = "Retrieve a Customer",
    security(("api_key" = []), ("ephemeral_key" = []))
)]
#[cfg(feature = "v2")]
pub async fn customers_retrieve() {}

/// Customers - Update
///
/// Updates the customer's details in a customer object.
#[utoipa::path(
    post,
    path = "/v2/customers/{id}",
    request_body (
        content = CustomerUpdateRequest,
        examples  (( "Update name and email of a customer" =(
        value =json!( {
            "email": "guest@example.com",
            "name": "John Doe"
        })
        )))
    ),
    params (("id" = String, Path, description = "The unique identifier for the Customer")),
    responses(
        (status = 200, description = "Customer was Updated", body = CustomerResponse),
        (status = 404, description = "Customer was not found")
    ),
    tag = "Customers",
    operation_id = "Update a Customer",
    security(("api_key" = []))
)]
#[cfg(feature = "v2")]
pub async fn customers_update() {}

/// Customers - Delete
///
/// Delete a customer record.
#[utoipa::path(
    delete,
    path = "/v2/customers/{id}",
    params (("id" = String, Path, description = "The unique identifier for the Customer")),
    responses(
        (status = 200, description = "Customer was Deleted", body = CustomerDeleteResponse),
        (status = 404, description = "Customer was not found")
    ),
    tag = "Customers",
    operation_id = "Delete a Customer",
    security(("api_key" = []))
)]
#[cfg(feature = "v2")]
pub async fn customers_delete() {}

/// Customers - List
///
/// Lists all the customers for a particular merchant id.
#[utoipa::path(
    get,
    path = "/v2/customers/list",
    responses(
        (status = 200, description = "Customers retrieved", body = Vec<CustomerResponse>),
        (status = 400, description = "Invalid Data"),
    ),
    tag = "Customers",
    operation_id = "List all Customers for a Merchant",
    security(("api_key" = []))
)]
#[cfg(feature = "v2")]
pub async fn customers_list() {}


// Struct: Profile
// File: crates/router/src/routes/app.rs
// Module: router
// Implementations: 2

pub struct Profile


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

impl api::Payment for for Ebanx


// Struct: HyperswitchAiInteractionNew
// File: crates/diesel_models/src/hyperswitch_ai_interaction.rs
// Module: diesel_models
// Implementations: 0

pub struct HyperswitchAiInteractionNew


// Function: new
// File: crates/hyperswitch_connectors/src/connectors/sift.rs
// Module: hyperswitch_connectors

pub fn new() -> &'static Self


// File: crates/analytics/src/sdk_events/metrics/payment_data_filled_count.rs
// Module: analytics

use std::collections::HashSet;

use api_models::analytics::{
    sdk_events::{
        SdkEventDimensions, SdkEventFilters, SdkEventMetricsBucketIdentifier, SdkEventNames,
    },
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

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

#[async_trait::async_trait]
impl<T> super::SdkEventMetric<T> for PaymentDataFilledCount
where
    T: AnalyticsDataSource + super::SdkEventMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[SdkEventDimensions],
        publishable_key: &str,
        filters: &SdkEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(SdkEventMetricsBucketIdentifier, SdkEventMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::SdkEventsAnalytics);
        let dimensions = dimensions.to_vec();

        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()?;

        if let Some(granularity) = granularity {
            query_builder
                .add_granularity_in_mins(granularity)
                .switch()?;
        }

        filters.set_filter_clause(&mut query_builder).switch()?;

        query_builder
            .add_filter_clause("merchant_id", publishable_key)
            .switch()?;

        query_builder
            .add_bool_filter_clause("first_event", 1)
            .switch()?;

        query_builder
            .add_filter_clause("event_name", SdkEventNames::PaymentDataFilled)
            .switch()?;

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .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.as_ref() {
            query_builder
                .add_group_by_clause("time_bucket")
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<SdkEventMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    SdkEventMetricsBucketIdentifier::new(
                        i.payment_method.clone(),
                        i.platform.clone(),
                        i.browser_name.clone(),
                        i.source.clone(),
                        i.component.clone(),
                        i.payment_experience.clone(),
                        i.time_bucket.clone(),
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(SdkEventMetricsBucketIdentifier, SdkEventMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// Struct: Nexinets
// File: crates/hyperswitch_connectors/src/connectors/nexinets.rs
// Module: hyperswitch_connectors
// Implementations: 18
// Traits: api::Payment, api::PaymentSession, api::ConnectorAccessToken, api::MandateSetup, api::PaymentAuthorize, api::PaymentSync, api::PaymentCapture, api::PaymentVoid, api::Refund, api::RefundExecute, api::RefundSync, ConnectorCommon, ConnectorValidation, IncomingWebhook, api::PaymentToken, ConnectorSpecifications, ConnectorTransactionId

pub struct Nexinets

api::Payment api::PaymentSession api::ConnectorAccessToken api::MandateSetup api::PaymentAuthorize api::PaymentSync api::PaymentCapture api::PaymentVoid api::Refund api::RefundExecute api::RefundSync ConnectorCommon ConnectorValidation IncomingWebhook api::PaymentToken ConnectorSpecifications ConnectorTransactionId


// Struct: GlobalpayRefreshTokenResponse
// File: crates/hyperswitch_connectors/src/connectors/globalpay/response.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct GlobalpayRefreshTokenResponse


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

pub struct WaitScreenData


// Struct: DwollaRefundsRequest
// File: crates/hyperswitch_connectors/src/connectors/dwolla/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct DwollaRefundsRequest


// Implementation: impl PaymentMethodsStorageInterface for for KVRouterStore
// File: crates/payment_methods/src/state.rs
// Module: payment_methods
// Methods: 0 total (0 public)

impl PaymentMethodsStorageInterface for for KVRouterStore


// Struct: GsmUpdateRequest
// File: crates/api_models/src/gsm.rs
// Module: api_models
// Implementations: 0

pub struct GsmUpdateRequest


// Function: insert_cvc_using_payment_token
// File: crates/router/src/core/payment_methods/vault.rs
// Module: router

pub fn insert_cvc_using_payment_token(
    state: &routes::SessionState,
    payment_token: &String,
    payment_method_data: api_models::payment_methods::PaymentMethodCreateData,
    payment_method: common_enums::PaymentMethod,
    fulfillment_time: i64,
    encryption_key: &masking::Secret<Vec<u8>>,
) -> RouterResult<()>