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

use common_utils::{errors, ext_traits::ValueExt, link_utils::GenericLinkStatus};
use diesel::{associations::HasTable, ExpressionMethods};
use error_stack::{report, Report, ResultExt};

use super::generics;
use crate::{
    errors as db_errors,
    generic_link::{
        GenericLink, GenericLinkData, GenericLinkNew, GenericLinkState, GenericLinkUpdateInternal,
        PaymentMethodCollectLink, PayoutLink, PayoutLinkUpdate,
    },
    schema::generic_link::dsl,
    PgPooledConn, StorageResult,
};

impl GenericLinkNew {
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<GenericLinkState> {
        generics::generic_insert(conn, self)
            .await
            .and_then(|res: GenericLink| {
                GenericLinkState::try_from(res)
                    .change_context(db_errors::DatabaseError::Others)
                    .attach_printable("failed to parse generic link data from DB")
            })
    }

    pub async fn insert_pm_collect_link(
        self,
        conn: &PgPooledConn,
    ) -> StorageResult<PaymentMethodCollectLink> {
        generics::generic_insert(conn, self)
            .await
            .and_then(|res: GenericLink| {
                PaymentMethodCollectLink::try_from(res)
                    .change_context(db_errors::DatabaseError::Others)
                    .attach_printable("failed to parse payment method collect link data from DB")
            })
    }

    pub async fn insert_payout_link(self, conn: &PgPooledConn) -> StorageResult<PayoutLink> {
        generics::generic_insert(conn, self)
            .await
            .and_then(|res: GenericLink| {
                PayoutLink::try_from(res)
                    .change_context(db_errors::DatabaseError::Others)
                    .attach_printable("failed to parse payout link data from DB")
            })
    }
}

impl GenericLink {
    pub async fn find_generic_link_by_link_id(
        conn: &PgPooledConn,
        link_id: &str,
    ) -> StorageResult<GenericLinkState> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::link_id.eq(link_id.to_owned()),
        )
        .await
        .and_then(|res: Self| {
            GenericLinkState::try_from(res)
                .change_context(db_errors::DatabaseError::Others)
                .attach_printable("failed to parse generic link data from DB")
        })
    }

    pub async fn find_pm_collect_link_by_link_id(
        conn: &PgPooledConn,
        link_id: &str,
    ) -> StorageResult<PaymentMethodCollectLink> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::link_id.eq(link_id.to_owned()),
        )
        .await
        .and_then(|res: Self| {
            PaymentMethodCollectLink::try_from(res)
                .change_context(db_errors::DatabaseError::Others)
                .attach_printable("failed to parse payment method collect link data from DB")
        })
    }

    pub async fn find_payout_link_by_link_id(
        conn: &PgPooledConn,
        link_id: &str,
    ) -> StorageResult<PayoutLink> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::link_id.eq(link_id.to_owned()),
        )
        .await
        .and_then(|res: Self| {
            PayoutLink::try_from(res)
                .change_context(db_errors::DatabaseError::Others)
                .attach_printable("failed to parse payout link data from DB")
        })
    }
}

impl PayoutLink {
    pub async fn update_payout_link(
        self,
        conn: &PgPooledConn,
        payout_link_update: PayoutLinkUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::link_id.eq(self.link_id.to_owned()),
            GenericLinkUpdateInternal::from(payout_link_update),
        )
        .await
        .and_then(|mut payout_links| {
            payout_links
                .pop()
                .ok_or(error_stack::report!(db_errors::DatabaseError::NotFound))
        })
        .or_else(|error| match error.current_context() {
            db_errors::DatabaseError::NoFieldsToUpdate => Ok(self),
            _ => Err(error),
        })
    }
}

impl TryFrom<GenericLink> for GenericLinkState {
    type Error = Report<errors::ParsingError>;
    fn try_from(db_val: GenericLink) -> Result<Self, Self::Error> {
        let link_data = match db_val.link_type {
            common_enums::GenericLinkType::PaymentMethodCollect => {
                let link_data = db_val
                    .link_data
                    .parse_value("PaymentMethodCollectLinkData")?;
                GenericLinkData::PaymentMethodCollect(link_data)
            }
            common_enums::GenericLinkType::PayoutLink => {
                let link_data = db_val.link_data.parse_value("PayoutLinkData")?;
                GenericLinkData::PayoutLink(link_data)
            }
        };

        Ok(Self {
            link_id: db_val.link_id,
            primary_reference: db_val.primary_reference,
            merchant_id: db_val.merchant_id,
            created_at: db_val.created_at,
            last_modified_at: db_val.last_modified_at,
            expiry: db_val.expiry,
            link_data,
            link_status: db_val.link_status,
            link_type: db_val.link_type,
            url: db_val.url,
            return_url: db_val.return_url,
        })
    }
}

impl TryFrom<GenericLink> for PaymentMethodCollectLink {
    type Error = Report<errors::ParsingError>;
    fn try_from(db_val: GenericLink) -> Result<Self, Self::Error> {
        let (link_data, link_status) = match db_val.link_type {
            common_enums::GenericLinkType::PaymentMethodCollect => {
                let link_data = db_val
                    .link_data
                    .parse_value("PaymentMethodCollectLinkData")?;
                let link_status = match db_val.link_status {
                    GenericLinkStatus::PaymentMethodCollect(status) => Ok(status),
                    _ => Err(report!(errors::ParsingError::EnumParseFailure(
                        "GenericLinkStatus"
                    )))
                    .attach_printable_lazy(|| {
                        format!(
                            "Invalid status for PaymentMethodCollectLink - {:?}",
                            db_val.link_status
                        )
                    }),
                }?;
                (link_data, link_status)
            }
            _ => Err(report!(errors::ParsingError::UnknownError)).attach_printable_lazy(|| {
                format!(
                    "Invalid link_type for PaymentMethodCollectLink - {}",
                    db_val.link_type
                )
            })?,
        };

        Ok(Self {
            link_id: db_val.link_id,
            primary_reference: db_val.primary_reference,
            merchant_id: db_val.merchant_id,
            created_at: db_val.created_at,
            last_modified_at: db_val.last_modified_at,
            expiry: db_val.expiry,
            link_data,
            link_status,
            link_type: db_val.link_type,
            url: db_val.url,
            return_url: db_val.return_url,
        })
    }
}

impl TryFrom<GenericLink> for PayoutLink {
    type Error = Report<errors::ParsingError>;
    fn try_from(db_val: GenericLink) -> Result<Self, Self::Error> {
        let (link_data, link_status) = match db_val.link_type {
            common_enums::GenericLinkType::PayoutLink => {
                let link_data = db_val.link_data.parse_value("PayoutLinkData")?;
                let link_status = match db_val.link_status {
                    GenericLinkStatus::PayoutLink(status) => Ok(status),
                    _ => Err(report!(errors::ParsingError::EnumParseFailure(
                        "GenericLinkStatus"
                    )))
                    .attach_printable_lazy(|| {
                        format!("Invalid status for PayoutLink - {:?}", db_val.link_status)
                    }),
                }?;
                (link_data, link_status)
            }
            _ => Err(report!(errors::ParsingError::UnknownError)).attach_printable_lazy(|| {
                format!("Invalid link_type for PayoutLink - {}", db_val.link_type)
            })?,
        };

        Ok(Self {
            link_id: db_val.link_id,
            primary_reference: common_utils::id_type::PayoutId::try_from(std::borrow::Cow::Owned(
                db_val.primary_reference,
            ))
            .change_context(errors::ParsingError::UnknownError)
            .attach_printable("Failed to parse PayoutId from primary_reference string")?,
            merchant_id: db_val.merchant_id,
            created_at: db_val.created_at,
            last_modified_at: db_val.last_modified_at,
            expiry: db_val.expiry,
            link_data,
            link_status,
            link_type: db_val.link_type,
            url: db_val.url,
            return_url: db_val.return_url,
        })
    }
}


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

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

use crate::{
    errors,
    query::generics,
    schema::unified_translations::dsl,
    unified_translations::{UnifiedTranslationsUpdateInternal, *},
    PgPooledConn, StorageResult,
};

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

impl UnifiedTranslations {
    pub async fn find_by_unified_code_unified_message_locale(
        conn: &PgPooledConn,
        unified_code: String,
        unified_message: String,
        locale: String,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::unified_code
                .eq(unified_code)
                .and(dsl::unified_message.eq(unified_message))
                .and(dsl::locale.eq(locale)),
        )
        .await
    }

    pub async fn update_by_unified_code_unified_message_locale(
        conn: &PgPooledConn,
        unified_code: String,
        unified_message: String,
        locale: String,
        data: UnifiedTranslationsUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_results::<
            <Self as HasTable>::Table,
            UnifiedTranslationsUpdateInternal,
            _,
            _,
        >(
            conn,
            dsl::unified_code
                .eq(unified_code)
                .and(dsl::unified_message.eq(unified_message))
                .and(dsl::locale.eq(locale)),
            data.into(),
        )
        .await?
        .first()
        .cloned()
        .ok_or_else(|| {
            report!(errors::DatabaseError::NotFound)
                .attach_printable("Error while updating unified_translations entry")
        })
    }

    pub async fn delete_by_unified_code_unified_message_locale(
        conn: &PgPooledConn,
        unified_code: String,
        unified_message: String,
        locale: String,
    ) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(
            conn,
            dsl::unified_code
                .eq(unified_code)
                .and(dsl::unified_message.eq(unified_message))
                .and(dsl::locale.eq(locale)),
        )
        .await
    }
}


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

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

use super::generics;
use crate::{
    api_keys::{ApiKey, ApiKeyNew, ApiKeyUpdate, ApiKeyUpdateInternal, HashedApiKey},
    errors,
    schema::api_keys::dsl,
    PgPooledConn, StorageResult,
};

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

impl ApiKey {
    pub async fn update_by_merchant_id_key_id(
        conn: &PgPooledConn,
        merchant_id: common_utils::id_type::MerchantId,
        key_id: common_utils::id_type::ApiKeyId,
        api_key_update: ApiKeyUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::key_id.eq(key_id.to_owned())),
            ApiKeyUpdateInternal::from(api_key_update),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NotFound => {
                    Err(error.attach_printable("API key with the given key ID does not exist"))
                }
                errors::DatabaseError::NoFieldsToUpdate => {
                    generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
                        conn,
                        dsl::merchant_id
                            .eq(merchant_id.to_owned())
                            .and(dsl::key_id.eq(key_id.to_owned())),
                    )
                    .await
                }
                _ => Err(error),
            },
            result => result,
        }
    }

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

    pub async fn find_optional_by_merchant_id_key_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        key_id: &common_utils::id_type::ApiKeyId,
    ) -> StorageResult<Option<Self>> {
        generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::key_id.eq(key_id.to_owned())),
        )
        .await
    }

    pub async fn find_optional_by_hashed_api_key(
        conn: &PgPooledConn,
        hashed_api_key: HashedApiKey,
    ) -> StorageResult<Option<Self>> {
        generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::hashed_api_key.eq(hashed_api_key),
        )
        .await
    }

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


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

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

use crate::{
    enums,
    query::generics,
    schema::dashboard_metadata::dsl,
    user::dashboard_metadata::{
        DashboardMetadata, DashboardMetadataNew, DashboardMetadataUpdate,
        DashboardMetadataUpdateInternal,
    },
    PgPooledConn, StorageResult,
};

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

impl DashboardMetadata {
    pub async fn update(
        conn: &PgPooledConn,
        user_id: Option<String>,
        merchant_id: id_type::MerchantId,
        org_id: id_type::OrganizationId,
        data_key: enums::DashboardMetadata,
        dashboard_metadata_update: DashboardMetadataUpdate,
    ) -> StorageResult<Self> {
        let predicate = dsl::merchant_id
            .eq(merchant_id.to_owned())
            .and(dsl::org_id.eq(org_id.to_owned()))
            .and(dsl::data_key.eq(data_key.to_owned()));

        if let Some(uid) = user_id {
            generics::generic_update_with_unique_predicate_get_result::<
                <Self as HasTable>::Table,
                _,
                _,
                _,
            >(
                conn,
                predicate.and(dsl::user_id.eq(uid)),
                DashboardMetadataUpdateInternal::from(dashboard_metadata_update),
            )
            .await
        } else {
            generics::generic_update_with_unique_predicate_get_result::<
                <Self as HasTable>::Table,
                _,
                _,
                _,
            >(
                conn,
                predicate.and(dsl::user_id.is_null()),
                DashboardMetadataUpdateInternal::from(dashboard_metadata_update),
            )
            .await
        }
    }

    pub async fn find_user_scoped_dashboard_metadata(
        conn: &PgPooledConn,
        user_id: String,
        merchant_id: id_type::MerchantId,
        org_id: id_type::OrganizationId,
        data_types: Vec<enums::DashboardMetadata>,
    ) -> StorageResult<Vec<Self>> {
        let predicate = dsl::user_id
            .eq(user_id)
            .and(dsl::merchant_id.eq(merchant_id))
            .and(dsl::org_id.eq(org_id))
            .and(dsl::data_key.eq_any(data_types));

        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            predicate,
            None,
            None,
            Some(dsl::last_modified_at.asc()),
        )
        .await
    }

    pub async fn find_merchant_scoped_dashboard_metadata(
        conn: &PgPooledConn,
        merchant_id: id_type::MerchantId,
        org_id: id_type::OrganizationId,
        data_types: Vec<enums::DashboardMetadata>,
    ) -> StorageResult<Vec<Self>> {
        let predicate = dsl::merchant_id
            .eq(merchant_id)
            .and(dsl::org_id.eq(org_id))
            .and(dsl::data_key.eq_any(data_types));

        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            predicate,
            None,
            None,
            Some(dsl::last_modified_at.asc()),
        )
        .await
    }

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

    pub async fn delete_user_scoped_dashboard_metadata_by_merchant_id_data_key(
        conn: &PgPooledConn,
        user_id: String,
        merchant_id: id_type::MerchantId,
        data_key: enums::DashboardMetadata,
    ) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::user_id
                .eq(user_id)
                .and(dsl::merchant_id.eq(merchant_id))
                .and(dsl::data_key.eq(data_key)),
        )
        .await
    }
}


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

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

use super::generics;
#[cfg(feature = "v1")]
use crate::schema::customers::dsl;
#[cfg(feature = "v2")]
use crate::schema_v2::customers::dsl;
use crate::{
    customers::{Customer, CustomerNew, CustomerUpdateInternal},
    errors, PgPooledConn, StorageResult,
};

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

pub struct CustomerListConstraints {
    pub limit: i64,
    pub offset: Option<i64>,
    pub customer_id: Option<id_type::CustomerId>,
    pub time_range: Option<common_utils::types::TimeRange>,
}

impl Customer {
    #[cfg(feature = "v2")]
    pub async fn update_by_id(
        conn: &PgPooledConn,
        id: id_type::GlobalCustomerId,
        customer: CustomerUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            id.clone(),
            customer,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => {
                    generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(conn, id).await
                }
                _ => Err(error),
            },
            result => result,
        }
    }

    #[cfg(feature = "v2")]
    pub async fn find_by_global_id(
        conn: &PgPooledConn,
        id: &id_type::GlobalCustomerId,
    ) -> StorageResult<Self> {
        generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(conn, id.to_owned()).await
    }

    #[cfg(feature = "v1")]
    pub async fn get_customer_count_by_merchant_id_and_constraints(
        conn: &PgPooledConn,
        merchant_id: &id_type::MerchantId,
        customer_list_constraints: CustomerListConstraints,
    ) -> StorageResult<usize> {
        if let Some(customer_id) = customer_list_constraints.customer_id {
            let predicate = dsl::merchant_id
                .eq(merchant_id.clone())
                .and(dsl::customer_id.eq(customer_id));
            generics::generic_count::<<Self as HasTable>::Table, _>(conn, predicate).await
        } else if let Some(time_range) = customer_list_constraints.time_range {
            let start_time = time_range.start_time;
            let end_time = time_range
                .end_time
                .unwrap_or_else(common_utils::date_time::now);
            let predicate = dsl::merchant_id
                .eq(merchant_id.clone())
                .and(dsl::created_at.between(start_time, end_time));

            generics::generic_count::<<Self as HasTable>::Table, _>(conn, predicate).await
        } else {
            generics::generic_count::<<Self as HasTable>::Table, _>(
                conn,
                dsl::merchant_id.eq(merchant_id.to_owned()),
            )
            .await
        }
    }

    #[cfg(feature = "v2")]
    pub async fn get_customer_count_by_merchant_id_and_constraints(
        conn: &PgPooledConn,
        merchant_id: &id_type::MerchantId,
        customer_list_constraints: CustomerListConstraints,
    ) -> StorageResult<usize> {
        if let Some(customer_id) = customer_list_constraints.customer_id {
            let predicate = dsl::merchant_id
                .eq(merchant_id.clone())
                .and(dsl::merchant_reference_id.eq(customer_id));
            generics::generic_count::<<Self as HasTable>::Table, _>(conn, predicate).await
        } else if let Some(time_range) = customer_list_constraints.time_range {
            let start_time = time_range.start_time;
            let end_time = time_range
                .end_time
                .unwrap_or_else(common_utils::date_time::now);
            let predicate = dsl::merchant_id
                .eq(merchant_id.clone())
                .and(dsl::created_at.between(start_time, end_time));

            generics::generic_count::<<Self as HasTable>::Table, _>(conn, predicate).await
        } else {
            generics::generic_count::<<Self as HasTable>::Table, _>(
                conn,
                dsl::merchant_id.eq(merchant_id.to_owned()),
            )
            .await
        }
    }

    #[cfg(feature = "v1")]
    pub async fn list_customers_by_merchant_id_and_constraints(
        conn: &PgPooledConn,
        merchant_id: &id_type::MerchantId,
        constraints: CustomerListConstraints,
    ) -> StorageResult<Vec<Self>> {
        if let Some(customer_id) = constraints.customer_id {
            let predicate = dsl::merchant_id
                .eq(merchant_id.clone())
                .and(dsl::customer_id.eq(customer_id));
            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
                conn,
                predicate,
                Some(constraints.limit),
                constraints.offset,
                Some(dsl::created_at),
            )
            .await
        } else if let Some(time_range) = constraints.time_range {
            let start_time = time_range.start_time;
            let end_time = time_range
                .end_time
                .unwrap_or_else(common_utils::date_time::now);
            let predicate = dsl::merchant_id
                .eq(merchant_id.clone())
                .and(dsl::created_at.between(start_time, end_time));

            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
                conn,
                predicate,
                Some(constraints.limit),
                constraints.offset,
                Some(dsl::created_at),
            )
            .await
        } else {
            let predicate = dsl::merchant_id.eq(merchant_id.clone());
            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
                conn,
                predicate,
                Some(constraints.limit),
                constraints.offset,
                Some(dsl::created_at),
            )
            .await
        }
    }

    #[cfg(feature = "v2")]
    pub async fn list_customers_by_merchant_id_and_constraints(
        conn: &PgPooledConn,
        merchant_id: &id_type::MerchantId,
        constraints: CustomerListConstraints,
    ) -> StorageResult<Vec<Self>> {
        if let Some(customer_id) = constraints.customer_id {
            let predicate = dsl::merchant_id
                .eq(merchant_id.clone())
                .and(dsl::merchant_reference_id.eq(customer_id));
            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
                conn,
                predicate,
                Some(constraints.limit),
                constraints.offset,
                Some(dsl::created_at),
            )
            .await
        } else if let Some(time_range) = constraints.time_range {
            let start_time = time_range.start_time;
            let end_time = time_range
                .end_time
                .unwrap_or_else(common_utils::date_time::now);
            let predicate = dsl::merchant_id
                .eq(merchant_id.clone())
                .and(dsl::created_at.between(start_time, end_time));

            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
                conn,
                predicate,
                Some(constraints.limit),
                constraints.offset,
                Some(dsl::created_at),
            )
            .await
        } else {
            let predicate = dsl::merchant_id.eq(merchant_id.clone());
            generics::generic_filter::<<Self as HasTable>::Table, _, _, Self>(
                conn,
                predicate,
                Some(constraints.limit),
                constraints.offset,
                Some(dsl::created_at),
            )
            .await
        }
    }

    #[cfg(feature = "v2")]
    pub async fn find_optional_by_merchant_id_merchant_reference_id(
        conn: &PgPooledConn,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
    ) -> StorageResult<Option<Self>> {
        generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::merchant_reference_id.eq(customer_id.to_owned())),
        )
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn find_optional_by_customer_id_merchant_id(
        conn: &PgPooledConn,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
    ) -> StorageResult<Option<Self>> {
        generics::generic_find_by_id_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            (customer_id.to_owned(), merchant_id.to_owned()),
        )
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn update_by_customer_id_merchant_id(
        conn: &PgPooledConn,
        customer_id: id_type::CustomerId,
        merchant_id: id_type::MerchantId,
        customer: CustomerUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_by_id::<<Self as HasTable>::Table, _, _, _>(
            conn,
            (customer_id.clone(), merchant_id.clone()),
            customer,
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => {
                    generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(
                        conn,
                        (customer_id, merchant_id),
                    )
                    .await
                }
                _ => Err(error),
            },
            result => result,
        }
    }

    #[cfg(feature = "v1")]
    pub async fn delete_by_customer_id_merchant_id(
        conn: &PgPooledConn,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
    ) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(
            conn,
            dsl::customer_id
                .eq(customer_id.to_owned())
                .and(dsl::merchant_id.eq(merchant_id.to_owned())),
        )
        .await
    }

    #[cfg(feature = "v2")]
    pub async fn find_by_merchant_reference_id_merchant_id(
        conn: &PgPooledConn,
        merchant_reference_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::merchant_reference_id.eq(merchant_reference_id.to_owned())),
        )
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn find_by_customer_id_merchant_id(
        conn: &PgPooledConn,
        customer_id: &id_type::CustomerId,
        merchant_id: &id_type::MerchantId,
    ) -> StorageResult<Self> {
        generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(
            conn,
            (customer_id.to_owned(), merchant_id.to_owned()),
        )
        .await
    }
}


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

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

use crate::{
    errors, gsm::*, query::generics, schema::gateway_status_map::dsl, PgPooledConn, StorageResult,
};

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

impl GatewayStatusMap {
    pub async fn find(
        conn: &PgPooledConn,
        connector: String,
        flow: String,
        sub_flow: String,
        code: String,
        message: String,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::connector
                .eq(connector)
                .and(dsl::flow.eq(flow))
                .and(dsl::sub_flow.eq(sub_flow))
                .and(dsl::code.eq(code))
                .and(dsl::message.eq(message)),
        )
        .await
    }

    pub async fn retrieve_decision(
        conn: &PgPooledConn,
        connector: String,
        flow: String,
        sub_flow: String,
        code: String,
        message: String,
    ) -> StorageResult<String> {
        Self::find(conn, connector, flow, sub_flow, code, message)
            .await
            .map(|item| item.decision)
    }

    pub async fn update(
        conn: &PgPooledConn,
        connector: String,
        flow: String,
        sub_flow: String,
        code: String,
        message: String,
        gsm: GatewayStatusMappingUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_results::<
            <Self as HasTable>::Table,
            GatewayStatusMapperUpdateInternal,
            _,
            _,
        >(
            conn,
            dsl::connector
                .eq(connector)
                .and(dsl::flow.eq(flow))
                .and(dsl::sub_flow.eq(sub_flow))
                .and(dsl::code.eq(code))
                .and(dsl::message.eq(message)),
            gsm.into(),
        )
        .await?
        .first()
        .cloned()
        .ok_or_else(|| {
            report!(errors::DatabaseError::NotFound)
                .attach_printable("Error while updating gsm entry")
        })
    }

    pub async fn delete(
        conn: &PgPooledConn,
        connector: String,
        flow: String,
        sub_flow: String,
        code: String,
        message: String,
    ) -> StorageResult<bool> {
        generics::generic_delete::<<Self as HasTable>::Table, _>(
            conn,
            dsl::connector
                .eq(connector)
                .and(dsl::flow.eq(flow))
                .and(dsl::sub_flow.eq(sub_flow))
                .and(dsl::code.eq(code))
                .and(dsl::message.eq(message)),
        )
        .await
    }
}


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

use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};
use router_env::{instrument, logger, tracing};

use super::generics;
use crate::{
    connector_response::{
        ConnectorResponse, ConnectorResponseNew, ConnectorResponseUpdate,
        ConnectorResponseUpdateInternal,
    },
    errors,
    payment_attempt::{PaymentAttempt, PaymentAttemptUpdate, PaymentAttemptUpdateInternal},
    schema::{connector_response::dsl, payment_attempt::dsl as pa_dsl},
    PgPooledConn, StorageResult,
};

impl ConnectorResponseNew {
    #[instrument(skip(conn))]
    pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<ConnectorResponse> {
        let payment_attempt_update = PaymentAttemptUpdate::ConnectorResponse {
            authentication_data: self.authentication_data.clone(),
            encoded_data: self.encoded_data.clone(),
            connector_transaction_id: self.connector_transaction_id.clone(),
            connector: self.connector_name.clone(),
            updated_by: self.updated_by.clone(),
            charge_id: self.charge_id.clone(),
        };

        let _payment_attempt: Result<PaymentAttempt, _> =
            generics::generic_update_with_unique_predicate_get_result::<
                <PaymentAttempt as HasTable>::Table,
                _,
                _,
                _,
            >(
                conn,
                pa_dsl::attempt_id
                    .eq(self.attempt_id.to_owned())
                    .and(pa_dsl::merchant_id.eq(self.merchant_id.to_owned())),
                PaymentAttemptUpdateInternal::from(payment_attempt_update),
            )
            .await
            .inspect_err(|err| {
                logger::error!(
                    "Error while updating payment attempt in connector_response flow {:?}",
                    err
                );
            });

        generics::generic_insert(conn, self).await
    }
}

impl ConnectorResponse {
    #[instrument(skip(conn))]
    pub async fn update(
        self,
        conn: &PgPooledConn,
        connector_response: ConnectorResponseUpdate,
    ) -> StorageResult<Self> {
        let payment_attempt_update = match connector_response.clone() {
            ConnectorResponseUpdate::ResponseUpdate {
                connector_transaction_id,
                authentication_data,
                encoded_data,
                connector_name,
                charge_id,
                updated_by,
            } => PaymentAttemptUpdate::ConnectorResponse {
                authentication_data,
                encoded_data,
                connector_transaction_id,
                connector: connector_name,
                charge_id,
                updated_by,
            },
            ConnectorResponseUpdate::ErrorUpdate {
                connector_name,
                updated_by,
            } => PaymentAttemptUpdate::ConnectorResponse {
                authentication_data: None,
                encoded_data: None,
                connector_transaction_id: None,
                connector: connector_name,
                charge_id: None,
                updated_by,
            },
        };

        let _payment_attempt: Result<PaymentAttempt, _> =
            generics::generic_update_with_unique_predicate_get_result::<
                <PaymentAttempt as HasTable>::Table,
                _,
                _,
                _,
            >(
                conn,
                pa_dsl::attempt_id
                    .eq(self.attempt_id.to_owned())
                    .and(pa_dsl::merchant_id.eq(self.merchant_id.to_owned())),
                PaymentAttemptUpdateInternal::from(payment_attempt_update),
            )
            .await
            .inspect_err(|err| {
                logger::error!(
                    "Error while updating payment attempt in connector_response flow {:?}",
                    err
                );
            });

        let connector_response_result =
            match generics::generic_update_with_unique_predicate_get_result::<
                <Self as HasTable>::Table,
                _,
                _,
                _,
            >(
                conn,
                dsl::merchant_id
                    .eq(self.merchant_id.clone())
                    .and(dsl::payment_id.eq(self.payment_id.clone()))
                    .and(dsl::attempt_id.eq(self.attempt_id.clone())),
                ConnectorResponseUpdateInternal::from(connector_response),
            )
            .await
            {
                Err(error) => match error.current_context() {
                    errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                    _ => Err(error),
                },
                result => result,
            };

        connector_response_result
    }

    #[instrument(skip(conn))]
    pub async fn find_by_payment_id_merchant_id_attempt_id(
        conn: &PgPooledConn,
        payment_id: &common_utils::id_type::PaymentId,
        merchant_id: &common_utils::id_type::MerchantId,
        attempt_id: &str,
    ) -> StorageResult<Self> {
        let connector_response: Self =
            generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
                conn,
                dsl::merchant_id.eq(merchant_id.to_owned()).and(
                    dsl::payment_id
                        .eq(payment_id.to_owned())
                        .and(dsl::attempt_id.eq(attempt_id.to_owned())),
                ),
            )
            .await?;

        match generics::generic_find_one::<<PaymentAttempt as HasTable>::Table, _, _>(
            conn,
            pa_dsl::payment_id.eq(payment_id.to_owned()).and(
                pa_dsl::merchant_id
                    .eq(merchant_id.to_owned())
                    .and(pa_dsl::attempt_id.eq(attempt_id.to_owned())),
            ),
        )
        .await
        {
            Ok::<PaymentAttempt, _>(payment_attempt) => {
                if payment_attempt.authentication_data != connector_response.authentication_data {
                    logger::error!(
                        "Not Equal pa_authentication_data : {:?}, cr_authentication_data: {:?} ",
                        payment_attempt.authentication_data,
                        connector_response.authentication_data
                    );
                }

                if payment_attempt.encoded_data != connector_response.encoded_data {
                    logger::error!(
                        "Not Equal pa_encoded_data : {:?}, cr_encoded_data: {:?} ",
                        payment_attempt.encoded_data,
                        connector_response.encoded_data
                    );
                }

                if payment_attempt.connector_transaction_id
                    != connector_response.connector_transaction_id
                {
                    logger::error!(
                            "Not Equal pa_connector_transaction_id : {:?}, cr_connector_transaction_id: {:?} ",
                            payment_attempt.connector_transaction_id,
                            connector_response.connector_transaction_id
                        );
                }
                if payment_attempt.connector != connector_response.connector_name {
                    logger::error!(
                        "Not Equal pa_connector : {:?}, cr_connector_name: {:?} ",
                        payment_attempt.connector,
                        connector_response.connector_name
                    );
                }
            }
            Err(err) => {
                logger::error!(
                    "Error while finding payment attempt in connector_response flow {:?}",
                    err
                );
            }
        }

        Ok(connector_response)
    }
}


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

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

use crate::{
    hyperswitch_ai_interaction::{HyperswitchAiInteraction, HyperswitchAiInteractionNew},
    query::generics,
    schema::hyperswitch_ai_interaction::dsl,
    PgPooledConn, StorageResult,
};

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

impl HyperswitchAiInteraction {
    pub async fn filter_by_optional_merchant_id(
        conn: &PgPooledConn,
        merchant_id: Option<&common_utils::id_type::MerchantId>,
        limit: i64,
        offset: i64,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::merchant_id.eq(merchant_id.cloned()),
            Some(limit),
            Some(offset),
            Some(dsl::created_at.desc()),
        )
        .await
    }
}


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

use std::collections::HashSet;

use async_bb8_diesel::AsyncRunQueryDsl;
use diesel::{
    associations::HasTable,
    query_dsl::methods::{DistinctDsl, FilterDsl, SelectDsl},
    BoolExpressionMethods, ExpressionMethods,
};
use error_stack::{report, ResultExt};

use super::generics;
use crate::{
    enums,
    errors::DatabaseError,
    payout_attempt::{
        PayoutAttempt, PayoutAttemptNew, PayoutAttemptUpdate, PayoutAttemptUpdateInternal,
    },
    schema::{payout_attempt::dsl, payouts as payout_dsl},
    Payouts, PgPooledConn, StorageResult,
};

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

impl PayoutAttempt {
    pub async fn update_with_attempt_id(
        self,
        conn: &PgPooledConn,
        payout_attempt_update: PayoutAttemptUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::payout_attempt_id
                .eq(self.payout_attempt_id.to_owned())
                .and(dsl::merchant_id.eq(self.merchant_id.to_owned())),
            PayoutAttemptUpdateInternal::from(payout_attempt_update),
        )
        .await
        {
            Err(error) => match error.current_context() {
                DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

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

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

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

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

    pub async fn update_by_merchant_id_payout_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        payout_id: &common_utils::id_type::PayoutId,
        payout: PayoutAttemptUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::payout_id.eq(payout_id.to_owned())),
            PayoutAttemptUpdateInternal::from(payout),
        )
        .await?
        .first()
        .cloned()
        .ok_or_else(|| {
            report!(DatabaseError::NotFound).attach_printable("Error while updating payout")
        })
    }

    pub async fn update_by_merchant_id_payout_attempt_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        payout_attempt_id: &str,
        payout: PayoutAttemptUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::payout_attempt_id.eq(payout_attempt_id.to_owned())),
            PayoutAttemptUpdateInternal::from(payout),
        )
        .await?
        .first()
        .cloned()
        .ok_or_else(|| {
            report!(DatabaseError::NotFound).attach_printable("Error while updating payout")
        })
    }

    pub async fn get_filters_for_payouts(
        conn: &PgPooledConn,
        payouts: &[Payouts],
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<(
        Vec<String>,
        Vec<enums::Currency>,
        Vec<enums::PayoutStatus>,
        Vec<enums::PayoutType>,
    )> {
        let active_attempt_ids = payouts
            .iter()
            .map(|payout| {
                format!(
                    "{}_{}",
                    payout.payout_id.get_string_repr(),
                    payout.attempt_count.clone()
                )
            })
            .collect::<Vec<String>>();

        let active_payout_ids = payouts
            .iter()
            .map(|payout| payout.payout_id.to_owned())
            .collect::<Vec<common_utils::id_type::PayoutId>>();

        let filter = <Self as HasTable>::table()
            .filter(dsl::merchant_id.eq(merchant_id.to_owned()))
            .filter(dsl::payout_attempt_id.eq_any(active_attempt_ids));

        let payouts_filter = <Payouts as HasTable>::table()
            .filter(payout_dsl::merchant_id.eq(merchant_id.to_owned()))
            .filter(payout_dsl::payout_id.eq_any(active_payout_ids));

        let payout_status: Vec<enums::PayoutStatus> = payouts
            .iter()
            .map(|payout| payout.status)
            .collect::<HashSet<enums::PayoutStatus>>()
            .into_iter()
            .collect();

        let filter_connector = filter
            .clone()
            .select(dsl::connector)
            .distinct()
            .get_results_async::<Option<String>>(conn)
            .await
            .change_context(DatabaseError::Others)
            .attach_printable("Error filtering records by connector")?
            .into_iter()
            .flatten()
            .collect::<Vec<String>>();

        let filter_currency = payouts_filter
            .clone()
            .select(payout_dsl::destination_currency)
            .distinct()
            .get_results_async::<enums::Currency>(conn)
            .await
            .change_context(DatabaseError::Others)
            .attach_printable("Error filtering records by currency")?
            .into_iter()
            .collect::<Vec<enums::Currency>>();

        let filter_payout_method = payouts_filter
            .clone()
            .select(payout_dsl::payout_type)
            .distinct()
            .get_results_async::<Option<enums::PayoutType>>(conn)
            .await
            .change_context(DatabaseError::Others)
            .attach_printable("Error filtering records by payout type")?
            .into_iter()
            .flatten()
            .collect::<Vec<enums::PayoutType>>();

        Ok((
            filter_connector,
            filter_currency,
            payout_status,
            filter_payout_method,
        ))
    }
}


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

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

use super::generics;
use crate::{
    authentication::{
        Authentication, AuthenticationNew, AuthenticationUpdate, AuthenticationUpdateInternal,
    },
    errors,
    schema::authentication::dsl,
    PgPooledConn, StorageResult,
};

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

impl Authentication {
    pub async fn update_by_merchant_id_authentication_id(
        conn: &PgPooledConn,
        merchant_id: common_utils::id_type::MerchantId,
        authentication_id: common_utils::id_type::AuthenticationId,
        authorization_update: AuthenticationUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::authentication_id.eq(authentication_id.to_owned())),
            AuthenticationUpdateInternal::from(authorization_update),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NotFound => Err(error.attach_printable(
                    "Authentication with the given Authentication ID does not exist",
                )),
                errors::DatabaseError::NoFieldsToUpdate => {
                    generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
                        conn,
                        dsl::merchant_id
                            .eq(merchant_id.to_owned())
                            .and(dsl::authentication_id.eq(authentication_id.to_owned())),
                    )
                    .await
                }
                _ => Err(error),
            },
            result => result,
        }
    }

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

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


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

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

use super::generics;
use crate::{
    merchant_key_store::{MerchantKeyStore, MerchantKeyStoreNew},
    schema::merchant_key_store::dsl,
    PgPooledConn, StorageResult,
};

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

impl MerchantKeyStore {
    pub async fn find_by_merchant_id(
        conn: &PgPooledConn,
        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()),
        )
        .await
    }

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

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

    pub async fn list_all_key_stores(
        conn: &PgPooledConn,
        from: u32,
        limit: u32,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as diesel::Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::merchant_id.ne_all(vec!["".to_string()]),
            Some(limit.into()),
            Some(from.into()),
            None,
        )
        .await
    }
}


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

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

use super::generics;
use crate::{
    schema::user_key_store::dsl,
    user_key_store::{UserKeyStore, UserKeyStoreNew},
    PgPooledConn, StorageResult,
};

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

impl UserKeyStore {
    pub async fn get_all_user_key_stores(
        conn: &PgPooledConn,
        from: u32,
        limit: u32,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as diesel::Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::user_id.ne_all(vec!["".to_string()]),
            Some(limit.into()),
            Some(from.into()),
            None,
        )
        .await
    }

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


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

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

use super::generics;
use crate::{
    reverse_lookup::{ReverseLookup, ReverseLookupNew},
    schema::reverse_lookup::dsl,
    PgPooledConn, StorageResult,
};

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

    pub async fn batch_insert(
        reverse_lookups: Vec<Self>,
        conn: &PgPooledConn,
    ) -> StorageResult<()> {
        generics::generic_insert::<_, _, ReverseLookup>(conn, reverse_lookups).await?;
        Ok(())
    }
}
impl ReverseLookup {
    pub async fn find_by_lookup_id(lookup_id: &str, conn: &PgPooledConn) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::lookup_id.eq(lookup_id.to_owned()),
        )
        .await
    }
}


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

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

use super::generics;
use crate::{
    errors,
    relay::{Relay, RelayNew, RelayUpdateInternal},
    schema::relay::dsl,
    PgPooledConn, StorageResult,
};

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

impl Relay {
    pub async fn update(
        self,
        conn: &PgPooledConn,
        relay: RelayUpdateInternal,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(conn, dsl::id.eq(self.id.to_owned()), relay)
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

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

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


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

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

use super::generics;
use crate::{errors, mandate::*, schema::mandate::dsl, PgPooledConn, StorageResult};

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

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

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

    pub async fn find_by_merchant_id_customer_id(
        conn: &PgPooledConn,
        merchant_id: &common_utils::id_type::MerchantId,
        customer_id: &common_utils::id_type::CustomerId,
    ) -> 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::customer_id.eq(customer_id.to_owned())),
            None,
            None,
            None,
        )
        .await
    }

    //Fix this function once V2 mandate is schema is being built
    #[cfg(feature = "v2")]
    pub async fn find_by_global_customer_id(
        conn: &PgPooledConn,
        customer_id: &common_utils::id_type::GlobalCustomerId,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<
            <Self as HasTable>::Table,
            _,
            <<Self as HasTable>::Table as Table>::PrimaryKey,
            _,
        >(
            conn,
            dsl::customer_id.eq(customer_id.to_owned()),
            None,
            None,
            None,
        )
        .await
    }

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


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

use crate::{schema, schema_v2};

/// This trait will return a single column as primary key even in case of composite primary key.
///
/// In case of composite key, it will return the column that is used as local unique.
pub(super) trait GetPrimaryKey: diesel::Table {
    type PK: diesel::ExpressionMethods;
    fn get_primary_key(&self) -> Self::PK;
}

/// This trait must be implemented for all composite keys.
pub(super) trait CompositeKey {
    type UK;
    /// It will return the local unique key of the composite key.
    ///
    /// If `(attempt_id, merchant_id)` is the composite key for `payment_attempt` table, then it will return `attempt_id`.
    fn get_local_unique_key(&self) -> Self::UK;
}

/// implementation of `CompositeKey` trait for all the composite keys must be done here.
mod composite_key {
    use super::{schema, schema_v2, CompositeKey};
    impl CompositeKey for <schema::payment_attempt::table as diesel::Table>::PrimaryKey {
        type UK = schema::payment_attempt::dsl::attempt_id;
        fn get_local_unique_key(&self) -> Self::UK {
            self.0
        }
    }
    impl CompositeKey for <schema::refund::table as diesel::Table>::PrimaryKey {
        type UK = schema::refund::dsl::refund_id;
        fn get_local_unique_key(&self) -> Self::UK {
            self.1
        }
    }
    impl CompositeKey for <schema::customers::table as diesel::Table>::PrimaryKey {
        type UK = schema::customers::dsl::customer_id;
        fn get_local_unique_key(&self) -> Self::UK {
            self.0
        }
    }
    impl CompositeKey for <schema::blocklist::table as diesel::Table>::PrimaryKey {
        type UK = schema::blocklist::dsl::fingerprint_id;
        fn get_local_unique_key(&self) -> Self::UK {
            self.1
        }
    }
    impl CompositeKey for <schema::incremental_authorization::table as diesel::Table>::PrimaryKey {
        type UK = schema::incremental_authorization::dsl::authorization_id;
        fn get_local_unique_key(&self) -> Self::UK {
            self.0
        }
    }
    impl CompositeKey for <schema::hyperswitch_ai_interaction::table as diesel::Table>::PrimaryKey {
        type UK = schema::hyperswitch_ai_interaction::dsl::id;
        fn get_local_unique_key(&self) -> Self::UK {
            self.0
        }
    }
    impl CompositeKey for <schema_v2::incremental_authorization::table as diesel::Table>::PrimaryKey {
        type UK = schema_v2::incremental_authorization::dsl::authorization_id;
        fn get_local_unique_key(&self) -> Self::UK {
            self.0
        }
    }
    impl CompositeKey for <schema_v2::blocklist::table as diesel::Table>::PrimaryKey {
        type UK = schema_v2::blocklist::dsl::fingerprint_id;
        fn get_local_unique_key(&self) -> Self::UK {
            self.1
        }
    }
}

/// This macro will implement the `GetPrimaryKey` trait for all the tables with single primary key.
macro_rules! impl_get_primary_key {
    ($($table:ty),*) => {
        $(
            impl GetPrimaryKey for $table
            {
                type PK = <$table as diesel::Table>::PrimaryKey;
                fn get_primary_key(&self) -> Self::PK {
                    <Self as diesel::Table>::primary_key(self)
                }
            }
        )*
    };
}
impl_get_primary_key!(
    // v1 tables
    schema::dashboard_metadata::table,
    schema::merchant_connector_account::table,
    schema::merchant_key_store::table,
    schema::payment_methods::table,
    schema::user_authentication_methods::table,
    schema::user_key_store::table,
    schema::users::table,
    schema::api_keys::table,
    schema::captures::table,
    schema::business_profile::table,
    schema::mandate::dsl::mandate,
    schema::dispute::table,
    schema::events::table,
    schema::merchant_account::table,
    schema::process_tracker::table,
    schema::invoice::table,
    // v2 tables
    schema_v2::dashboard_metadata::table,
    schema_v2::merchant_connector_account::table,
    schema_v2::merchant_key_store::table,
    schema_v2::payment_methods::table,
    schema_v2::user_authentication_methods::table,
    schema_v2::user_key_store::table,
    schema_v2::users::table,
    schema_v2::api_keys::table,
    schema_v2::captures::table,
    schema_v2::business_profile::table,
    schema_v2::mandate::table,
    schema_v2::dispute::table,
    schema_v2::events::table,
    schema_v2::merchant_account::table,
    schema_v2::process_tracker::table,
    schema_v2::refund::table,
    schema_v2::customers::table,
    schema_v2::payment_attempt::table
);

/// This macro will implement the `GetPrimaryKey` trait for all the tables with composite key.
macro_rules! impl_get_primary_key_for_composite {
    ($($table:ty),*) => {
        $(
            impl GetPrimaryKey for $table
            {
                type PK = <<$table as diesel::Table>::PrimaryKey as CompositeKey>::UK;
                fn get_primary_key(&self) -> Self::PK {
                    <Self as diesel::Table>::primary_key(self).get_local_unique_key()
                }
            }
        )*
    };
}

impl_get_primary_key_for_composite!(
    schema::payment_attempt::table,
    schema::refund::table,
    schema::customers::table,
    schema::blocklist::table,
    schema::incremental_authorization::table,
    schema::hyperswitch_ai_interaction::table,
    schema_v2::incremental_authorization::table,
    schema_v2::blocklist::table
);


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

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

use super::generics;
#[cfg(feature = "v1")]
use crate::schema::payment_intent::dsl;
#[cfg(feature = "v2")]
use crate::schema_v2::payment_intent::dsl;
use crate::{
    errors,
    payment_intent::{self, PaymentIntent, PaymentIntentNew},
    PgPooledConn, StorageResult,
};

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

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

    #[cfg(feature = "v2")]
    pub async fn find_by_global_id(
        conn: &PgPooledConn,
        id: &common_utils::id_type::GlobalPaymentId,
    ) -> StorageResult<Self> {
        generics::generic_find_by_id::<<Self as HasTable>::Table, _, _>(conn, id.to_owned()).await
    }

    #[cfg(feature = "v1")]
    pub async fn update(
        self,
        conn: &PgPooledConn,
        payment_intent: payment_intent::PaymentIntentUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::payment_id
                .eq(self.payment_id.to_owned())
                .and(dsl::merchant_id.eq(self.merchant_id.to_owned())),
            payment_intent::PaymentIntentUpdateInternal::from(payment_intent),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            Ok(mut payment_intents) => payment_intents
                .pop()
                .ok_or(error_stack::report!(errors::DatabaseError::NotFound)),
        }
    }

    #[cfg(feature = "v2")]
    pub async fn find_by_merchant_reference_id_merchant_id(
        conn: &PgPooledConn,
        merchant_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::merchant_reference_id.eq(merchant_reference_id.to_owned())),
        )
        .await
    }

    // This query should be removed in the future because direct queries to the intent table without an intent ID are not allowed.
    // In an active-active setup, a lookup table should be implemented, and the merchant reference ID will serve as the idempotency key.
    #[cfg(feature = "v2")]
    pub async fn find_by_merchant_reference_id_profile_id(
        conn: &PgPooledConn,
        merchant_reference_id: &common_utils::id_type::PaymentReferenceId,
        profile_id: &common_utils::id_type::ProfileId,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::profile_id
                .eq(profile_id.to_owned())
                .and(dsl::merchant_reference_id.eq(merchant_reference_id.to_owned())),
        )
        .await
    }

    #[cfg(feature = "v1")]
    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<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::payment_id.eq(payment_id.to_owned())),
        )
        .await
    }

    #[cfg(feature = "v2")]
    pub async fn find_optional_by_merchant_reference_id_merchant_id(
        conn: &PgPooledConn,
        merchant_reference_id: &str,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Option<Self>> {
        generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::merchant_reference_id.eq(merchant_reference_id.to_owned())),
        )
        .await
    }

    #[cfg(feature = "v1")]
    pub async fn find_optional_by_payment_id_merchant_id(
        conn: &PgPooledConn,
        payment_id: &common_utils::id_type::PaymentId,
        merchant_id: &common_utils::id_type::MerchantId,
    ) -> StorageResult<Option<Self>> {
        generics::generic_find_one_optional::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::payment_id.eq(payment_id.to_owned())),
        )
        .await
    }
}


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

use common_utils::types::ConnectorTransactionIdTrait;
use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods};

use super::generics;
use crate::{
    capture::{Capture, CaptureNew, CaptureUpdate, CaptureUpdateInternal},
    errors,
    schema::captures::dsl,
    PgPooledConn, StorageResult,
};

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

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

    pub async fn update_with_capture_id(
        self,
        conn: &PgPooledConn,
        capture: CaptureUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::capture_id.eq(self.capture_id.to_owned()),
            CaptureUpdateInternal::from(capture),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }

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

impl ConnectorTransactionIdTrait for Capture {
    fn get_optional_connector_transaction_id(&self) -> Option<&String> {
        match self
            .connector_capture_id
            .as_ref()
            .map(|capture_id| capture_id.get_txn_id(self.processor_capture_data.as_ref()))
            .transpose()
        {
            Ok(capture_id) => capture_id,

            // In case hashed data is missing from DB, use the hashed ID as connector transaction ID
            Err(_) => self
                .connector_capture_id
                .as_ref()
                .map(|txn_id| txn_id.get_id()),
        }
    }
}


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

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

use super::generics;
use crate::{
    authorization::{
        Authorization, AuthorizationNew, AuthorizationUpdate, AuthorizationUpdateInternal,
    },
    errors,
    schema::incremental_authorization::dsl,
    PgPooledConn, StorageResult,
};

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

impl Authorization {
    pub async fn update_by_merchant_id_authorization_id(
        conn: &PgPooledConn,
        merchant_id: common_utils::id_type::MerchantId,
        authorization_id: String,
        authorization_update: AuthorizationUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::merchant_id
                .eq(merchant_id.to_owned())
                .and(dsl::authorization_id.eq(authorization_id.to_owned())),
            AuthorizationUpdateInternal::from(authorization_update),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NotFound => Err(error.attach_printable(
                    "Authorization with the given Authorization ID does not exist",
                )),
                errors::DatabaseError::NoFieldsToUpdate => {
                    generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
                        conn,
                        dsl::merchant_id
                            .eq(merchant_id.to_owned())
                            .and(dsl::authorization_id.eq(authorization_id.to_owned())),
                    )
                    .await
                }
                _ => Err(error),
            },
            result => result,
        }
    }

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


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

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

use crate::{
    query::generics, schema::user_authentication_methods::dsl, user_authentication_method::*,
    PgPooledConn, StorageResult,
};

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

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

    pub async fn list_user_authentication_methods_for_auth_id(
        conn: &PgPooledConn,
        auth_id: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::auth_id.eq(auth_id.to_owned()),
            None,
            None,
            Some(dsl::last_modified_at.asc()),
        )
        .await
    }

    pub async fn list_user_authentication_methods_for_owner_id(
        conn: &PgPooledConn,
        owner_id: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::owner_id.eq(owner_id.to_owned()),
            None,
            None,
            Some(dsl::last_modified_at.asc()),
        )
        .await
    }

    pub async fn update_user_authentication_method(
        conn: &PgPooledConn,
        id: &str,
        user_authentication_method_update: UserAuthenticationMethodUpdate,
    ) -> StorageResult<Self> {
        generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::id.eq(id.to_owned()),
            OrgAuthenticationMethodUpdateInternal::from(user_authentication_method_update),
        )
        .await
    }

    pub async fn list_user_authentication_methods_for_email_domain(
        conn: &PgPooledConn,
        email_domain: &str,
    ) -> StorageResult<Vec<Self>> {
        generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
            conn,
            dsl::email_domain.eq(email_domain.to_owned()),
            None,
            None,
            Some(dsl::last_modified_at.asc()),
        )
        .await
    }
}


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

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

use super::generics;
use crate::{
    errors,
    file::{FileMetadata, FileMetadataNew, FileMetadataUpdate, FileMetadataUpdateInternal},
    schema::file_metadata::dsl,
    PgPooledConn, StorageResult,
};

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

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

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

    pub async fn update(
        self,
        conn: &PgPooledConn,
        file_metadata: FileMetadataUpdate,
    ) -> StorageResult<Self> {
        match generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(
            conn,
            dsl::file_id.eq(self.file_id.to_owned()),
            FileMetadataUpdateInternal::from(file_metadata),
        )
        .await
        {
            Err(error) => match error.current_context() {
                errors::DatabaseError::NoFieldsToUpdate => Ok(self),
                _ => Err(error),
            },
            result => result,
        }
    }
}


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

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

use crate::{
    errors::DatabaseError,
    query::generics::{
        self,
        db_metrics::{track_database_call, DatabaseOperation},
    },
    schema::themes::dsl,
    user::theme::{Theme, ThemeNew, ThemeUpdate, ThemeUpdateInternal},
    PgPooledConn, StorageResult,
};

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

impl Theme {
    fn lineage_filter(
        lineage: ThemeLineage,
    ) -> Box<
        dyn diesel::BoxableExpression<<Self as HasTable>::Table, Pg, SqlType = Nullable<Bool>>
            + 'static,
    > {
        match lineage {
            ThemeLineage::Tenant { tenant_id } => Box::new(
                dsl::tenant_id
                    .eq(tenant_id)
                    .and(dsl::org_id.is_null())
                    .and(dsl::merchant_id.is_null())
                    .and(dsl::profile_id.is_null())
                    .nullable(),
            ),
            ThemeLineage::Organization { tenant_id, org_id } => Box::new(
                dsl::tenant_id
                    .eq(tenant_id)
                    .and(dsl::org_id.eq(org_id))
                    .and(dsl::merchant_id.is_null())
                    .and(dsl::profile_id.is_null()),
            ),
            ThemeLineage::Merchant {
                tenant_id,
                org_id,
                merchant_id,
            } => Box::new(
                dsl::tenant_id
                    .eq(tenant_id)
                    .and(dsl::org_id.eq(org_id))
                    .and(dsl::merchant_id.eq(merchant_id))
                    .and(dsl::profile_id.is_null()),
            ),
            ThemeLineage::Profile {
                tenant_id,
                org_id,
                merchant_id,
                profile_id,
            } => Box::new(
                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)),
            ),
        }
    }

    /// Matches all themes that belong to the specified hierarchy level or below
    fn lineage_hierarchy_filter(
        lineage: ThemeLineage,
    ) -> Box<
        dyn diesel::BoxableExpression<<Self as HasTable>::Table, Pg, SqlType = Nullable<Bool>>
            + 'static,
    > {
        match lineage {
            ThemeLineage::Tenant { tenant_id } => Box::new(dsl::tenant_id.eq(tenant_id).nullable()),
            ThemeLineage::Organization { tenant_id, org_id } => Box::new(
                dsl::tenant_id
                    .eq(tenant_id)
                    .and(dsl::org_id.eq(org_id))
                    .nullable(),
            ),
            ThemeLineage::Merchant {
                tenant_id,
                org_id,
                merchant_id,
            } => Box::new(
                dsl::tenant_id
                    .eq(tenant_id)
                    .and(dsl::org_id.eq(org_id))
                    .and(dsl::merchant_id.eq(merchant_id))
                    .nullable(),
            ),
            ThemeLineage::Profile {
                tenant_id,
                org_id,
                merchant_id,
                profile_id,
            } => Box::new(
                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))
                    .nullable(),
            ),
        }
    }

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

    pub async fn find_most_specific_theme_in_lineage(
        conn: &PgPooledConn,
        lineage: ThemeLineage,
    ) -> StorageResult<Self> {
        let query = <Self as HasTable>::table().into_boxed();

        let query =
            lineage
                .get_same_and_higher_lineages()
                .into_iter()
                .fold(query, |mut query, lineage| {
                    query = query.or_filter(Self::lineage_filter(lineage));
                    query
                });

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

        let data: Vec<Self> = match track_database_call::<Self, _, _>(
            query.get_results_async(conn),
            DatabaseOperation::Filter,
        )
        .await
        {
            Ok(value) => Ok(value),
            Err(err) => match err {
                DieselError::NotFound => Err(report!(err)).change_context(DatabaseError::NotFound),
                _ => Err(report!(err)).change_context(DatabaseError::Others),
            },
        }?;

        data.into_iter()
            .min_by_key(|theme| theme.entity_type)
            .ok_or(report!(DatabaseError::NotFound))
    }

    pub async fn find_by_lineage(
        conn: &PgPooledConn,
        lineage: ThemeLineage,
    ) -> StorageResult<Self> {
        generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
            conn,
            Self::lineage_filter(lineage),
        )
        .await
    }

    pub async fn update_by_theme_id(
        conn: &PgPooledConn,
        theme_id: String,
        update: ThemeUpdate,
    ) -> StorageResult<Self> {
        let update_internal: ThemeUpdateInternal = update.into();

        let predicate = dsl::theme_id.eq(theme_id);
        generics::generic_update_with_unique_predicate_get_result::<
            <Self as HasTable>::Table,
            _,
            _,
            _,
        >(conn, predicate, update_internal)
        .await
    }

    pub async fn delete_by_theme_id_and_lineage(
        conn: &PgPooledConn,
        theme_id: String,
        lineage: ThemeLineage,
    ) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::theme_id
                .eq(theme_id)
                .and(Self::lineage_filter(lineage)),
        )
        .await
    }
    pub async fn delete_by_theme_id(conn: &PgPooledConn, theme_id: String) -> StorageResult<Self> {
        generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(
            conn,
            dsl::theme_id.eq(theme_id),
        )
        .await
    }
    /// Finds all themes that match the specified lineage hierarchy.
    pub async fn find_all_by_lineage_hierarchy(
        conn: &PgPooledConn,
        lineage: ThemeLineage,
    ) -> StorageResult<Vec<Self>> {
        let filter = Self::lineage_hierarchy_filter(lineage);

        let query = <Self as HasTable>::table().filter(filter).into_boxed();

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

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


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

use async_bb8_diesel::AsyncRunQueryDsl;
use diesel::{associations::HasTable, debug_query, ExpressionMethods, TextExpressionMethods};
use error_stack::ResultExt;
use router_env::logger;

#[cfg(feature = "v1")]
use crate::schema::{
    payment_attempt::dsl as payment_attempt_dsl, payment_intent::dsl as payment_intent_dsl,
    refund::dsl as refund_dsl,
};
#[cfg(feature = "v2")]
use crate::schema_v2::{
    payment_attempt::dsl as payment_attempt_dsl, payment_intent::dsl as payment_intent_dsl,
    refund::dsl as refund_dsl,
};
use crate::{
    errors, schema::dispute::dsl as dispute_dsl, Dispute, DisputeNew, PaymentAttempt,
    PaymentIntent, PgPooledConn, Refund, RefundNew, StorageResult,
};
#[cfg(feature = "v1")]
use crate::{user, PaymentIntentNew};

#[cfg(feature = "v1")]
pub async fn insert_payment_intents(
    conn: &PgPooledConn,
    batch: Vec<PaymentIntentNew>,
) -> StorageResult<Vec<PaymentIntent>> {
    let query = diesel::insert_into(<PaymentIntent>::table()).values(batch);

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

    query
        .get_results_async(conn)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while inserting payment intents")
}

#[cfg(feature = "v1")]
pub async fn insert_payment_attempts(
    conn: &PgPooledConn,
    batch: Vec<user::sample_data::PaymentAttemptBatchNew>,
) -> StorageResult<Vec<PaymentAttempt>> {
    let query = diesel::insert_into(<PaymentAttempt>::table()).values(batch);

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

    query
        .get_results_async(conn)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while inserting payment attempts")
}

pub async fn insert_refunds(
    conn: &PgPooledConn,
    batch: Vec<RefundNew>,
) -> StorageResult<Vec<Refund>> {
    let query = diesel::insert_into(<Refund>::table()).values(batch);

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

    query
        .get_results_async(conn)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while inserting refunds")
}

pub async fn insert_disputes(
    conn: &PgPooledConn,
    batch: Vec<DisputeNew>,
) -> StorageResult<Vec<Dispute>> {
    let query = diesel::insert_into(<Dispute>::table()).values(batch);

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

    query
        .get_results_async(conn)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while inserting disputes")
}

#[cfg(feature = "v1")]
pub async fn delete_payment_intents(
    conn: &PgPooledConn,
    merchant_id: &common_utils::id_type::MerchantId,
) -> StorageResult<Vec<PaymentIntent>> {
    let query = diesel::delete(<PaymentIntent>::table())
        .filter(payment_intent_dsl::merchant_id.eq(merchant_id.to_owned()))
        .filter(payment_intent_dsl::payment_id.like("test_%"));

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

    query
        .get_results_async(conn)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while deleting payment intents")
        .and_then(|result| match result.len() {
            n if n > 0 => {
                logger::debug!("{n} records deleted");
                Ok(result)
            }
            0 => Err(error_stack::report!(errors::DatabaseError::NotFound)
                .attach_printable("No records deleted")),
            _ => Ok(result),
        })
}

#[cfg(feature = "v2")]
pub async fn delete_payment_intents(
    conn: &PgPooledConn,
    merchant_id: &common_utils::id_type::MerchantId,
) -> StorageResult<Vec<PaymentIntent>> {
    let query = diesel::delete(<PaymentIntent>::table())
        .filter(payment_intent_dsl::merchant_id.eq(merchant_id.to_owned()))
        .filter(payment_intent_dsl::merchant_reference_id.like("test_%"));

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

    query
        .get_results_async(conn)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while deleting payment intents")
        .and_then(|result| match result.len() {
            n if n > 0 => {
                logger::debug!("{n} records deleted");
                Ok(result)
            }
            0 => Err(error_stack::report!(errors::DatabaseError::NotFound)
                .attach_printable("No records deleted")),
            _ => Ok(result),
        })
}
pub async fn delete_payment_attempts(
    conn: &PgPooledConn,
    merchant_id: &common_utils::id_type::MerchantId,
) -> StorageResult<Vec<PaymentAttempt>> {
    let query = diesel::delete(<PaymentAttempt>::table())
        .filter(payment_attempt_dsl::merchant_id.eq(merchant_id.to_owned()))
        .filter(payment_attempt_dsl::payment_id.like("test_%"));

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

    query
        .get_results_async(conn)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while deleting payment attempts")
        .and_then(|result| match result.len() {
            n if n > 0 => {
                logger::debug!("{n} records deleted");
                Ok(result)
            }
            0 => Err(error_stack::report!(errors::DatabaseError::NotFound)
                .attach_printable("No records deleted")),
            _ => Ok(result),
        })
}

pub async fn delete_refunds(
    conn: &PgPooledConn,
    merchant_id: &common_utils::id_type::MerchantId,
) -> StorageResult<Vec<Refund>> {
    let query = diesel::delete(<Refund>::table())
        .filter(refund_dsl::merchant_id.eq(merchant_id.to_owned()))
        .filter(refund_dsl::payment_id.like("test_%"));

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

    query
        .get_results_async(conn)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while deleting refunds")
        .and_then(|result| match result.len() {
            n if n > 0 => {
                logger::debug!("{n} records deleted");
                Ok(result)
            }
            0 => Err(error_stack::report!(errors::DatabaseError::NotFound)
                .attach_printable("No records deleted")),
            _ => Ok(result),
        })
}

pub async fn delete_disputes(
    conn: &PgPooledConn,
    merchant_id: &common_utils::id_type::MerchantId,
) -> StorageResult<Vec<Dispute>> {
    let query = diesel::delete(<Dispute>::table())
        .filter(dispute_dsl::merchant_id.eq(merchant_id.to_owned()))
        .filter(dispute_dsl::dispute_id.like("test_%"));

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

    query
        .get_results_async(conn)
        .await
        .change_context(errors::DatabaseError::Others)
        .attach_printable("Error while deleting disputes")
        .and_then(|result| match result.len() {
            n if n > 0 => {
                logger::debug!("{n} records deleted");
                Ok(result)
            }
            0 => Err(error_stack::report!(errors::DatabaseError::NotFound)
                .attach_printable("No records deleted")),
            _ => Ok(result),
        })
}