// File: crates/diesel_models/src/query/payment_method.rs

use async_bb8_diesel::AsyncRunQueryDsl;
#[cfg(feature = "v1")]
use diesel::Table;
use diesel::{
    associations::HasTable, debug_query, pg::Pg, BoolExpressionMethods, ExpressionMethods, QueryDsl,
};
use error_stack::ResultExt;

use super::generics;
#[cfg(feature = "v1")]
use crate::schema::payment_methods::dsl;
#[cfg(feature = "v2")]
use crate::schema_v2::payment_methods::dsl::{self, id as pm_id};
use crate::{
    enums as storage_enums, errors,
    payment_method::{self, PaymentMethod, PaymentMethodNew},
    PgPooledConn, StorageResult,
};

impl PaymentMethodNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<PaymentMethod> {
        generics::generic_insert(conn, self).await
    }
}

#[cfg(feature = "v1")]
impl PaymentMethod {
    pub async fn delete_by_payment_method_id(
        conn: &PgPooledConn,
        payment_method_id: String,
    ) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, Self>(
            conn,
            dsl::payment_method_id.eq(payment_method_id),
        )
        .await
    }

    pub async fn delete_by_merchant_id_payment_method_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        payment_method_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, Self>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::payment_method_id.eq(payment_method_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_locker_id(conn: &PgPooledConn, locker_id: &str) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::locker_id.eq(locker_id.to_owned()),
        )
        .await
    }

    pub async fn find_by_payment_method_id(
        conn: &PgPooledConn,
        payment_method_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::payment_method_id.eq(payment_method_id.to_owned()),
        )
        .await
    }

    pub async fn find_by_merchant_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id.eq(merchant_id.to_owned()),
            None,
            None,
            None,
        )
        .await
    }

    pub async fn find_by_customer_id_merchant_id(
        conn: &PgPooledConn,
        customer_id: &common_utils::id_type::CustomerId,
        merchant_id: &common_utils::id_type::MerchantId,
        limit: Option<i64>,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::customer_id
                .eq(customer_id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
            limit,
            None,
            Some(dsl::last_used_at.desc()),
        )
        .await
    }

    pub async fn get_count_by_customer_id_merchant_id_status(
        conn: &PgPooledConn,
        customer_id: &common_utils::id_type::CustomerId,
        merchant_id: &common_utils::id_type::MerchantId,
        status: common_enums::PaymentMethodStatus,
    ) -> StorageResult<i64> {
        let filter = <Self as HasTable>::table()
            .count()
            .filter(
                dsl::customer_id
                    .eq(customer_id.to_owned())
                    .and(dsl::merchant_id.eq(merchant_id.to_owned()))
                    .and(dsl::status.eq(status.to_owned())),
            )
            .into_boxed();

        router_env::logger::debug!(query = %debug_query::<Pg, _>(&filter).to_string());

        generics::db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>(
            filter.get_result_async::<i64>(conn),
            generics::db_metrics::DatabaseOperation::Count,
        )
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Failed to get a count of payment methods")
    }

    pub async fn get_count_by_merchant_id_status(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        status: common_enums::PaymentMethodStatus,
    ) -> StorageResult<i64> {
        let query = <Self as HasTable>::table().count().filter(
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::status.eq(status.to_owned())),
        );

        router_env::logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

        generics::db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>(
            query.get_result_async::<i64>(conn),
            generics::db_metrics::DatabaseOperation::Count,
        )
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Failed to get a count of payment methods")
    }

    pub async fn find_by_customer_id_merchant_id_status(
        conn: &PgPooledConn,
        customer_id: &common_utils::id_type::CustomerId,
        merchant_id: &common_utils::id_type::MerchantId,
        status: storage_enums::PaymentMethodStatus,
        limit: Option<i64>,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::customer_id
                .eq(customer_id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned()))
                .and(dsl::status.eq(status)),
            limit,
            None,
            Some(dsl::last_used_at.desc()),
        )
        .await
    }

    pub async fn update_with_payment_method_id(
        self,
        conn: &PgPooledConn,
        payment_method: payment_method::PaymentMethodUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::payment_method_id.eq(self.payment_method_id.to_owned()),
            payment_method,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }
}

#[cfg(feature = "v2")]
impl PaymentMethod {
    pub async fn find_by_id(
        conn: &PgPooledConn,
        id: &common_utils::id_type::GlobalPaymentMethodId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(conn, pm_id.eq(id.to_owned()))
            .await
    }

    pub async fn find_by_global_customer_id_merchant_id_status(
        conn: &PgPooledConn,
        customer_id: &common_utils::id_type::GlobalCustomerId,
        merchant_id: &common_utils::id_type::MerchantId,
        status: storage_enums::PaymentMethodStatus,
        limit: Option<i64>,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::customer_id
                .eq(customer_id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned()))
                .and(dsl::status.eq(status)),
            limit,
            None,
            Some(dsl::last_used_at.desc()),
        )
        .await
    }

    pub async fn find_by_global_customer_id(
        conn: &PgPooledConn,
        customer_id: &common_utils::id_type::GlobalCustomerId,
        limit: Option<i64>,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::customer_id.eq(customer_id.to_owned()),
            limit,
            None,
            Some(dsl::last_used_at.desc()),
        )
        .await
    }

    pub async fn update_with_id(
        self,
        conn: &PgPooledConn,
        payment_method: payment_method::PaymentMethodUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(conn, pm_id.eq(self.id.to_owned()), payment_method)
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn find_by_fingerprint_id(
        conn: &PgPooledConn,
        fingerprint_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::locker_fingerprint_id.eq(fingerprint_id.to_owned()),
        )
        .await
    }

    pub async fn get_count_by_merchant_id_status(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        status: common_enums::PaymentMethodStatus,
    ) -> StorageResult<i64> {
        let query = <Self as HasTable>::table().count().filter(
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::status.eq(status.to_owned())),
        );

        router_env::logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

        generics::db_metrics::track_database_call::<<Self as HasTable>::Table, _, _>(
            query.get_result_async::<i64>(conn),
            generics::db_metrics::DatabaseOperation::Count,
        )
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Failed to get a count of payment methods")
    }
}


// File: crates/diesel_models/src/query/merchant_connector_account.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, Table};

use super::generics;
#[cfg(feature = "v1")]
use crate::schema::merchant_connector_account::dsl;
#[cfg(feature = "v2")]
use crate::schema_v2::merchant_connector_account::dsl;
use crate::{
    errors,
    merchant_connector_account::{
        MerchantConnectorAccount, MerchantConnectorAccountNew,
        MerchantConnectorAccountUpdateInternal,
    },
    PgPooledConn, StorageResult,
};

impl MerchantConnectorAccountNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<MerchantConnectorAccount> {
        generics::generic_insert(conn, self).await
    }
}

#[cfg(feature = "v1")]
impl MerchantConnectorAccount {
    pub async fn update(
        self,
        conn: &PgPooledConn,
        merchant_connector_account: MerchantConnectorAccountUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            self.merchant_connector_id.to_owned(),
            merchant_connector_account,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn delete_by_merchant_id_merchant_connector_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        merchant_connector_id: &common_utils::id_type::MerchantConnectorAccountId,
    ) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::merchant_connector_id.eq(merchant_connector_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_merchant_id_connector(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_label: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::connector_label.eq(connector_label.to_owned())),
        )
        .await
    }

    pub async fn find_by_profile_id_connector_name(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        connector_name: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::profile_id
                .eq(profile_id.to_owned())
                .and(dsl::connector_name.eq(connector_name.to_owned())),
        )
        .await
    }

    pub async fn find_by_merchant_id_connector_name(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_name: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::connector_name.eq(connector_name.to_owned())),
            None,
            None,
            None,
        )
        .await
    }

    pub async fn find_by_merchant_id_merchant_connector_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        merchant_connector_id: &common_utils::id_type::MerchantConnectorAccountId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::merchant_connector_id.eq(merchant_connector_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_merchant_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        get_disabled: bool,
    ) -> StorageResult<Vec<Self>> {
        if get_disabled {
            generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
                conn,
                dsl::merchant_id.eq(merchant_id.to_owned()),
                None,
                None,
                Some(dsl::created_at.asc()),
            )
            .await
        } else {
            generics::generic_filter::<
                <Self as HasTable>::Table,
                _,
                <<Self as HasTable>::Table as Table>::PrimaryKey,
                _,
            >(
                conn,
                dsl::merchant_id
                    .eq(merchant_id.to_owned())
                    .and(dsl::disabled.eq(false)),
                None,
                None,
                None,
            )
            .await
        }
    }

    pub async fn list_enabled_by_profile_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        connector_type: common_enums::ConnectorType,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::profile_id
                .eq(profile_id.to_owned())
                .and(dsl::disabled.eq(false))
                .and(dsl::connector_type.eq(connector_type)),
            None,
            None,
            Some(dsl::created_at.asc()),
        )
        .await
    }
}

#[cfg(feature = "v2")]
impl MerchantConnectorAccount {
    pub async fn update(
        self,
        conn: &PgPooledConn,
        merchant_connector_account: MerchantConnectorAccountUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            self.id.to_owned(),
            merchant_connector_account,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn delete_by_id(
        conn: &PgPooledConn,
        id: &common_utils::id_type::MerchantConnectorAccountId,
    ) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(conn, dsl::id.eq(id.to_owned()))
            .await
    }

    pub async fn find_by_id(
        conn: &PgPooledConn,
        id: &common_utils::id_type::MerchantConnectorAccountId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::id.eq(id.to_owned()),
        )
        .await
    }

    pub async fn find_by_merchant_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        get_disabled: bool,
    ) -> StorageResult<Vec<Self>> {
        if get_disabled {
            generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
                conn,
                dsl::merchant_id.eq(merchant_id.to_owned()),
                None,
                None,
                Some(dsl::created_at.asc()),
            )
            .await
        } else {
            generics::generic_filter::<
                <Self as HasTable>::Table,
                _,
                <<Self as HasTable>::Table as Table>::PrimaryKey,
                _,
            >(
                conn,
                dsl::merchant_id
                    .eq(merchant_id.to_owned())
                    .and(dsl::disabled.eq(false)),
                None,
                None,
                None,
            )
            .await
        }
    }

    pub async fn list_by_profile_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::profile_id.eq(profile_id.to_owned()),
            None,
            None,
            Some(dsl::created_at.asc()),
        )
        .await
    }

    pub async fn list_enabled_by_profile_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        connector_type: common_enums::ConnectorType,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::profile_id
                .eq(profile_id.to_owned())
                .and(dsl::disabled.eq(false))
                .and(dsl::connector_type.eq(connector_type)),
            None,
            None,
            Some(dsl::created_at.asc()),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/configs.rs

use diesel::{associations::HasTable, ExpressionMethods};

use super::generics;
use crate::{
    configs::{Config, ConfigNew, ConfigUpdate, ConfigUpdateInternal},
    errors,
    schema::configs::dsl,
    PgPooledConn, StorageResult,
};

impl ConfigNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Config> {
        generics::generic_insert(conn, self).await
    }
}

impl Config {
    pub async fn find_by_key(conn: &PgPooledConn, key: &str) -> StorageResult<Self> {
        generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(conn, key.to_owned()).await
    }

    pub async fn update_by_key(
        conn: &PgPooledConn,
        key: &str,
        config_update: ConfigUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            key.to_owned(),
            ConfigUpdateInternal::from(config_update),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => {
                    generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(
                        conn,
                        key.to_owned(),
                    )
                    .await
                }
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn delete_by_key(conn: &PgPooledConn, key: &str) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::key.eq(key.to_owned()),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/blocklist_fingerprint.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};

use super::generics;
use crate::{
    blocklist_fingerprint::{BlocklistFingerprint, BlocklistFingerprintNew},
    schema::blocklist_fingerprint::dsl,
    PgPooledConn, StorageResult,
};

impl BlocklistFingerprintNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<BlocklistFingerprint> {
        generics::generic_insert(conn, self).await
    }
}

impl BlocklistFingerprint {
    pub async fn find_by_merchant_id_fingerprint_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        fingerprint_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::fingerprint_id.eq(fingerprint_id.to_owned())),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/routing_algorithm.rs

use async_bb8_diesel::AsyncRunQueryDsl;
use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, QueryDsl};
use error_stack::{report, ResultExt};
use time::PrimitiveDateTime;

use crate::{
    enums,
    errors::DatabaseError,
    query::generics,
    routing_algorithm::{RoutingAlgorithm, RoutingProfileMetadata},
    schema::routing_algorithm::dsl,
    PgPooledConn, StorageResult,
};

impl RoutingAlgorithm {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Self> {
        generics::generic_insert(conn, self).await
    }

    pub async fn find_by_algorithm_id_merchant_id(
        conn: &PgPooledConn,
        algorithm_id: &common_utils::id_type::RoutingId,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::algorithm_id
                .eq(algorithm_id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_algorithm_id_profile_id(
        conn: &PgPooledConn,
        algorithm_id: &common_utils::id_type::RoutingId,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::algorithm_id
                .eq(algorithm_id.to_owned())
                .and(dsl::profile_id.eq(profile_id.to_owned())),
        )
        .await
    }

    pub async fn find_metadata_by_algorithm_id_profile_id(
        conn: &PgPooledConn,
        algorithm_id: &common_utils::id_type::RoutingId,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> StorageResult<RoutingProfileMetadata> {
        Self::table()
            .select((
                dsl::profile_id,
                dsl::algorithm_id,
                dsl::name,
                dsl::description,
                dsl::kind,
                dsl::created_at,
                dsl::modified_at,
                dsl::algorithm_for,
            ))
            .filter(
                dsl::algorithm_id
                    .eq(algorithm_id.to_owned())
                    .and(dsl::profile_id.eq(profile_id.to_owned())),
            )
            .limit(1)
            .load_async::<(
                common_utils::id_type::ProfileId,
                common_utils::id_type::RoutingId,
                String,
                Option<String>,
                enums::RoutingAlgorithmKind,
                PrimitiveDateTime,
                PrimitiveDateTime,
                enums::TransactionType,
            )>(conn)
            .await
            .change_context(DatabaseError::Others)?
            .into_iter()
            .next()
            .ok_or(report!(DatabaseError::NotFound))
            .map(
                |(
                    profile_id,
                    algorithm_id,
                    name,
                    description,
                    kind,
                    created_at,
                    modified_at,
                    algorithm_for,
                )| {
                    RoutingProfileMetadata {
                        profile_id,
                        algorithm_id,
                        name,
                        description,
                        kind,
                        created_at,
                        modified_at,
                        algorithm_for,
                    }
                },
            )
    }

    pub async fn list_metadata_by_profile_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        limit: i64,
        offset: i64,
    ) -> StorageResult<Vec<RoutingProfileMetadata>> {
        Ok(Self::table()
            .select((
                dsl::algorithm_id,
                dsl::profile_id,
                dsl::name,
                dsl::description,
                dsl::kind,
                dsl::created_at,
                dsl::modified_at,
                dsl::algorithm_for,
            ))
            .filter(dsl::profile_id.eq(profile_id.to_owned()))
            .limit(limit)
            .offset(offset)
            .load_async::<(
                common_utils::id_type::RoutingId,
                common_utils::id_type::ProfileId,
                String,
                Option<String>,
                enums::RoutingAlgorithmKind,
                PrimitiveDateTime,
                PrimitiveDateTime,
                enums::TransactionType,
            )>(conn)
            .await
            .change_context(DatabaseError::Others)?
            .into_iter()
            .map(
                |(
                    algorithm_id,
                    profile_id,
                    name,
                    description,
                    kind,
                    created_at,
                    modified_at,
                    algorithm_for,
                )| {
                    RoutingProfileMetadata {
                        algorithm_id,
                        name,
                        description,
                        kind,
                        created_at,
                        modified_at,
                        algorithm_for,
                        profile_id,
                    }
                },
            )
            .collect())
    }

    pub async fn list_metadata_by_merchant_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        limit: i64,
        offset: i64,
    ) -> StorageResult<Vec<RoutingProfileMetadata>> {
        Ok(Self::table()
            .select((
                dsl::profile_id,
                dsl::algorithm_id,
                dsl::name,
                dsl::description,
                dsl::kind,
                dsl::created_at,
                dsl::modified_at,
                dsl::algorithm_for,
            ))
            .filter(dsl::merchant_id.eq(merchant_id.to_owned()))
            .limit(limit)
            .offset(offset)
            .order(dsl::modified_at.desc())
            .load_async::<(
                common_utils::id_type::ProfileId,
                common_utils::id_type::RoutingId,
                String,
                Option<String>,
                enums::RoutingAlgorithmKind,
                PrimitiveDateTime,
                PrimitiveDateTime,
                enums::TransactionType,
            )>(conn)
            .await
            .change_context(DatabaseError::Others)?
            .into_iter()
            .map(
                |(
                    profile_id,
                    algorithm_id,
                    name,
                    description,
                    kind,
                    created_at,
                    modified_at,
                    algorithm_for,
                )| {
                    RoutingProfileMetadata {
                        profile_id,
                        algorithm_id,
                        name,
                        description,
                        kind,
                        created_at,
                        modified_at,
                        algorithm_for,
                    }
                },
            )
            .collect())
    }

    pub async fn list_metadata_by_merchant_id_transaction_type(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        transaction_type: &enums::TransactionType,
        limit: i64,
        offset: i64,
    ) -> StorageResult<Vec<RoutingProfileMetadata>> {
        Ok(Self::table()
            .select((
                dsl::profile_id,
                dsl::algorithm_id,
                dsl::name,
                dsl::description,
                dsl::kind,
                dsl::created_at,
                dsl::modified_at,
                dsl::algorithm_for,
            ))
            .filter(dsl::merchant_id.eq(merchant_id.to_owned()))
            .filter(dsl::algorithm_for.eq(transaction_type.to_owned()))
            .limit(limit)
            .offset(offset)
            .order(dsl::modified_at.desc())
            .load_async::<(
                common_utils::id_type::ProfileId,
                common_utils::id_type::RoutingId,
                String,
                Option<String>,
                enums::RoutingAlgorithmKind,
                PrimitiveDateTime,
                PrimitiveDateTime,
                enums::TransactionType,
            )>(conn)
            .await
            .change_context(DatabaseError::Others)?
            .into_iter()
            .map(
                |(
                    profile_id,
                    algorithm_id,
                    name,
                    description,
                    kind,
                    created_at,
                    modified_at,
                    algorithm_for,
                )| {
                    RoutingProfileMetadata {
                        profile_id,
                        algorithm_id,
                        name,
                        description,
                        kind,
                        created_at,
                        modified_at,
                        algorithm_for,
                    }
                },
            )
            .collect())
    }
}


// File: crates/diesel_models/src/query/subscription.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
use error_stack::report;

use super::generics;
use crate::{
    errors,
    schema::subscription::dsl,
    subscription::{Subscription, SubscriptionNew, SubscriptionUpdate},
    PgPooledConn, StorageResult,
};

impl SubscriptionNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Subscription> {
        generics::generic_insert(conn, self).await
    }
}

impl Subscription {
    pub async fn find_by_merchant_id_subscription_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        id: String,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::id.eq(id.to_owned())),
        )
        .await
    }

    pub async fn update_subscription_entry(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        id: String,
        subscription_update: SubscriptionUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_results::<
            <Self as HasTable>::Table,
            SubscriptionUpdate,
            _,
            _,
        >(
            conn,
            dsl::id
                .eq(id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
            subscription_update,
        )
        .await?
        .first()
        .cloned()
        .ok_or_else(|| {
            report!(errors::DatabaseError::NotFound)
                .attach_printable("Error while updating subscription entry")
        })
    }
}


// File: crates/diesel_models/src/query/address.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};

use super::generics;
use crate::{
    address::{Address, AddressNew, AddressUpdateInternal},
    errors,
    schema::address::dsl,
    PgPooledConn, StorageResult,
};

impl AddressNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Address> {
        generics::generic_insert(conn, self).await
    }
}

impl Address {
    pub async fn find_by_address_id(conn: &PgPooledConn, address_id: &str) -> StorageResult<Self> {
        generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(conn, address_id.to_owned())
            .await
    }

    pub async fn update_by_address_id(
        conn: &PgPooledConn,
        address_id: String,
        address: AddressUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            address_id.clone(),
            address,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NotFound => {
                    Err(error.attach_printable("Address with the given ID doesn't exist"))
                }
                errors::DatabaseError::NoFieldsToUpdate => {
                    generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(
                        conn,
                        address_id.clone(),
                    )
                    .await
                }
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn update(
        self,
        conn: &PgPooledConn,
        address_update_internal: AddressUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::address_id.eq(self.address_id.clone()),
            address_update_internal,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn delete_by_address_id(
        conn: &PgPooledConn,
        address_id: &str,
    ) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(
            conn,
            dsl::address_id.eq(address_id.to_owned()),
        )
        .await
    }

    pub async fn update_by_merchant_id_customer_id(
        conn: &PgPooledConn,
        customer_id: &common_utils::id_type::CustomerId,
        merchant_id: &common_utils::id_type::MerchantId,
        address: AddressUpdateInternal,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::customer_id.eq(customer_id.to_owned())),
            address,
        )
        .await
    }

    pub async fn find_by_merchant_id_payment_id_address_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        payment_id: &common_utils::id_type::PaymentId,
        address_id: &str,
    ) -> StorageResult<Self> {
        match generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::payment_id
                .eq(payment_id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned()))
                .and(dsl::address_id.eq(address_id.to_owned())),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NotFound => {
                    generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(
                        conn,
                        address_id.to_owned(),
                    )
                    .await
                }
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn find_optional_by_address_id(
        conn: &PgPooledConn,
        address_id: &str,
    ) -> StorageResult<Option<Self>> {
        generics::generic_find_by_id_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            address_id.to_owned(),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/blocklist_lookup.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};

use super::generics;
use crate::{
    blocklist_lookup::{BlocklistLookup, BlocklistLookupNew},
    schema::blocklist_lookup::dsl,
    PgPooledConn, StorageResult,
};

impl BlocklistLookupNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<BlocklistLookup> {
        generics::generic_insert(conn, self).await
    }
}

impl BlocklistLookup {
    pub async fn find_by_merchant_id_fingerprint(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        fingerprint: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::fingerprint.eq(fingerprint.to_owned())),
        )
        .await
    }

    pub async fn delete_by_merchant_id_fingerprint(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        fingerprint: &str,
    ) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::fingerprint.eq(fingerprint.to_owned())),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/blocklist.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};

use super::generics;
use crate::{
    blocklist::{Blocklist, BlocklistNew},
    schema::blocklist::dsl,
    PgPooledConn, StorageResult,
};

impl BlocklistNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Blocklist> {
        generics::generic_insert(conn, self).await
    }
}

impl Blocklist {
    pub async fn find_by_merchant_id_fingerprint_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        fingerprint_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::fingerprint_id.eq(fingerprint_id.to_owned())),
        )
        .await
    }

    pub async fn list_by_merchant_id_data_kind(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        data_kind: common_enums::BlocklistDataKind,
        limit: i64,
        offset: i64,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::data_kind.eq(data_kind.to_owned())),
            Some(limit),
            Some(offset),
            Some(dsl::created_at.desc()),
        )
        .await
    }

    pub async fn list_by_merchant_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::merchant_id.eq(merchant_id.to_owned()),
            None,
            None,
            Some(dsl::created_at.desc()),
        )
        .await
    }

    pub async fn delete_by_merchant_id_fingerprint_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        fingerprint_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::fingerprint_id.eq(fingerprint_id.to_owned())),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/user.rs

use common_utils::pii;
use diesel::{associations::HasTable, ExpressionMethods};

pub mod sample_data;
pub mod theme;

use crate::{
    query::generics, schema::users::dsl as users_dsl, user::*, PgPooledConn, StorageResult,
};

impl UserNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<User> {
        generics::generic_insert(conn, self).await
    }
}

impl User {
    pub async fn find_by_user_email(
        conn: &PgPooledConn,
        user_email: &pii::Email,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            users_dsl::email.eq(user_email.to_owned()),
        )
        .await
    }

    pub async fn find_by_user_id(conn: &PgPooledConn, user_id: &str) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            users_dsl::user_id.eq(user_id.to_owned()),
        )
        .await
    }

    pub async fn update_by_user_id(
        conn: &PgPooledConn,
        user_id: &str,
        user_update: UserUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            users_dsl::user_id.eq(user_id.to_owned()),
            UserUpdateInternal::from(user_update),
        )
        .await
    }

    pub async fn update_by_user_email(
        conn: &PgPooledConn,
        user_email: &pii::Email,
        user_update: UserUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            users_dsl::email.eq(user_email.to_owned()),
            UserUpdateInternal::from(user_update),
        )
        .await
    }

    pub async fn delete_by_user_id(conn: &PgPooledConn, user_id: &str) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(
            conn,
            users_dsl::user_id.eq(user_id.to_owned()),
        )
        .await
    }

    pub async fn find_users_by_user_ids(
        conn: &PgPooledConn,
        user_ids: Vec<String>,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as diesel::Table>::PrimaryKey,
            _,
        >(conn, users_dsl::user_id.eq_any(user_ids), None, None, None)
        .await
    }
}


// File: crates/diesel_models/src/query/user_role.rs

use async_bb8_diesel::AsyncRunQueryDsl;
use common_utils::id_type;
use diesel::{
    associations::HasTable,
    debug_query,
    pg::Pg,
    result::Error as DieselError,
    sql_types::{Bool, Nullable},
    BoolExpressionMethods, ExpressionMethods, QueryDsl,
};
use error_stack::{report, ResultExt};

use crate::{
    enums::{UserRoleVersion, UserStatus},
    errors,
    query::generics,
    schema::user_roles::dsl,
    user_role::*,
    PgPooledConn, StorageResult,
};

impl UserRoleNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<UserRole> {
        generics::generic_insert(conn, self).await
    }
}

impl UserRole {
    fn check_user_in_lineage(
        tenant_id: id_type::TenantId,
        org_id: Option<id_type::OrganizationId>,
        merchant_id: Option<id_type::MerchantId>,
        profile_id: Option<id_type::ProfileId>,
    ) -> Box<
        dyn diesel::BoxableExpression<<Self as HasTable>::Table, Pg, SqlType = Nullable<Bool>>
            + 'static,
    > {
        // Checking in user roles, for a user in token hierarchy, only one of the relations will be true:
        // either tenant level, org level, merchant level, or profile level
        // Tenant-level: (tenant_id = ? && org_id = null && merchant_id = null && profile_id = null)
        // Org-level: (org_id = ? && merchant_id = null && profile_id = null)
        // Merchant-level: (org_id = ? && merchant_id = ? && profile_id = null)
        // Profile-level: (org_id = ? && merchant_id = ? && profile_id = ?)
        Box::new(
            // Tenant-level condition
            dsl::tenant_id
                .eq(tenant_id.clone())
                .and(dsl::org_id.is_null())
                .and(dsl::merchant_id.is_null())
                .and(dsl::profile_id.is_null())
                .or(
                    // Org-level condition
                    dsl::tenant_id
                        .eq(tenant_id.clone())
                        .and(dsl::org_id.eq(org_id.clone()))
                        .and(dsl::merchant_id.is_null())
                        .and(dsl::profile_id.is_null()),
                )
                .or(
                    // Merchant-level condition
                    dsl::tenant_id
                        .eq(tenant_id.clone())
                        .and(dsl::org_id.eq(org_id.clone()))
                        .and(dsl::merchant_id.eq(merchant_id.clone()))
                        .and(dsl::profile_id.is_null()),
                )
                .or(
                    // Profile-level condition
                    dsl::tenant_id
                        .eq(tenant_id)
                        .and(dsl::org_id.eq(org_id))
                        .and(dsl::merchant_id.eq(merchant_id))
                        .and(dsl::profile_id.eq(profile_id)),
                ),
        )
    }

    pub async fn find_by_user_id_tenant_id_org_id_merchant_id_profile_id(
        conn: &PgPooledConn,
        user_id: String,
        tenant_id: id_type::TenantId,
        org_id: id_type::OrganizationId,
        merchant_id: id_type::MerchantId,
        profile_id: id_type::ProfileId,
        version: UserRoleVersion,
    ) -> StorageResult<Self> {
        let check_lineage = Self::check_user_in_lineage(
            tenant_id,
            Some(org_id),
            Some(merchant_id),
            Some(profile_id),
        );

        let predicate = dsl::user_id
            .eq(user_id)
            .and(check_lineage)
            .and(dsl::version.eq(version));

        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(conn, predicate).await
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn update_by_user_id_tenant_id_org_id_merchant_id_profile_id(
        conn: &PgPooledConn,
        user_id: String,
        tenant_id: id_type::TenantId,
        org_id: id_type::OrganizationId,
        merchant_id: Option<id_type::MerchantId>,
        profile_id: Option<id_type::ProfileId>,
        update: UserRoleUpdate,
        version: UserRoleVersion,
    ) -> StorageResult<Self> {
        let check_lineage = dsl::tenant_id
            .eq(tenant_id.clone())
            .and(dsl::org_id.is_null())
            .and(dsl::merchant_id.is_null())
            .and(dsl::profile_id.is_null())
            .or(
                // Org-level condition
                dsl::tenant_id
                    .eq(tenant_id.clone())
                    .and(dsl::org_id.eq(org_id.clone()))
                    .and(dsl::merchant_id.is_null())
                    .and(dsl::profile_id.is_null()),
            )
            .or(
                // Merchant-level condition
                dsl::tenant_id
                    .eq(tenant_id.clone())
                    .and(dsl::org_id.eq(org_id.clone()))
                    .and(dsl::merchant_id.eq(merchant_id.clone()))
                    .and(dsl::profile_id.is_null()),
            )
            .or(
                // Profile-level condition
                dsl::tenant_id
                    .eq(tenant_id)
                    .and(dsl::org_id.eq(org_id))
                    .and(dsl::merchant_id.eq(merchant_id))
                    .and(dsl::profile_id.eq(profile_id)),
            );

        let predicate = dsl::user_id
            .eq(user_id)
            .and(check_lineage)
            .and(dsl::version.eq(version));

        generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            UserRoleUpdateInternal,
            _,
            _,
        >(conn, predicate, update.into())
        .await
    }

    pub async fn delete_by_user_id_tenant_id_org_id_merchant_id_profile_id(
        conn: &PgPooledConn,
        user_id: String,
        tenant_id: id_type::TenantId,
        org_id: id_type::OrganizationId,
        merchant_id: id_type::MerchantId,
        profile_id: id_type::ProfileId,
        version: UserRoleVersion,
    ) -> StorageResult<Self> {
        let check_lineage = Self::check_user_in_lineage(
            tenant_id,
            Some(org_id),
            Some(merchant_id),
            Some(profile_id),
        );

        let predicate = dsl::user_id
            .eq(user_id)
            .and(check_lineage)
            .and(dsl::version.eq(version));

        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(conn, predicate)
            .await
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn generic_user_roles_list_for_user(
        conn: &PgPooledConn,
        user_id: String,
        tenant_id: id_type::TenantId,
        org_id: Option<id_type::OrganizationId>,
        merchant_id: Option<id_type::MerchantId>,
        profile_id: Option<id_type::ProfileId>,
        entity_id: Option<String>,
        status: Option<UserStatus>,
        version: Option<UserRoleVersion>,
        limit: Option<u32>,
    ) -> StorageResult<Vec<Self>> {
        let mut query = <Self as HasTable>::table()
            .filter(dsl::user_id.eq(user_id).and(dsl::tenant_id.eq(tenant_id)))
            .into_boxed();

        if let Some(org_id) = org_id {
            query = query.filter(dsl::org_id.eq(org_id));
        }

        if let Some(merchant_id) = merchant_id {
            query = query.filter(dsl::merchant_id.eq(merchant_id));
        }

        if let Some(profile_id) = profile_id {
            query = query.filter(dsl::profile_id.eq(profile_id));
        }

        if let Some(entity_id) = entity_id {
            query = query.filter(dsl::entity_id.eq(entity_id));
        }

        if let Some(version) = version {
            query = query.filter(dsl::version.eq(version));
        }

        if let Some(status) = status {
            query = query.filter(dsl::status.eq(status));
        }

        if let Some(limit) = limit {
            query = query.limit(limit.into());
        }

        router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());

        match generics::db_metrics::track_database_call::<Self, _, _>(
            query.get_results_async(conn),
            generics::db_metrics::DatabaseOperation::Filter,
        )
        .await
        {
            Ok(value) => Ok(value),
            Err(err) => match err {
                DieselError::NotFound => {
                    Err(report!(err)).change_context(errors::DatabaseError::NotFound)
                }
                _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
            },
        }
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn generic_user_roles_list_for_org_and_extra(
        conn: &PgPooledConn,
        user_id: Option<String>,
        tenant_id: id_type::TenantId,
        org_id: id_type::OrganizationId,
        merchant_id: Option<id_type::MerchantId>,
        profile_id: Option<id_type::ProfileId>,
        version: Option<UserRoleVersion>,
        limit: Option<u32>,
    ) -> StorageResult<Vec<Self>> {
        let mut query = <Self as HasTable>::table()
            .filter(dsl::org_id.eq(org_id).and(dsl::tenant_id.eq(tenant_id)))
            .into_boxed();

        if let Some(user_id) = user_id {
            query = query.filter(dsl::user_id.eq(user_id));
        }

        if let Some(merchant_id) = merchant_id {
            query = query.filter(dsl::merchant_id.eq(merchant_id));
        }

        if let Some(profile_id) = profile_id {
            query = query.filter(dsl::profile_id.eq(profile_id));
        }

        if let Some(version) = version {
            query = query.filter(dsl::version.eq(version));
        }

        if let Some(limit) = limit {
            query = query.limit(limit.into());
        }

        router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());

        match generics::db_metrics::track_database_call::<Self, _, _>(
            query.get_results_async(conn),
            generics::db_metrics::DatabaseOperation::Filter,
        )
        .await
        {
            Ok(value) => Ok(value),
            Err(err) => match err {
                DieselError::NotFound => {
                    Err(report!(err)).change_context(errors::DatabaseError::NotFound)
                }
                _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
            },
        }
    }

    pub async fn list_user_roles_by_user_id_across_tenants(
        conn: &PgPooledConn,
        user_id: String,
        limit: Option<u32>,
    ) -> StorageResult<Vec<Self>> {
        let mut query = <Self as HasTable>::table()
            .filter(dsl::user_id.eq(user_id))
            .into_boxed();
        if let Some(limit) = limit {
            query = query.limit(limit.into());
        }

        router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());

        match generics::db_metrics::track_database_call::<Self, _, _>(
            query.get_results_async(conn),
            generics::db_metrics::DatabaseOperation::Filter,
        )
        .await
        {
            Ok(value) => Ok(value),
            Err(err) => match err {
                DieselError::NotFound => {
                    Err(report!(err)).change_context(errors::DatabaseError::NotFound)
                }
                _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
            },
        }
    }
}


// File: crates/diesel_models/src/query/locker_mock_up.rs

use diesel::{associations::HasTable, ExpressionMethods};

use super::generics;
use crate::{
    locker_mock_up::{LockerMockUp, LockerMockUpNew},
    schema::locker_mock_up::dsl,
    PgPooledConn, StorageResult,
};

impl LockerMockUpNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<LockerMockUp> {
        generics::generic_insert(conn, self).await
    }
}

impl LockerMockUp {
    pub async fn find_by_card_id(conn: &PgPooledConn, card_id: &str) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::card_id.eq(card_id.to_owned()),
        )
        .await
    }

    pub async fn delete_by_card_id(conn: &PgPooledConn, card_id: &str) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::card_id.eq(card_id.to_owned()),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/refund.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, Table};

use super::generics;
#[cfg(feature = "v1")]
use crate::schema::refund::dsl;
#[cfg(feature = "v2")]
use crate::schema_v2::refund::dsl;
use crate::{
    errors,
    refund::{Refund, RefundNew, RefundUpdate, RefundUpdateInternal},
    PgPooledConn, StorageResult,
};

impl RefundNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Refund> {
        generics::generic_insert(conn, self).await
    }
}

#[cfg(feature = "v1")]
impl Refund {
    pub async fn update(self, conn: &PgPooledConn, refund: RefundUpdate) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::refund_id
                .eq(self.refund_id.to_owned())
                .and(dsl::merchant_id.eq(self.merchant_id.to_owned())),
            RefundUpdateInternal::from(refund),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    // This is required to be changed for KV.
    pub async fn find_by_merchant_id_refund_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        refund_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::refund_id.eq(refund_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_merchant_id_connector_refund_id_connector(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_refund_id: &str,
        connector: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::connector_refund_id.eq(connector_refund_id.to_owned()))
                .and(dsl::connector.eq(connector.to_owned())),
        )
        .await
    }

    pub async fn find_by_internal_reference_id_merchant_id(
        conn: &PgPooledConn,
        internal_reference_id: &str,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::internal_reference_id.eq(internal_reference_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_merchant_id_connector_transaction_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_transaction_id: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::connector_transaction_id.eq(connector_transaction_id.to_owned())),
            None,
            None,
            None,
        )
        .await
    }

    pub async fn find_by_payment_id_merchant_id(
        conn: &PgPooledConn,
        payment_id: &common_utils::id_type::PaymentId,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::payment_id.eq(payment_id.to_owned())),
            None,
            None,
            None,
        )
        .await
    }
}

#[cfg(feature = "v2")]
impl Refund {
    pub async fn update_with_id(
        self,
        conn: &PgPooledConn,
        refund: RefundUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            self.id.to_owned(),
            RefundUpdateInternal::from(refund),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn find_by_global_id(
        conn: &PgPooledConn,
        id: &common_utils::id_type::GlobalRefundId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::id.eq(id.to_owned()),
        )
        .await
    }

    pub async fn find_by_merchant_id_connector_transaction_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        connector_transaction_id: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::connector_transaction_id.eq(connector_transaction_id.to_owned())),
            None,
            None,
            None,
        )
        .await
    }
}


// File: crates/diesel_models/src/query/generics.rs

use std::fmt::Debug;

use async_bb8_diesel::AsyncRunQueryDsl;
use diesel::{
    associations::HasTable,
    debug_query,
    dsl::{count_star, Find, IsNotNull, Limit},
    helper_types::{Filter, IntoBoxed},
    insertable::CanInsertInSingleQuery,
    pg::{Pg, PgConnection},
    query_builder::{
        AsChangeset, AsQuery, DeleteStatement, InsertStatement, IntoUpdateTarget, QueryFragment,
        QueryId, UpdateStatement,
    },
    query_dsl::{
        methods::{BoxedDsl, FilterDsl, FindDsl, LimitDsl, OffsetDsl, OrderDsl, SelectDsl},
        LoadQuery, RunQueryDsl,
    },
    result::Error as DieselError,
    Expression, ExpressionMethods, Insertable, QueryDsl, QuerySource, Table,
};
use error_stack::{report, ResultExt};
use router_env::logger;

use crate::{errors, query::utils::GetPrimaryKey, PgPooledConn, StorageResult};

pub mod db_metrics {
    #[derive(Debug)]
    pub enum DatabaseOperation {
        FindOne,
        Filter,
        Update,
        Insert,
        Delete,
        DeleteWithResult,
        UpdateWithResults,
        UpdateOne,
        Count,
    }

    #[inline]
    pub async fn track_database_call<T, Fut, U>(future: Fut, operation: DatabaseOperation) -> U
    where
        Fut: std::future::Future<Output = U>,
    {
        let start = std::time::Instant::now();
        let output = future.await;
        let time_elapsed = start.elapsed();

        let table_name = std::any::type_name::<T>().rsplit("::").nth(1);

        let attributes = router_env::metric_attributes!(
            ("table", table_name.unwrap_or("undefined")),
            ("operation", format!("{:?}", operation))
        );

        crate::metrics::DATABASE_CALLS_COUNT.add(1, attributes);
        crate::metrics::DATABASE_CALL_TIME.record(time_elapsed.as_secs_f64(), attributes);

        output
    }
}

use db_metrics::*;

pub async fn generic_insert<T, V, R>(conn: &PgPooledConn, values: V) -> StorageResult<R>
where
    T: HasTable<Table = T> + Table + 'static + Debug,
    V: Debug + Insertable<T>,
    <T as QuerySource>::FromClause: QueryFragment<Pg> + Debug,
    <V as Insertable<T>>::Values: CanInsertInSingleQuery<Pg> + QueryFragment<Pg> + 'static,
    InsertStatement<T, <V as Insertable<T>>::Values>:
        AsQuery + LoadQuery<'static, PgConnection, R> + Send,
    R: Send + 'static,
{
    let debug_values = format!("{values:?}");

    let query = diesel::insert_into(<T as HasTable>::table()).values(values);
    logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

    match track_database_call::<T, _, _>(query.get_result_async(conn), DatabaseOperation::Insert)
        .await
    {
        Ok(value) => Ok(value),
        Err(err) => match err {
            DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _) => {
                Err(report!(err)).change_context(errors::DatabaseError::UniqueViolation)
            }
            _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
        },
    }
    .attach_printable_lazy(|| format!("Error while inserting {debug_values}"))
}

pub async fn generic_update<T, V, P>(
    conn: &PgPooledConn,
    predicate: P,
    values: V,
) -> StorageResult<usize>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + 'static,
    V: AsChangeset<Target = <Filter<T, P> as HasTable>::Table> + Debug,
    Filter<T, P>: IntoUpdateTarget,
    UpdateStatement<
        <Filter<T, P> as HasTable>::Table,
        <Filter<T, P> as IntoUpdateTarget>::WhereClause,
        <V as AsChangeset>::Changeset,
    >: AsQuery + QueryFragment<Pg> + QueryId + Send + 'static,
{
    let debug_values = format!("{values:?}");

    let query = diesel::update(<T as HasTable>::table().filter(predicate)).set(values);
    logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

    track_database_call::<T, _, _>(query.execute_async(conn), DatabaseOperation::Update)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable_lazy(|| format!("Error while updating {debug_values}"))
}

pub async fn generic_update_with_results<T, V, P, R>(
    conn: &PgPooledConn,
    predicate: P,
    values: V,
) -> StorageResult<Vec<R>>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + 'static,
    V: AsChangeset<Target = <Filter<T, P> as HasTable>::Table> + Debug + 'static,
    Filter<T, P>: IntoUpdateTarget + 'static,
    UpdateStatement<
        <Filter<T, P> as HasTable>::Table,
        <Filter<T, P> as IntoUpdateTarget>::WhereClause,
        <V as AsChangeset>::Changeset,
    >: AsQuery + LoadQuery<'static, PgConnection, R> + QueryFragment<Pg> + Send + Clone,
    R: Send + 'static,

    // For cloning query (UpdateStatement)
    <Filter<T, P> as HasTable>::Table: Clone,
    <Filter<T, P> as IntoUpdateTarget>::WhereClause: Clone,
    <V as AsChangeset>::Changeset: Clone,
    <<Filter<T, P> as HasTable>::Table as QuerySource>::FromClause: Clone,
{
    let debug_values = format!("{values:?}");

    let query = diesel::update(<T as HasTable>::table().filter(predicate)).set(values);

    match track_database_call::<T, _, _>(
        query.to_owned().get_results_async(conn),
        DatabaseOperation::UpdateWithResults,
    )
    .await
    {
        Ok(result) => {
            logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());
            Ok(result)
        }
        Err(DieselError::QueryBuilderError(_)) => {
            Err(report!(errors::DatabaseError::NoFieldsToUpdate))
                .attach_printable_lazy(|| format!("Error while updating {debug_values}"))
        }
        Err(DieselError::NotFound) => Err(report!(errors::DatabaseError::NotFound))
            .attach_printable_lazy(|| format!("Error while updating {debug_values}")),
        Err(error) => Err(error)
            .change_context(errors::DatabaseError::Others)
            .attach_printable_lazy(|| format!("Error while updating {debug_values}")),
    }
}

pub async fn generic_update_with_unique_predicate_get_result<T, V, P, R>(
    conn: &PgPooledConn,
    predicate: P,
    values: V,
) -> StorageResult<R>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + 'static,
    V: AsChangeset<Target = <Filter<T, P> as HasTable>::Table> + Debug + 'static,
    Filter<T, P>: IntoUpdateTarget + 'static,
    UpdateStatement<
        <Filter<T, P> as HasTable>::Table,
        <Filter<T, P> as IntoUpdateTarget>::WhereClause,
        <V as AsChangeset>::Changeset,
    >: AsQuery + LoadQuery<'static, PgConnection, R> + QueryFragment<Pg> + Send,
    R: Send + 'static,

    // For cloning query (UpdateStatement)
    <Filter<T, P> as HasTable>::Table: Clone,
    <Filter<T, P> as IntoUpdateTarget>::WhereClause: Clone,
    <V as AsChangeset>::Changeset: Clone,
    <<Filter<T, P> as HasTable>::Table as QuerySource>::FromClause: Clone,
{
    generic_update_with_results::<<T as HasTable>::Table, _, _, _>(conn, predicate, values)
        .await
        .map(|mut vec_r| {
            if vec_r.is_empty() {
                Err(errors::DatabaseError::NotFound)
            } else if vec_r.len() != 1 {
                Err(errors::DatabaseError::Others)
            } else {
                vec_r.pop().ok_or(errors::DatabaseError::Others)
            }
            .attach_printable("Maybe not queried using a unique key")
        })?
}

pub async fn generic_update_by_id<T, V, Pk, R>(
    conn: &PgPooledConn,
    id: Pk,
    values: V,
) -> StorageResult<R>
where
    T: FindDsl<Pk> + HasTable<Table = T> + LimitDsl + Table + 'static,
    V: AsChangeset<Target = <Find<T, Pk> as HasTable>::Table> + Debug,
    Find<T, Pk>: IntoUpdateTarget + QueryFragment<Pg> + RunQueryDsl<PgConnection> + Send + 'static,
    UpdateStatement<
        <Find<T, Pk> as HasTable>::Table,
        <Find<T, Pk> as IntoUpdateTarget>::WhereClause,
        <V as AsChangeset>::Changeset,
    >: AsQuery + LoadQuery<'static, PgConnection, R> + QueryFragment<Pg> + Send + 'static,
    Find<T, Pk>: LimitDsl,
    Limit<Find<T, Pk>>: LoadQuery<'static, PgConnection, R>,
    R: Send + 'static,
    Pk: Clone + Debug,

    // For cloning query (UpdateStatement)
    <Find<T, Pk> as HasTable>::Table: Clone,
    <Find<T, Pk> as IntoUpdateTarget>::WhereClause: Clone,
    <V as AsChangeset>::Changeset: Clone,
    <<Find<T, Pk> as HasTable>::Table as QuerySource>::FromClause: Clone,
{
    let debug_values = format!("{values:?}");

    let query = diesel::update(<T as HasTable>::table().find(id.to_owned())).set(values);

    match track_database_call::<T, _, _>(
        query.to_owned().get_result_async(conn),
        DatabaseOperation::UpdateOne,
    )
    .await
    {
        Ok(result) => {
            logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());
            Ok(result)
        }
        Err(DieselError::QueryBuilderError(_)) => {
            Err(report!(errors::DatabaseError::NoFieldsToUpdate))
                .attach_printable_lazy(|| format!("Error while updating by ID {debug_values}"))
        }
        Err(DieselError::NotFound) => Err(report!(errors::DatabaseError::NotFound))
            .attach_printable_lazy(|| format!("Error while updating by ID {debug_values}")),
        Err(error) => Err(error)
            .change_context(errors::DatabaseError::Others)
            .attach_printable_lazy(|| format!("Error while updating by ID {debug_values}")),
    }
}

pub async fn generic_delete<T, P>(conn: &PgPooledConn, predicate: P) -> StorageResult<bool>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + 'static,
    Filter<T, P>: IntoUpdateTarget,
    DeleteStatement<
        <Filter<T, P> as HasTable>::Table,
        <Filter<T, P> as IntoUpdateTarget>::WhereClause,
    >: AsQuery + QueryFragment<Pg> + QueryId + Send + 'static,
{
    let query = diesel::delete(<T as HasTable>::table().filter(predicate));
    logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

    track_database_call::<T, _, _>(query.execute_async(conn), DatabaseOperation::Delete)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while deleting")
        .and_then(|result| match result {
            n if n > 0 => {
                logger::debug!("{n} records deleted");
                Ok(true)
            }
            0 => {
                Err(report!(errors::DatabaseError::NotFound).attach_printable("No records deleted"))
            }
            _ => Ok(true), // n is usize, rustc requires this for exhaustive check
        })
}

pub async fn generic_delete_one_with_result<T, P, R>(
    conn: &PgPooledConn,
    predicate: P,
) -> StorageResult<R>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + 'static,
    Filter<T, P>: IntoUpdateTarget,
    DeleteStatement<
        <Filter<T, P> as HasTable>::Table,
        <Filter<T, P> as IntoUpdateTarget>::WhereClause,
    >: AsQuery + LoadQuery<'static, PgConnection, R> + QueryFragment<Pg> + Send + 'static,
    R: Send + Clone + 'static,
{
    let query = diesel::delete(<T as HasTable>::table().filter(predicate));
    logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

    track_database_call::<T, _, _>(
        query.get_results_async(conn),
        DatabaseOperation::DeleteWithResult,
    )
    .await
    .change_context(errors::DatabaseError::Others)
    .attach_printable("Error while deleting")
    .and_then(|result| {
        result.first().cloned().ok_or_else(|| {
            report!(errors::DatabaseError::NotFound)
                .attach_printable("Object to be deleted does not exist")
        })
    })
}

async fn generic_find_by_id_core<T, Pk, R>(conn: &PgPooledConn, id: Pk) -> StorageResult<R>
where
    T: FindDsl<Pk> + HasTable<Table = T> + LimitDsl + Table + 'static,
    Find<T, Pk>: LimitDsl + QueryFragment<Pg> + RunQueryDsl<PgConnection> + Send + 'static,
    Limit<Find<T, Pk>>: LoadQuery<'static, PgConnection, R>,
    Pk: Clone + Debug,
    R: Send + 'static,
{
    let query = <T as HasTable>::table().find(id.to_owned());
    logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

    match track_database_call::<T, _, _>(query.first_async(conn), DatabaseOperation::FindOne).await
    {
        Ok(value) => Ok(value),
        Err(err) => match err {
            DieselError::NotFound => {
                Err(report!(err)).change_context(errors::DatabaseError::NotFound)
            }
            _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
        },
    }
    .attach_printable_lazy(|| format!("Error finding record by primary key: {id:?}"))
}

pub async fn generic_find_by_id<T, Pk, R>(conn: &PgPooledConn, id: Pk) -> StorageResult<R>
where
    T: FindDsl<Pk> + HasTable<Table = T> + LimitDsl + Table + 'static,
    Find<T, Pk>: LimitDsl + QueryFragment<Pg> + RunQueryDsl<PgConnection> + Send + 'static,
    Limit<Find<T, Pk>>: LoadQuery<'static, PgConnection, R>,
    Pk: Clone + Debug,
    R: Send + 'static,
{
    generic_find_by_id_core::<T, _, _>(conn, id).await
}

pub async fn generic_find_by_id_optional<T, Pk, R>(
    conn: &PgPooledConn,
    id: Pk,
) -> StorageResult<Option<R>>
where
    T: FindDsl<Pk> + HasTable<Table = T> + LimitDsl + Table + 'static,
    <T as HasTable>::Table: FindDsl<Pk>,
    Find<T, Pk>: LimitDsl + QueryFragment<Pg> + RunQueryDsl<PgConnection> + Send + 'static,
    Limit<Find<T, Pk>>: LoadQuery<'static, PgConnection, R>,
    Pk: Clone + Debug,
    R: Send + 'static,
{
    to_optional(generic_find_by_id_core::<T, _, _>(conn, id).await)
}

async fn generic_find_one_core<T, P, R>(conn: &PgPooledConn, predicate: P) -> StorageResult<R>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + 'static,
    Filter<T, P>: LoadQuery<'static, PgConnection, R> + QueryFragment<Pg> + Send + 'static,
    R: Send + 'static,
{
    let query = <T as HasTable>::table().filter(predicate);
    logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

    track_database_call::<T, _, _>(query.get_result_async(conn), DatabaseOperation::FindOne)
        .await
        .map_err(|err| match err {
            DieselError::NotFound => report!(err).change_context(errors::DatabaseError::NotFound),
            _ => report!(err).change_context(errors::DatabaseError::Others),
        })
        .attach_printable("Error finding record by predicate")
}

pub async fn generic_find_one<T, P, R>(conn: &PgPooledConn, predicate: P) -> StorageResult<R>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + 'static,
    Filter<T, P>: LoadQuery<'static, PgConnection, R> + QueryFragment<Pg> + Send + 'static,
    R: Send + 'static,
{
    generic_find_one_core::<T, _, _>(conn, predicate).await
}

pub async fn generic_find_one_optional<T, P, R>(
    conn: &PgPooledConn,
    predicate: P,
) -> StorageResult<Option<R>>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + 'static,
    Filter<T, P>: LoadQuery<'static, PgConnection, R> + QueryFragment<Pg> + Send + 'static,
    R: Send + 'static,
{
    to_optional(generic_find_one_core::<T, _, _>(conn, predicate).await)
}

pub(super) async fn generic_filter<T, P, O, R>(
    conn: &PgPooledConn,
    predicate: P,
    limit: Option<i64>,
    offset: Option<i64>,
    order: Option<O>,
) -> StorageResult<Vec<R>>
where
    T: HasTable<Table = T> + Table + BoxedDsl<'static, Pg> + GetPrimaryKey + 'static,
    IntoBoxed<'static, T, Pg>: FilterDsl<P, Output = IntoBoxed<'static, T, Pg>>
        + FilterDsl<IsNotNull<T::PK>, Output = IntoBoxed<'static, T, Pg>>
        + LimitDsl<Output = IntoBoxed<'static, T, Pg>>
        + OffsetDsl<Output = IntoBoxed<'static, T, Pg>>
        + OrderDsl<O, Output = IntoBoxed<'static, T, Pg>>
        + LoadQuery<'static, PgConnection, R>
        + QueryFragment<Pg>
        + Send,
    O: Expression,
    R: Send + 'static,
{
    let mut query = T::table().into_boxed();
    query = query
        .filter(predicate)
        .filter(T::table().get_primary_key().is_not_null());
    if let Some(limit) = limit {
        query = query.limit(limit);
    }

    if let Some(offset) = offset {
        query = query.offset(offset);
    }

    if let Some(order) = order {
        query = query.order(order);
    }

    logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

    track_database_call::<T, _, _>(query.get_results_async(conn), DatabaseOperation::Filter)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error filtering records by predicate")
}

pub async fn generic_count<T, P>(conn: &PgPooledConn, predicate: P) -> StorageResult<usize>
where
    T: FilterDsl<P> + HasTable<Table = T> + Table + SelectDsl<count_star> + 'static,
    Filter<T, P>: SelectDsl<count_star>,
    diesel::dsl::Select<Filter<T, P>, count_star>:
        LoadQuery<'static, PgConnection, i64> + QueryFragment<Pg> + Send + 'static,
{
    let query = <T as HasTable>::table()
        .filter(predicate)
        .select(count_star());

    logger::debug!(query = %debug_query::<Pg, _>(&query).to_string());

    let count_i64: i64 =
        track_database_call::<T, _, _>(query.get_result_async(conn), DatabaseOperation::Count)
            .await
            .change_context(errors::DatabaseError::Others)
            .attach_printable("Error counting records by predicate")?;

    let count_usize = usize::try_from(count_i64).map_err(|_| {
        report!(errors::DatabaseError::Others).attach_printable("Count value does not fit in usize")
    })?;

    Ok(count_usize)
}

fn to_optional<T>(arg: StorageResult<T>) -> StorageResult<Option<T>> {
    match arg {
        Ok(value) => Ok(Some(value)),
        Err(err) => match err.current_context() {
            errors::DatabaseError::NotFound => Ok(None),
            _ => Err(err),
        },
    }
}


// File: crates/diesel_models/src/query/process_tracker.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, Table};
use router_env::{instrument, tracing};
use time::PrimitiveDateTime;

use super::generics;
use crate::{
    enums, errors,
    process_tracker::{
        ProcessTracker, ProcessTrackerNew, ProcessTrackerUpdate, ProcessTrackerUpdateInternal,
    },
    schema::process_tracker::dsl,
    PgPooledConn, StorageResult,
};

impl ProcessTrackerNew {
    #[instrument(skip(conn))]
    pub async fn insert_process(self, conn: &PgPooledConn) -> StorageResult<ProcessTracker> {
        generics::generic_insert(conn, self).await
    }
}

impl ProcessTracker {
    #[instrument(skip(conn))]
    pub async fn update(
        self,
        conn: &PgPooledConn,
        process: ProcessTrackerUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            self.id.clone(),
            ProcessTrackerUpdateInternal::from(process),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    #[instrument(skip(conn))]
    pub async fn update_process_status_by_ids(
        conn: &PgPooledConn,
        task_ids: Vec<String>,
        task_update: ProcessTrackerUpdate,
    ) -> StorageResult<usize> {
        generics::generic_update::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::id.eq_any(task_ids),
            ProcessTrackerUpdateInternal::from(task_update),
        )
        .await
    }

    #[instrument(skip(conn))]
    pub async fn find_process_by_id(conn: &PgPooledConn, id: &str) -> StorageResult<Option<Self>> {
        generics::generic_find_by_id_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            id.to_owned(),
        )
        .await
    }

    #[instrument(skip(conn))]
    pub async fn find_processes_by_time_status(
        conn: &PgPooledConn,
        time_lower_limit: PrimitiveDateTime,
        time_upper_limit: PrimitiveDateTime,
        status: enums::ProcessTrackerStatus,
        limit: Option<i64>,
        version: enums::ApiVersion,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::schedule_time
                .between(time_lower_limit, time_upper_limit)
                .and(dsl::status.eq(status))
                .and(dsl::version.eq(version)),
            limit,
            None,
            None,
        )
        .await
    }

    #[instrument(skip(conn))]
    pub async fn find_processes_to_clean(
        conn: &PgPooledConn,
        time_lower_limit: PrimitiveDateTime,
        time_upper_limit: PrimitiveDateTime,
        runner: &str,
        limit: usize,
    ) -> StorageResult<Vec<Self>> {
        let mut x: Vec<Self> = generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::schedule_time
                .between(time_lower_limit, time_upper_limit)
                .and(dsl::status.eq(enums::ProcessTrackerStatus::ProcessStarted))
                .and(dsl::runner.eq(runner.to_owned())),
            None,
            None,
            None,
        )
        .await?;
        x.sort_by(|a, b| a.schedule_time.cmp(&b.schedule_time));
        x.truncate(limit);
        Ok(x)
    }

    #[instrument(skip(conn))]
    pub async fn reinitialize_limbo_processes(
        conn: &PgPooledConn,
        ids: Vec<String>,
        schedule_time: PrimitiveDateTime,
    ) -> StorageResult<usize> {
        generics::generic_update::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::status
                .eq(enums::ProcessTrackerStatus::ProcessStarted)
                .and(dsl::id.eq_any(ids)),
            (
                dsl::status.eq(enums::ProcessTrackerStatus::Processing),
                dsl::schedule_time.eq(schedule_time),
            ),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/business_profile.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, Table};

use super::generics;
#[cfg(feature = "v1")]
use crate::schema::business_profile::dsl::{self, profile_id as dsl_identifier};
#[cfg(feature = "v2")]
use crate::schema_v2::business_profile::dsl::{self, id as dsl_identifier};
use crate::{
    business_profile::{Profile, ProfileNew, ProfileUpdateInternal},
    errors, PgPooledConn, StorageResult,
};

impl ProfileNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Profile> {
        generics::generic_insert(conn, self).await
    }
}

impl Profile {
    pub async fn update_by_profile_id(
        self,
        conn: &PgPooledConn,
        business_profile: ProfileUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            self.get_id().to_owned(),
            business_profile,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

    pub async fn find_by_profile_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl_identifier.eq(profile_id.to_owned()),
        )
        .await
    }

    pub async fn find_by_merchant_id_profile_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl_identifier.eq(profile_id.to_owned())),
        )
        .await
    }

    pub async fn find_by_profile_name_merchant_id(
        conn: &PgPooledConn,
        profile_name: &str,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::profile_name
                .eq(profile_name.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
        )
        .await
    }

    pub async fn list_profile_by_merchant_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id.eq(merchant_id.to_owned()),
            None,
            None,
            None,
        )
        .await
    }

    pub async fn delete_by_profile_id_merchant_id(
        conn: &PgPooledConn,
        profile_id: &common_utils::id_type::ProfileId,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(
            conn,
            dsl_identifier
                .eq(profile_id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/payment_link.rs

use diesel::{associations::HasTable, ExpressionMethods};

use super::generics;
use crate::{
    payment_link::{PaymentLink, PaymentLinkNew},
    schema::payment_link::dsl,
    PgPooledConn, StorageResult,
};

impl PaymentLinkNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<PaymentLink> {
        generics::generic_insert(conn, self).await
    }
}

impl PaymentLink {
    pub async fn find_link_by_payment_link_id(
        conn: &PgPooledConn,
        payment_link_id: &str,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::payment_link_id.eq(payment_link_id.to_owned()),
        )
        .await
    }
}


// File: crates/diesel_models/src/query/invoice.rs

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};

use super::generics;
use crate::{
    invoice::{Invoice, InvoiceNew, InvoiceUpdate},
    schema::invoice::dsl,
    PgPooledConn, StorageResult,
};

impl InvoiceNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<Invoice> {
        generics::generic_insert(conn, self).await
    }
}

impl Invoice {
    pub async fn find_invoice_by_id_invoice_id(
        conn: &PgPooledConn,
        id: String,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::id.eq(id.to_owned()),
        )
        .await
    }

    pub async fn update_invoice_entry(
        conn: &PgPooledConn,
        id: String,
        invoice_update: InvoiceUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(conn, dsl::id.eq(id.to_owned()), invoice_update)
        .await
    }

    pub async fn list_invoices_by_subscription_id(
        conn: &PgPooledConn,
        subscription_id: String,
        limit: Option<i64>,
        offset: Option<i64>,
        order_by_ascending_order: bool,
    ) -> StorageResult<Vec<Self>> {
        if order_by_ascending_order {
            generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
                conn,
                dsl::subscription_id.eq(subscription_id.to_owned()),
                limit,
                offset,
                Some(dsl::created_at.asc()),
            )
            .await
        } else {
            generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
                conn,
                dsl::subscription_id.eq(subscription_id.to_owned()),
                limit,
                offset,
                Some(dsl::created_at.desc()),
            )
            .await
        }
    }

    pub async fn get_invoice_by_subscription_id_connector_invoice_id(
        conn: &PgPooledConn,
        subscription_id: String,
        connector_invoice_id: common_utils::id_type::InvoiceId,
    ) -> StorageResult<Option<Self>> {
        generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::subscription_id
                .eq(subscription_id.to_owned())
                .and(dsl::connector_invoice_id.eq(connector_invoice_id.to_owned())),
        )
        .await
    }
}