// File: crates/analytics/src/payments/metrics/sessionized_metrics/debit_routing.rs

use std::collections::HashSet;

use api_models::analytics::{
    payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

#[derive(Default)]
pub(crate) struct DebitRouting;

#[async_trait::async_trait]
impl<T> super::PaymentMetric<T> for DebitRouting
where
    T: AnalyticsDataSource + super::PaymentMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[PaymentDimensions],
        auth: &AuthInfo,
        filters: &PaymentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::PaymentSessionized);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }
        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Sum {
                field: "debit_routing_savings",
                alias: Some("total"),
            })
            .switch()?;
        query_builder.add_select_column("currency").switch()?;
        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;

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

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

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }

        query_builder
            .add_group_by_clause("currency")
            .attach_printable("Error grouping by currency")
            .switch()?;

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .add_filter_clause(
                PaymentDimensions::PaymentStatus,
                storage_enums::AttemptStatus::Charged,
            )
            .switch()?;

        query_builder
            .execute_query::<PaymentMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentMetricsBucketIdentifier::new(
                        i.currency.as_ref().map(|i| i.0),
                        None,
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.client_source.clone(),
                        i.client_version.clone(),
                        i.profile_id.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        i.routing_approach.as_ref().map(|i| i.0.clone()),
                        i.signature_network.clone(),
                        i.is_issuer_regulated,
                        i.is_debit_routed,
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// File: crates/analytics/src/payments/metrics/sessionized_metrics/payment_count.rs

use std::collections::HashSet;

use api_models::analytics::{
    payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

#[derive(Default)]
pub(crate) struct PaymentCount;

#[async_trait::async_trait]
impl<T> super::PaymentMetric<T> for PaymentCount
where
    T: AnalyticsDataSource + super::PaymentMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[PaymentDimensions],
        auth: &AuthInfo,
        filters: &PaymentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::PaymentSessionized);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }

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

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

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

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<PaymentMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentMetricsBucketIdentifier::new(
                        i.currency.as_ref().map(|i| i.0),
                        i.status.as_ref().map(|i| i.0),
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.client_source.clone(),
                        i.client_version.clone(),
                        i.profile_id.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        i.routing_approach.as_ref().map(|i| i.0.clone()),
                        i.signature_network.clone(),
                        i.is_issuer_regulated,
                        i.is_debit_routed,
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<HashSet<_>, crate::query::PostProcessingError>>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// File: crates/analytics/src/payments/metrics/sessionized_metrics/payment_success_count.rs

use std::collections::HashSet;

use api_models::analytics::{
    payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

#[derive(Default)]
pub(crate) struct PaymentSuccessCount;

#[async_trait::async_trait]
impl<T> super::PaymentMetric<T> for PaymentSuccessCount
where
    T: AnalyticsDataSource + super::PaymentMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[PaymentDimensions],
        auth: &AuthInfo,
        filters: &PaymentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::PaymentSessionized);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }

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

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

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

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .add_filter_clause(
                PaymentDimensions::PaymentStatus,
                storage_enums::AttemptStatus::Charged,
            )
            .switch()?;
        query_builder
            .execute_query::<PaymentMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentMetricsBucketIdentifier::new(
                        i.currency.as_ref().map(|i| i.0),
                        None,
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.client_source.clone(),
                        i.client_version.clone(),
                        i.profile_id.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        i.routing_approach.as_ref().map(|i| i.0.clone()),
                        i.signature_network.clone(),
                        i.is_issuer_regulated,
                        i.is_debit_routed,
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// File: crates/analytics/src/payments/metrics/sessionized_metrics/payment_processed_amount.rs

use std::collections::HashSet;

use api_models::analytics::{
    payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

#[derive(Default)]
pub(crate) struct PaymentProcessedAmount;

#[async_trait::async_trait]
impl<T> super::PaymentMetric<T> for PaymentProcessedAmount
where
    T: AnalyticsDataSource + super::PaymentMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[PaymentDimensions],
        auth: &AuthInfo,
        filters: &PaymentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>> {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::PaymentSessionized);

        let mut dimensions = dimensions.to_vec();

        dimensions.push(PaymentDimensions::PaymentStatus);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }
        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("count"),
            })
            .switch()?;

        query_builder.add_select_column("first_attempt").switch()?;

        query_builder.add_select_column("currency").switch()?;

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

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

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

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }
        query_builder
            .add_group_by_clause("first_attempt")
            .attach_printable("Error grouping by first_attempt")
            .switch()?;

        query_builder
            .add_group_by_clause("currency")
            .attach_printable("Error grouping by currency")
            .switch()?;

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .add_filter_clause(
                PaymentDimensions::PaymentStatus,
                storage_enums::AttemptStatus::Charged,
            )
            .switch()?;

        query_builder
            .execute_query::<PaymentMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentMetricsBucketIdentifier::new(
                        i.currency.as_ref().map(|i| i.0),
                        None,
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.client_source.clone(),
                        i.client_version.clone(),
                        i.profile_id.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        i.routing_approach.as_ref().map(|i| i.0.clone()),
                        i.signature_network.clone(),
                        i.is_issuer_regulated,
                        i.is_debit_routed,
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(PaymentMetricsBucketIdentifier, PaymentMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// File: crates/analytics/src/payments/distribution/payment_error_message.rs

use api_models::analytics::{
    payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier},
    Granularity, PaymentDistributionBody, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use diesel_models::enums as storage_enums;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

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

#[async_trait::async_trait]
impl<T> PaymentDistribution<T> for PaymentErrorMessage
where
    T: AnalyticsDataSource + super::PaymentDistributionAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_distribution(
        &self,
        distribution: &PaymentDistributionBody,
        dimensions: &[PaymentDimensions],
        auth: &AuthInfo,
        filters: &PaymentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<Vec<(PaymentMetricsBucketIdentifier, PaymentDistributionRow)>> {
        let mut query_builder: QueryBuilder<T> = QueryBuilder::new(AnalyticsCollection::Payment);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }

        query_builder
            .add_select_column(&distribution.distribution_for)
            .switch()?;

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

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

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

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder
                .add_group_by_clause(dim)
                .attach_printable("Error grouping by dimensions")
                .switch()?;
        }

        query_builder
            .add_group_by_clause(&distribution.distribution_for)
            .attach_printable("Error grouping by distribution_for")
            .switch()?;

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .add_filter_clause(
                PaymentDimensions::PaymentStatus,
                storage_enums::AttemptStatus::Failure,
            )
            .switch()?;

        for dim in dimensions.iter() {
            query_builder.add_outer_select_column(dim).switch()?;
        }

        query_builder
            .add_outer_select_column(&distribution.distribution_for)
            .switch()?;
        query_builder.add_outer_select_column("count").switch()?;
        query_builder
            .add_outer_select_column("start_bucket")
            .switch()?;
        query_builder
            .add_outer_select_column("end_bucket")
            .switch()?;
        let sql_dimensions = query_builder.transform_to_sql_values(dimensions).switch()?;

        query_builder
            .add_outer_select_column(Window::Sum {
                field: "count",
                partition_by: Some(sql_dimensions),
                order_by: None,
                alias: Some("total"),
            })
            .switch()?;

        query_builder
            .add_top_n_clause(
                dimensions,
                distribution.distribution_cardinality.into(),
                "count",
                Order::Descending,
            )
            .switch()?;

        query_builder
            .execute_query::<PaymentDistributionRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    PaymentMetricsBucketIdentifier::new(
                        i.currency.as_ref().map(|i| i.0),
                        i.status.as_ref().map(|i| i.0),
                        i.connector.clone(),
                        i.authentication_type.as_ref().map(|i| i.0),
                        i.payment_method.clone(),
                        i.payment_method_type.clone(),
                        i.client_source.clone(),
                        i.client_version.clone(),
                        i.profile_id.clone(),
                        i.card_network.clone(),
                        i.merchant_id.clone(),
                        i.card_last_4.clone(),
                        i.card_issuer.clone(),
                        i.error_reason.clone(),
                        i.routing_approach.as_ref().map(|i| i.0.clone()),
                        i.signature_network.clone(),
                        i.is_issuer_regulated,
                        i.is_debit_routed,
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                Vec<(PaymentMetricsBucketIdentifier, PaymentDistributionRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// File: crates/analytics/src/routing_events/core.rs

use api_models::analytics::routing_events::RoutingEventsRequest;
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;

use super::events::{get_routing_events, RoutingEventsResult};
use crate::{errors::AnalyticsResult, types::FiltersError, AnalyticsProvider};

pub async fn routing_events_core(
    pool: &AnalyticsProvider,
    req: RoutingEventsRequest,
    merchant_id: &common_utils::id_type::MerchantId,
) -> AnalyticsResult<Vec<RoutingEventsResult>> {
    let data = match pool {
        AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented(
            "Connector Events not implemented for SQLX",
        ))
        .attach_printable("SQL Analytics is not implemented for Connector Events"),
        AnalyticsProvider::Clickhouse(ckh_pool)
        | AnalyticsProvider::CombinedSqlx(_, ckh_pool)
        | AnalyticsProvider::CombinedCkh(_, ckh_pool) => {
            get_routing_events(merchant_id, req, ckh_pool).await
        }
    }
    .switch()?;
    Ok(data)
}


// File: crates/analytics/src/routing_events/events.rs

use api_models::analytics::{routing_events::RoutingEventsRequest, Granularity};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use crate::{
    query::{Aggregate, GroupByClause, QueryBuilder, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow},
};
pub trait RoutingEventLogAnalytics: LoadRow<RoutingEventsResult> {}

pub async fn get_routing_events<T>(
    merchant_id: &common_utils::id_type::MerchantId,
    query_param: RoutingEventsRequest,
    pool: &T,
) -> FiltersResult<Vec<RoutingEventsResult>>
where
    T: AnalyticsDataSource + RoutingEventLogAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    let mut query_builder: QueryBuilder<T> = QueryBuilder::new(AnalyticsCollection::RoutingEvents);
    query_builder.add_select_column("*").switch()?;

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

    query_builder
        .add_filter_clause("payment_id", &query_param.payment_id)
        .switch()?;

    if let Some(refund_id) = query_param.refund_id {
        query_builder
            .add_filter_clause("refund_id", &refund_id)
            .switch()?;
    }

    if let Some(dispute_id) = query_param.dispute_id {
        query_builder
            .add_filter_clause("dispute_id", &dispute_id)
            .switch()?;
    }

    query_builder
        .execute_query::<RoutingEventsResult, _>(pool)
        .await
        .change_context(FiltersError::QueryBuildingError)?
        .change_context(FiltersError::QueryExecutionFailure)
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct RoutingEventsResult {
    pub merchant_id: common_utils::id_type::MerchantId,
    pub profile_id: common_utils::id_type::ProfileId,
    pub payment_id: String,
    pub routable_connectors: String,
    pub payment_connector: Option<String>,
    pub request_id: Option<String>,
    pub flow: String,
    pub url: Option<String>,
    pub request: String,
    pub response: Option<String>,
    pub error: Option<String>,
    pub status_code: Option<u16>,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
    pub method: String,
    pub routing_engine: String,
    pub routing_approach: Option<String>,
}


// File: crates/analytics/src/connector_events/core.rs

use api_models::analytics::connector_events::ConnectorEventsRequest;
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;

use super::events::{get_connector_events, ConnectorEventsResult};
use crate::{errors::AnalyticsResult, types::FiltersError, AnalyticsProvider};

pub async fn connector_events_core(
    pool: &AnalyticsProvider,
    req: ConnectorEventsRequest,
    merchant_id: &common_utils::id_type::MerchantId,
) -> AnalyticsResult<Vec<ConnectorEventsResult>> {
    let data = match pool {
        AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented(
            "Connector Events not implemented for SQLX",
        ))
        .attach_printable("SQL Analytics is not implemented for Connector Events"),
        AnalyticsProvider::Clickhouse(ckh_pool)
        | AnalyticsProvider::CombinedSqlx(_, ckh_pool)
        | AnalyticsProvider::CombinedCkh(_, ckh_pool) => {
            get_connector_events(merchant_id, req, ckh_pool).await
        }
    }
    .switch()?;
    Ok(data)
}


// File: crates/analytics/src/connector_events/events.rs

use api_models::analytics::{connector_events::ConnectorEventsRequest, Granularity};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use crate::{
    query::{Aggregate, GroupByClause, QueryBuilder, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow},
};
pub trait ConnectorEventLogAnalytics: LoadRow<ConnectorEventsResult> {}

pub async fn get_connector_events<T>(
    merchant_id: &common_utils::id_type::MerchantId,
    query_param: ConnectorEventsRequest,
    pool: &T,
) -> FiltersResult<Vec<ConnectorEventsResult>>
where
    T: AnalyticsDataSource + ConnectorEventLogAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    let mut query_builder: QueryBuilder<T> =
        QueryBuilder::new(AnalyticsCollection::ConnectorEvents);
    query_builder.add_select_column("*").switch()?;

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

    query_builder
        .add_filter_clause("payment_id", &query_param.payment_id)
        .switch()?;

    if let Some(refund_id) = query_param.refund_id {
        query_builder
            .add_filter_clause("refund_id", &refund_id)
            .switch()?;
    }

    if let Some(dispute_id) = query_param.dispute_id {
        query_builder
            .add_filter_clause("dispute_id", &dispute_id)
            .switch()?;
    }

    //TODO!: update the execute_query function to return reports instead of plain errors...
    query_builder
        .execute_query::<ConnectorEventsResult, _>(pool)
        .await
        .change_context(FiltersError::QueryBuildingError)?
        .change_context(FiltersError::QueryExecutionFailure)
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ConnectorEventsResult {
    pub merchant_id: common_utils::id_type::MerchantId,
    pub payment_id: String,
    pub connector_name: Option<String>,
    pub request_id: Option<String>,
    pub flow: String,
    pub request: String,
    #[serde(rename = "masked_response")]
    pub response: Option<String>,
    pub error: Option<String>,
    pub status_code: u16,
    pub latency: Option<u128>,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
    pub method: Option<String>,
}


// File: crates/analytics/src/api_event/core.rs

use std::collections::HashMap;

use api_models::analytics::{
    api_event::{
        ApiEventMetricsBucketIdentifier, ApiEventMetricsBucketValue, ApiLogsRequest,
        ApiMetricsBucketResponse,
    },
    AnalyticsMetadata, ApiEventFiltersResponse, GetApiEventFiltersRequest,
    GetApiEventMetricRequest, MetricsResponse,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use router_env::{
    instrument, logger,
    tracing::{self, Instrument},
};

use super::{
    events::{get_api_event, ApiLogsResult},
    metrics::ApiEventMetricRow,
};
use crate::{
    errors::{AnalyticsError, AnalyticsResult},
    metrics,
    types::FiltersError,
    AnalyticsProvider,
};

#[instrument(skip_all)]
pub async fn api_events_core(
    pool: &AnalyticsProvider,
    req: ApiLogsRequest,
    merchant_id: &common_utils::id_type::MerchantId,
) -> AnalyticsResult<Vec<ApiLogsResult>> {
    let data = match pool {
        AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented(
            "API Events not implemented for SQLX",
        ))
        .attach_printable("SQL Analytics is not implemented for API Events"),
        AnalyticsProvider::Clickhouse(pool) => get_api_event(merchant_id, req, pool).await,
        AnalyticsProvider::CombinedSqlx(_sqlx_pool, ckh_pool)
        | AnalyticsProvider::CombinedCkh(_sqlx_pool, ckh_pool) => {
            get_api_event(merchant_id, req, ckh_pool).await
        }
    }
    .switch()?;
    Ok(data)
}

pub async fn get_filters(
    pool: &AnalyticsProvider,
    req: GetApiEventFiltersRequest,
    merchant_id: &common_utils::id_type::MerchantId,
) -> AnalyticsResult<ApiEventFiltersResponse> {
    use api_models::analytics::{api_event::ApiEventDimensions, ApiEventFilterValue};

    use super::filters::get_api_event_filter_for_dimension;
    use crate::api_event::filters::ApiEventFilter;

    let mut res = ApiEventFiltersResponse::default();
    for dim in req.group_by_names {
        let values = match pool {
            AnalyticsProvider::Sqlx(_pool) => Err(FiltersError::NotImplemented(
                "API Events not implemented for SQLX",
            ))
            .attach_printable("SQL Analytics is not implemented for API Events"),
            AnalyticsProvider::Clickhouse(ckh_pool)
            | AnalyticsProvider::CombinedSqlx(_, ckh_pool)
            | AnalyticsProvider::CombinedCkh(_, ckh_pool) => {
                get_api_event_filter_for_dimension(dim, merchant_id, &req.time_range, ckh_pool)
                    .await
            }
        }
        .switch()?
        .into_iter()
        .filter_map(|fil: ApiEventFilter| match dim {
            ApiEventDimensions::StatusCode => fil.status_code.map(|i| i.to_string()),
            ApiEventDimensions::FlowType => fil.flow_type,
            ApiEventDimensions::ApiFlow => fil.api_flow,
        })
        .collect::<Vec<String>>();
        res.query_data.push(ApiEventFilterValue {
            dimension: dim,
            values,
        })
    }

    Ok(res)
}

#[instrument(skip_all)]
pub async fn get_api_event_metrics(
    pool: &AnalyticsProvider,
    merchant_id: &common_utils::id_type::MerchantId,
    req: GetApiEventMetricRequest,
) -> AnalyticsResult<MetricsResponse<ApiMetricsBucketResponse>> {
    let mut metrics_accumulator: HashMap<ApiEventMetricsBucketIdentifier, ApiEventMetricRow> =
        HashMap::new();

    let mut set = tokio::task::JoinSet::new();
    for metric_type in req.metrics.iter().cloned() {
        let req = req.clone();
        let pool = pool.clone();
        let task_span = tracing::debug_span!(
            "analytics_api_metrics_query",
            api_event_metric = metric_type.as_ref()
        );

        // TODO: lifetime issues with joinset,
        // can be optimized away if joinset lifetime requirements are relaxed
        let merchant_id_scoped = merchant_id.to_owned();
        set.spawn(
            async move {
                let data = pool
                    .get_api_event_metrics(
                        &metric_type,
                        &req.group_by_names.clone(),
                        &merchant_id_scoped,
                        &req.filters,
                        req.time_series.map(|t| t.granularity),
                        &req.time_range,
                    )
                    .await
                    .change_context(AnalyticsError::UnknownError);
                (metric_type, data)
            }
            .instrument(task_span),
        );
    }

    while let Some((metric, data)) = set
        .join_next()
        .await
        .transpose()
        .change_context(AnalyticsError::UnknownError)?
    {
        let data = data?;
        let attributes = router_env::metric_attributes!(
            ("metric_type", metric.to_string()),
            ("source", pool.to_string()),
        );

        let value = u64::try_from(data.len());
        if let Ok(val) = value {
            metrics::BUCKETS_FETCHED.record(val, attributes);
            logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val);
        }
        for (id, value) in data {
            metrics_accumulator
                .entry(id)
                .and_modify(|data| {
                    data.api_count = data.api_count.or(value.api_count);
                    data.status_code_count = data.status_code_count.or(value.status_code_count);
                    data.latency = data.latency.or(value.latency);
                })
                .or_insert(value);
        }
    }

    let query_data: Vec<ApiMetricsBucketResponse> = metrics_accumulator
        .into_iter()
        .map(|(id, val)| ApiMetricsBucketResponse {
            values: ApiEventMetricsBucketValue {
                latency: val.latency,
                api_count: val.api_count,
                status_code_count: val.status_code_count,
            },
            dimensions: id,
        })
        .collect();

    Ok(MetricsResponse {
        query_data,
        meta_data: [AnalyticsMetadata {
            current_time_range: req.time_range,
        }],
    })
}


// File: crates/analytics/src/api_event/types.rs

use api_models::analytics::api_event::{ApiEventDimensions, ApiEventFilters};
use error_stack::ResultExt;

use crate::{
    query::{QueryBuilder, QueryFilter, QueryResult, ToSql},
    types::{AnalyticsCollection, AnalyticsDataSource},
};

impl<T> QueryFilter<T> for ApiEventFilters
where
    T: AnalyticsDataSource,
    AnalyticsCollection: ToSql<T>,
{
    fn set_filter_clause(&self, builder: &mut QueryBuilder<T>) -> QueryResult<()> {
        if !self.status_code.is_empty() {
            builder
                .add_filter_in_range_clause(ApiEventDimensions::StatusCode, &self.status_code)
                .attach_printable("Error adding status_code filter")?;
        }
        if !self.flow_type.is_empty() {
            builder
                .add_filter_in_range_clause(ApiEventDimensions::FlowType, &self.flow_type)
                .attach_printable("Error adding flow_type filter")?;
        }
        if !self.api_flow.is_empty() {
            builder
                .add_filter_in_range_clause(ApiEventDimensions::ApiFlow, &self.api_flow)
                .attach_printable("Error adding api_name filter")?;
        }

        Ok(())
    }
}


// File: crates/analytics/src/api_event/events.rs

use api_models::analytics::{
    api_event::{ApiLogsRequest, QueryType},
    Granularity,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use router_env::Flow;
use time::PrimitiveDateTime;

use crate::{
    query::{Aggregate, GroupByClause, QueryBuilder, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow},
};
pub trait ApiLogsFilterAnalytics: LoadRow<ApiLogsResult> {}

pub async fn get_api_event<T>(
    merchant_id: &common_utils::id_type::MerchantId,
    query_param: ApiLogsRequest,
    pool: &T,
) -> FiltersResult<Vec<ApiLogsResult>>
where
    T: AnalyticsDataSource + ApiLogsFilterAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    let mut query_builder: QueryBuilder<T> = QueryBuilder::new(AnalyticsCollection::ApiEvents);
    query_builder.add_select_column("*").switch()?;

    query_builder
        .add_filter_clause("merchant_id", merchant_id)
        .switch()?;
    match query_param.query_param {
        QueryType::Payment { payment_id } => {
            query_builder
                .add_filter_clause("payment_id", &payment_id)
                .switch()?;
            query_builder
                .add_filter_in_range_clause(
                    "api_flow",
                    &[
                        Flow::PaymentsCancel,
                        Flow::PaymentsCapture,
                        Flow::PaymentsConfirm,
                        Flow::PaymentsCreate,
                        Flow::PaymentsStart,
                        Flow::PaymentsUpdate,
                        Flow::RefundsCreate,
                        Flow::RefundsUpdate,
                        Flow::DisputesEvidenceSubmit,
                        Flow::AttachDisputeEvidence,
                        Flow::RetrieveDisputeEvidence,
                        Flow::IncomingWebhookReceive,
                    ],
                )
                .switch()?;
        }
        QueryType::Refund {
            payment_id,
            refund_id,
        } => {
            query_builder
                .add_filter_clause("payment_id", &payment_id)
                .switch()?;
            query_builder
                .add_filter_clause("refund_id", refund_id)
                .switch()?;
            query_builder
                .add_filter_in_range_clause("api_flow", &[Flow::RefundsCreate, Flow::RefundsUpdate])
                .switch()?;
        }
        QueryType::Dispute {
            payment_id,
            dispute_id,
        } => {
            query_builder
                .add_filter_clause("payment_id", &payment_id)
                .switch()?;
            query_builder
                .add_filter_clause("dispute_id", dispute_id)
                .switch()?;
            query_builder
                .add_filter_in_range_clause(
                    "api_flow",
                    &[
                        Flow::DisputesEvidenceSubmit,
                        Flow::AttachDisputeEvidence,
                        Flow::RetrieveDisputeEvidence,
                    ],
                )
                .switch()?;
        }
    }
    //TODO!: update the execute_query function to return reports instead of plain errors...
    query_builder
        .execute_query::<ApiLogsResult, _>(pool)
        .await
        .change_context(FiltersError::QueryBuildingError)?
        .change_context(FiltersError::QueryExecutionFailure)
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ApiLogsResult {
    pub merchant_id: common_utils::id_type::MerchantId,
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    pub refund_id: Option<String>,
    pub payment_method_id: Option<String>,
    pub payment_method: Option<String>,
    pub payment_method_type: Option<String>,
    pub customer_id: Option<String>,
    pub user_id: Option<String>,
    pub connector: Option<String>,
    pub request_id: Option<String>,
    pub flow_type: String,
    pub api_flow: String,
    pub api_auth_type: Option<String>,
    pub request: String,
    pub response: Option<String>,
    pub error: Option<String>,
    pub authentication_data: Option<String>,
    pub status_code: u16,
    pub latency: Option<u128>,
    pub user_agent: Option<String>,
    pub hs_latency: Option<u128>,
    pub ip_addr: Option<String>,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub created_at: PrimitiveDateTime,
    pub http_method: Option<String>,
    pub url_path: Option<String>,
}


// File: crates/analytics/src/api_event/metrics.rs

use api_models::analytics::{
    api_event::{
        ApiEventDimensions, ApiEventFilters, ApiEventMetrics, ApiEventMetricsBucketIdentifier,
    },
    Granularity, TimeRange,
};
use time::PrimitiveDateTime;

use crate::{
    query::{Aggregate, GroupByClause, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, LoadRow, MetricsResult},
};

mod api_count;
pub mod latency;
mod status_code_count;
use std::collections::HashSet;

use api_count::ApiCount;
use latency::MaxLatency;
use status_code_count::StatusCodeCount;

use self::latency::LatencyAvg;

#[derive(Debug, PartialEq, Eq, serde::Deserialize, Hash)]
pub struct ApiEventMetricRow {
    pub latency: Option<u64>,
    pub api_count: Option<u64>,
    pub status_code_count: Option<u64>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub start_bucket: Option<PrimitiveDateTime>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub end_bucket: Option<PrimitiveDateTime>,
}

pub trait ApiEventMetricAnalytics: LoadRow<ApiEventMetricRow> + LoadRow<LatencyAvg> {}

#[async_trait::async_trait]
pub trait ApiEventMetric<T>
where
    T: AnalyticsDataSource + ApiEventMetricAnalytics,
{
    async fn load_metrics(
        &self,
        dimensions: &[ApiEventDimensions],
        merchant_id: &common_utils::id_type::MerchantId,
        filters: &ApiEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(ApiEventMetricsBucketIdentifier, ApiEventMetricRow)>>;
}

#[async_trait::async_trait]
impl<T> ApiEventMetric<T> for ApiEventMetrics
where
    T: AnalyticsDataSource + ApiEventMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[ApiEventDimensions],
        merchant_id: &common_utils::id_type::MerchantId,
        filters: &ApiEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(ApiEventMetricsBucketIdentifier, ApiEventMetricRow)>> {
        match self {
            Self::Latency => {
                MaxLatency
                    .load_metrics(
                        dimensions,
                        merchant_id,
                        filters,
                        granularity,
                        time_range,
                        pool,
                    )
                    .await
            }
            Self::ApiCount => {
                ApiCount
                    .load_metrics(
                        dimensions,
                        merchant_id,
                        filters,
                        granularity,
                        time_range,
                        pool,
                    )
                    .await
            }
            Self::StatusCodeCount => {
                StatusCodeCount
                    .load_metrics(
                        dimensions,
                        merchant_id,
                        filters,
                        granularity,
                        time_range,
                        pool,
                    )
                    .await
            }
        }
    }
}


// File: crates/analytics/src/api_event/filters.rs

use api_models::analytics::{api_event::ApiEventDimensions, Granularity, TimeRange};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use crate::{
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow},
};

pub trait ApiEventFilterAnalytics: LoadRow<ApiEventFilter> {}

pub async fn get_api_event_filter_for_dimension<T>(
    dimension: ApiEventDimensions,
    merchant_id: &common_utils::id_type::MerchantId,
    time_range: &TimeRange,
    pool: &T,
) -> FiltersResult<Vec<ApiEventFilter>>
where
    T: AnalyticsDataSource + ApiEventFilterAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    let mut query_builder: QueryBuilder<T> = QueryBuilder::new(AnalyticsCollection::ApiEvents);

    query_builder.add_select_column(dimension).switch()?;
    time_range
        .set_filter_clause(&mut query_builder)
        .attach_printable("Error filtering time range")
        .switch()?;

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

    query_builder.set_distinct();

    query_builder
        .execute_query::<ApiEventFilter, _>(pool)
        .await
        .change_context(FiltersError::QueryBuildingError)?
        .change_context(FiltersError::QueryExecutionFailure)
}

#[derive(Debug, serde::Serialize, Eq, PartialEq, serde::Deserialize)]
pub struct ApiEventFilter {
    pub status_code: Option<i32>,
    pub flow_type: Option<String>,
    pub api_flow: Option<String>,
}


// File: crates/analytics/src/api_event/metrics/latency.rs

use std::collections::HashSet;

use api_models::analytics::{
    api_event::{ApiEventDimensions, ApiEventFilters, ApiEventMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

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

#[async_trait::async_trait]
impl<T> super::ApiEventMetric<T> for MaxLatency
where
    T: AnalyticsDataSource + super::ApiEventMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        _dimensions: &[ApiEventDimensions],
        merchant_id: &common_utils::id_type::MerchantId,
        filters: &ApiEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(ApiEventMetricsBucketIdentifier, ApiEventMetricRow)>> {
        let mut query_builder: QueryBuilder<T> = QueryBuilder::new(AnalyticsCollection::ApiEvents);

        query_builder
            .add_select_column(Aggregate::Sum {
                field: "latency",
                alias: Some("latency_sum"),
            })
            .switch()?;

        query_builder
            .add_select_column(Aggregate::Count {
                field: Some("latency"),
                alias: Some("latency_count"),
            })
            .switch()?;

        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;
        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

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

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

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        query_builder
            .add_custom_filter_clause("request", "10.63.134.6", FilterTypes::NotLike)
            .attach_printable("Error filtering out locker IP")
            .switch()?;

        query_builder
            .execute_query::<LatencyAvg, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    ApiEventMetricsBucketIdentifier::new(TimeRange {
                        start_time: match (granularity, i.start_bucket) {
                            (Some(g), Some(st)) => g.clip_to_start(st)?,
                            _ => time_range.start_time,
                        },
                        end_time: granularity.as_ref().map_or_else(
                            || Ok(time_range.end_time),
                            |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                        )?,
                    }),
                    ApiEventMetricRow {
                        latency: if i.latency_count != 0 {
                            Some(i.latency_sum.unwrap_or(0) / i.latency_count)
                        } else {
                            None
                        },
                        api_count: None,
                        status_code_count: None,
                        start_bucket: i.start_bucket,
                        end_bucket: i.end_bucket,
                    },
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(ApiEventMetricsBucketIdentifier, ApiEventMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}

#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
pub struct LatencyAvg {
    latency_sum: Option<u64>,
    latency_count: u64,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub start_bucket: Option<PrimitiveDateTime>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub end_bucket: Option<PrimitiveDateTime>,
}


// File: crates/analytics/src/api_event/metrics/status_code_count.rs

use std::collections::HashSet;

use api_models::analytics::{
    api_event::{ApiEventDimensions, ApiEventFilters, ApiEventMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

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

#[async_trait::async_trait]
impl<T> super::ApiEventMetric<T> for StatusCodeCount
where
    T: AnalyticsDataSource + super::ApiEventMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        _dimensions: &[ApiEventDimensions],
        merchant_id: &common_utils::id_type::MerchantId,
        filters: &ApiEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(ApiEventMetricsBucketIdentifier, ApiEventMetricRow)>> {
        let mut query_builder: QueryBuilder<T> = QueryBuilder::new(AnalyticsCollection::ApiEvents);

        query_builder
            .add_select_column(Aggregate::Count {
                field: Some("status_code"),
                alias: Some("status_code_count"),
            })
            .switch()?;

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

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

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;
        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

        query_builder
            .execute_query::<ApiEventMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    ApiEventMetricsBucketIdentifier::new(TimeRange {
                        start_time: match (granularity, i.start_bucket) {
                            (Some(g), Some(st)) => g.clip_to_start(st)?,
                            _ => time_range.start_time,
                        },
                        end_time: granularity.as_ref().map_or_else(
                            || Ok(time_range.end_time),
                            |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                        )?,
                    }),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(ApiEventMetricsBucketIdentifier, ApiEventMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// File: crates/analytics/src/api_event/metrics/api_count.rs

use std::collections::HashSet;

use api_models::analytics::{
    api_event::{ApiEventDimensions, ApiEventFilters, ApiEventMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

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

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

#[async_trait::async_trait]
impl<T> super::ApiEventMetric<T> for ApiCount
where
    T: AnalyticsDataSource + super::ApiEventMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        _dimensions: &[ApiEventDimensions],
        merchant_id: &common_utils::id_type::MerchantId,
        filters: &ApiEventFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(ApiEventMetricsBucketIdentifier, ApiEventMetricRow)>> {
        let mut query_builder: QueryBuilder<T> = QueryBuilder::new(AnalyticsCollection::ApiEvents);

        query_builder
            .add_select_column(Aggregate::Count {
                field: None,
                alias: Some("api_count"),
            })
            .switch()?;
        if !filters.flow_type.is_empty() {
            query_builder
                .add_filter_in_range_clause(ApiEventDimensions::FlowType, &filters.flow_type)
                .attach_printable("Error adding flow_type filter")
                .switch()?;
        }
        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;
        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .attach_printable("Error adding granularity")
                .switch()?;
        }

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

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        query_builder
            .execute_query::<ApiEventMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    ApiEventMetricsBucketIdentifier::new(TimeRange {
                        start_time: match (granularity, i.start_bucket) {
                            (Some(g), Some(st)) => g.clip_to_start(st)?,
                            _ => time_range.start_time,
                        },
                        end_time: granularity.as_ref().map_or_else(
                            || Ok(time_range.end_time),
                            |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                        )?,
                    }),
                    i,
                ))
            })
            .collect::<error_stack::Result<
                HashSet<(ApiEventMetricsBucketIdentifier, ApiEventMetricRow)>,
                crate::query::PostProcessingError,
            >>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// File: crates/analytics/src/payment_intents/core.rs

#![allow(dead_code)]
use std::collections::{HashMap, HashSet};

use api_models::analytics::{
    payment_intents::{
        MetricsBucketResponse, PaymentIntentDimensions, PaymentIntentMetrics,
        PaymentIntentMetricsBucketIdentifier,
    },
    GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, PaymentIntentFilterValue,
    PaymentIntentFiltersResponse, PaymentIntentsAnalyticsMetadata, PaymentIntentsMetricsResponse,
};
use bigdecimal::ToPrimitive;
use common_enums::Currency;
use common_utils::{errors::CustomResult, types::TimeRange};
use currency_conversion::{conversion::convert, types::ExchangeRates};
use error_stack::ResultExt;
use router_env::{
    instrument, logger,
    tracing::{self, Instrument},
};

use super::{
    filters::{get_payment_intent_filter_for_dimension, PaymentIntentFilterRow},
    metrics::PaymentIntentMetricRow,
    sankey::{get_sankey_data, SankeyRow},
    PaymentIntentMetricsAccumulator,
};
use crate::{
    enums::AuthInfo,
    errors::{AnalyticsError, AnalyticsResult},
    metrics,
    payment_intents::PaymentIntentMetricAccumulator,
    AnalyticsProvider,
};

#[derive(Debug)]
pub enum TaskType {
    MetricTask(
        PaymentIntentMetrics,
        CustomResult<
            HashSet<(PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricRow)>,
            AnalyticsError,
        >,
    ),
}

#[instrument(skip_all)]
pub async fn get_sankey(
    pool: &AnalyticsProvider,
    auth: &AuthInfo,
    req: TimeRange,
) -> AnalyticsResult<Vec<SankeyRow>> {
    match pool {
        AnalyticsProvider::Sqlx(_) => Err(AnalyticsError::NotImplemented(
            "Sankey not implemented for sqlx",
        ))?,
        AnalyticsProvider::Clickhouse(ckh_pool)
        | AnalyticsProvider::CombinedCkh(_, ckh_pool)
        | AnalyticsProvider::CombinedSqlx(_, ckh_pool) => {
            let sankey_rows = get_sankey_data(ckh_pool, auth, &req)
                .await
                .change_context(AnalyticsError::UnknownError)?;
            Ok(sankey_rows)
        }
    }
}

#[instrument(skip_all)]
pub async fn get_metrics(
    pool: &AnalyticsProvider,
    ex_rates: &Option<ExchangeRates>,
    auth: &AuthInfo,
    req: GetPaymentIntentMetricRequest,
) -> AnalyticsResult<PaymentIntentsMetricsResponse<MetricsBucketResponse>> {
    let mut metrics_accumulator: HashMap<
        PaymentIntentMetricsBucketIdentifier,
        PaymentIntentMetricsAccumulator,
    > = HashMap::new();

    let mut set = tokio::task::JoinSet::new();
    for metric_type in req.metrics.iter().cloned() {
        let req = req.clone();
        let pool = pool.clone();
        let task_span = tracing::debug_span!(
            "analytics_payment_intents_metrics_query",
            payment_metric = metric_type.as_ref()
        );

        // TODO: lifetime issues with joinset,
        // can be optimized away if joinset lifetime requirements are relaxed
        let auth_scoped = auth.to_owned();
        set.spawn(
            async move {
                let data = pool
                    .get_payment_intent_metrics(
                        &metric_type,
                        &req.group_by_names.clone(),
                        &auth_scoped,
                        &req.filters,
                        req.time_series.map(|t| t.granularity),
                        &req.time_range,
                    )
                    .await
                    .change_context(AnalyticsError::UnknownError);
                TaskType::MetricTask(metric_type, data)
            }
            .instrument(task_span),
        );
    }

    while let Some(task_type) = set
        .join_next()
        .await
        .transpose()
        .change_context(AnalyticsError::UnknownError)?
    {
        match task_type {
            TaskType::MetricTask(metric, data) => {
                let data = data?;
                let attributes = router_env::metric_attributes!(
                    ("metric_type", metric.to_string()),
                    ("source", pool.to_string()),
                );

                let value = u64::try_from(data.len());
                if let Ok(val) = value {
                    metrics::BUCKETS_FETCHED.record(val, attributes);
                    logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val);
                }

                for (id, value) in data {
                    logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for metric {metric}");
                    let metrics_builder = metrics_accumulator.entry(id).or_default();
                    match metric {
                        PaymentIntentMetrics::SuccessfulSmartRetries
                        | PaymentIntentMetrics::SessionizedSuccessfulSmartRetries => {
                            metrics_builder
                                .successful_smart_retries
                                .add_metrics_bucket(&value)
                        }
                        PaymentIntentMetrics::TotalSmartRetries
                        | PaymentIntentMetrics::SessionizedTotalSmartRetries => metrics_builder
                            .total_smart_retries
                            .add_metrics_bucket(&value),
                        PaymentIntentMetrics::SmartRetriedAmount
                        | PaymentIntentMetrics::SessionizedSmartRetriedAmount => metrics_builder
                            .smart_retried_amount
                            .add_metrics_bucket(&value),
                        PaymentIntentMetrics::PaymentIntentCount
                        | PaymentIntentMetrics::SessionizedPaymentIntentCount => metrics_builder
                            .payment_intent_count
                            .add_metrics_bucket(&value),
                        PaymentIntentMetrics::PaymentsSuccessRate
                        | PaymentIntentMetrics::SessionizedPaymentsSuccessRate => metrics_builder
                            .payments_success_rate
                            .add_metrics_bucket(&value),
                        PaymentIntentMetrics::SessionizedPaymentProcessedAmount
                        | PaymentIntentMetrics::PaymentProcessedAmount => metrics_builder
                            .payment_processed_amount
                            .add_metrics_bucket(&value),
                        PaymentIntentMetrics::SessionizedPaymentsDistribution => metrics_builder
                            .payments_distribution
                            .add_metrics_bucket(&value),
                    }
                }

                logger::debug!(
                    "Analytics Accumulated Results: metric: {}, results: {:#?}",
                    metric,
                    metrics_accumulator
                );
            }
        }
    }

    let mut success = 0;
    let mut success_without_smart_retries = 0;
    let mut total_smart_retried_amount = 0;
    let mut total_smart_retried_amount_in_usd = 0;
    let mut total_smart_retried_amount_without_smart_retries = 0;
    let mut total_smart_retried_amount_without_smart_retries_in_usd = 0;
    let mut total = 0;
    let mut total_payment_processed_amount = 0;
    let mut total_payment_processed_amount_in_usd = 0;
    let mut total_payment_processed_count = 0;
    let mut total_payment_processed_amount_without_smart_retries = 0;
    let mut total_payment_processed_amount_without_smart_retries_in_usd = 0;
    let mut total_payment_processed_count_without_smart_retries = 0;
    let query_data: Vec<MetricsBucketResponse> = metrics_accumulator
        .into_iter()
        .map(|(id, val)| {
            let mut collected_values = val.collect();
            if let Some(success_count) = collected_values.successful_payments {
                success += success_count;
            }
            if let Some(success_count) = collected_values.successful_payments_without_smart_retries
            {
                success_without_smart_retries += success_count;
            }
            if let Some(total_count) = collected_values.total_payments {
                total += total_count;
            }
            if let Some(retried_amount) = collected_values.smart_retried_amount {
                let amount_in_usd = if let Some(ex_rates) = ex_rates {
                    id.currency
                        .and_then(|currency| {
                            i64::try_from(retried_amount)
                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
                                .ok()
                                .and_then(|amount_i64| {
                                    convert(ex_rates, currency, Currency::USD, amount_i64)
                                        .inspect_err(|e| {
                                            logger::error!("Currency conversion error: {:?}", e)
                                        })
                                        .ok()
                                })
                        })
                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
                        .unwrap_or_default()
                } else {
                    None
                };
                collected_values.smart_retried_amount_in_usd = amount_in_usd;
                total_smart_retried_amount += retried_amount;
                total_smart_retried_amount_in_usd += amount_in_usd.unwrap_or(0);
            }
            if let Some(retried_amount) =
                collected_values.smart_retried_amount_without_smart_retries
            {
                let amount_in_usd = if let Some(ex_rates) = ex_rates {
                    id.currency
                        .and_then(|currency| {
                            i64::try_from(retried_amount)
                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
                                .ok()
                                .and_then(|amount_i64| {
                                    convert(ex_rates, currency, Currency::USD, amount_i64)
                                        .inspect_err(|e| {
                                            logger::error!("Currency conversion error: {:?}", e)
                                        })
                                        .ok()
                                })
                        })
                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
                        .unwrap_or_default()
                } else {
                    None
                };
                collected_values.smart_retried_amount_without_smart_retries_in_usd = amount_in_usd;
                total_smart_retried_amount_without_smart_retries += retried_amount;
                total_smart_retried_amount_without_smart_retries_in_usd +=
                    amount_in_usd.unwrap_or(0);
            }
            if let Some(amount) = collected_values.payment_processed_amount {
                let amount_in_usd = if let Some(ex_rates) = ex_rates {
                    id.currency
                        .and_then(|currency| {
                            i64::try_from(amount)
                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
                                .ok()
                                .and_then(|amount_i64| {
                                    convert(ex_rates, currency, Currency::USD, amount_i64)
                                        .inspect_err(|e| {
                                            logger::error!("Currency conversion error: {:?}", e)
                                        })
                                        .ok()
                                })
                        })
                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
                        .unwrap_or_default()
                } else {
                    None
                };
                collected_values.payment_processed_amount_in_usd = amount_in_usd;
                total_payment_processed_amount_in_usd += amount_in_usd.unwrap_or(0);
                total_payment_processed_amount += amount;
            }
            if let Some(count) = collected_values.payment_processed_count {
                total_payment_processed_count += count;
            }
            if let Some(amount) = collected_values.payment_processed_amount_without_smart_retries {
                let amount_in_usd = if let Some(ex_rates) = ex_rates {
                    id.currency
                        .and_then(|currency| {
                            i64::try_from(amount)
                                .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e))
                                .ok()
                                .and_then(|amount_i64| {
                                    convert(ex_rates, currency, Currency::USD, amount_i64)
                                        .inspect_err(|e| {
                                            logger::error!("Currency conversion error: {:?}", e)
                                        })
                                        .ok()
                                })
                        })
                        .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64())
                        .unwrap_or_default()
                } else {
                    None
                };
                collected_values.payment_processed_amount_without_smart_retries_in_usd =
                    amount_in_usd;
                total_payment_processed_amount_without_smart_retries_in_usd +=
                    amount_in_usd.unwrap_or(0);
                total_payment_processed_amount_without_smart_retries += amount;
            }
            if let Some(count) = collected_values.payment_processed_count_without_smart_retries {
                total_payment_processed_count_without_smart_retries += count;
            }
            MetricsBucketResponse {
                values: collected_values,
                dimensions: id,
            }
        })
        .collect();
    let total_success_rate = match (success, total) {
        (s, t) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
        _ => None,
    };
    let total_success_rate_without_smart_retries = match (success_without_smart_retries, total) {
        (s, t) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)),
        _ => None,
    };
    Ok(PaymentIntentsMetricsResponse {
        query_data,
        meta_data: [PaymentIntentsAnalyticsMetadata {
            total_success_rate,
            total_success_rate_without_smart_retries,
            total_smart_retried_amount: Some(total_smart_retried_amount),
            total_smart_retried_amount_without_smart_retries: Some(
                total_smart_retried_amount_without_smart_retries,
            ),
            total_payment_processed_amount: Some(total_payment_processed_amount),
            total_payment_processed_amount_without_smart_retries: Some(
                total_payment_processed_amount_without_smart_retries,
            ),
            total_smart_retried_amount_in_usd: if ex_rates.is_some() {
                Some(total_smart_retried_amount_in_usd)
            } else {
                None
            },
            total_smart_retried_amount_without_smart_retries_in_usd: if ex_rates.is_some() {
                Some(total_smart_retried_amount_without_smart_retries_in_usd)
            } else {
                None
            },
            total_payment_processed_amount_in_usd: if ex_rates.is_some() {
                Some(total_payment_processed_amount_in_usd)
            } else {
                None
            },
            total_payment_processed_amount_without_smart_retries_in_usd: if ex_rates.is_some() {
                Some(total_payment_processed_amount_without_smart_retries_in_usd)
            } else {
                None
            },
            total_payment_processed_count: Some(total_payment_processed_count),
            total_payment_processed_count_without_smart_retries: Some(
                total_payment_processed_count_without_smart_retries,
            ),
        }],
    })
}

pub async fn get_filters(
    pool: &AnalyticsProvider,
    req: GetPaymentIntentFiltersRequest,
    merchant_id: &common_utils::id_type::MerchantId,
) -> AnalyticsResult<PaymentIntentFiltersResponse> {
    let mut res = PaymentIntentFiltersResponse::default();

    for dim in req.group_by_names {
        let values = match pool {
                        AnalyticsProvider::Sqlx(pool) => {
                get_payment_intent_filter_for_dimension(dim, merchant_id, &req.time_range, pool)
                    .await
            }
                        AnalyticsProvider::Clickhouse(pool) => {
                get_payment_intent_filter_for_dimension(dim, merchant_id, &req.time_range, pool)
                    .await
            }
                    AnalyticsProvider::CombinedCkh(sqlx_poll, ckh_pool) => {
                let ckh_result = get_payment_intent_filter_for_dimension(
                    dim,
                    merchant_id,
                    &req.time_range,
                    ckh_pool,
                )
                .await;
                let sqlx_result = get_payment_intent_filter_for_dimension(
                    dim,
                    merchant_id,
                    &req.time_range,
                    sqlx_poll,
                )
                .await;
                match (&sqlx_result, &ckh_result) {
                    (Ok(ref sqlx_res), Ok(ref ckh_res)) if sqlx_res != ckh_res => {
                        router_env::logger::error!(clickhouse_result=?ckh_res, postgres_result=?sqlx_res, "Mismatch between clickhouse & postgres payment intents analytics filters")
                    },
                    _ => {}
                };
                ckh_result
            }
                    AnalyticsProvider::CombinedSqlx(sqlx_poll, ckh_pool) => {
                let ckh_result = get_payment_intent_filter_for_dimension(
                    dim,
                    merchant_id,
                    &req.time_range,
                    ckh_pool,
                )
                .await;
                let sqlx_result = get_payment_intent_filter_for_dimension(
                    dim,
                    merchant_id,
                    &req.time_range,
                    sqlx_poll,
                )
                .await;
                match (&sqlx_result, &ckh_result) {
                    (Ok(ref sqlx_res), Ok(ref ckh_res)) if sqlx_res != ckh_res => {
                        router_env::logger::error!(clickhouse_result=?ckh_res, postgres_result=?sqlx_res, "Mismatch between clickhouse & postgres payment intents analytics filters")
                    },
                    _ => {}
                };
                sqlx_result
            }
        }
        .change_context(AnalyticsError::UnknownError)?
        .into_iter()
        .filter_map(|fil: PaymentIntentFilterRow| match dim {
            PaymentIntentDimensions::PaymentIntentStatus => fil.status.map(|i| i.as_ref().to_string()),
            PaymentIntentDimensions::Currency => fil.currency.map(|i| i.as_ref().to_string()),
            PaymentIntentDimensions::ProfileId => fil.profile_id,
            PaymentIntentDimensions::Connector => fil.connector,
            PaymentIntentDimensions::AuthType => fil.authentication_type.map(|i| i.as_ref().to_string()),
            PaymentIntentDimensions::PaymentMethod => fil.payment_method,
            PaymentIntentDimensions::PaymentMethodType => fil.payment_method_type,
            PaymentIntentDimensions::CardNetwork => fil.card_network,
            PaymentIntentDimensions::MerchantId => fil.merchant_id,
            PaymentIntentDimensions::CardLast4 => fil.card_last_4,
            PaymentIntentDimensions::CardIssuer => fil.card_issuer,
            PaymentIntentDimensions::ErrorReason => fil.error_reason,
        })
        .collect::<Vec<String>>();
        res.query_data.push(PaymentIntentFilterValue {
            dimension: dim,
            values,
        })
    }
    Ok(res)
}


// File: crates/analytics/src/payment_intents/types.rs

use api_models::analytics::payment_intents::{PaymentIntentDimensions, PaymentIntentFilters};
use error_stack::ResultExt;

use crate::{
    query::{QueryBuilder, QueryFilter, QueryResult, ToSql},
    types::{AnalyticsCollection, AnalyticsDataSource},
};

impl<T> QueryFilter<T> for PaymentIntentFilters
where
    T: AnalyticsDataSource,
    AnalyticsCollection: ToSql<T>,
{
    fn set_filter_clause(&self, builder: &mut QueryBuilder<T>) -> QueryResult<()> {
        if !self.status.is_empty() {
            builder
                .add_filter_in_range_clause(
                    PaymentIntentDimensions::PaymentIntentStatus,
                    &self.status,
                )
                .attach_printable("Error adding payment intent status filter")?;
        }
        if !self.currency.is_empty() {
            builder
                .add_filter_in_range_clause(PaymentIntentDimensions::Currency, &self.currency)
                .attach_printable("Error adding currency filter")?;
        }
        if !self.profile_id.is_empty() {
            builder
                .add_filter_in_range_clause(PaymentIntentDimensions::ProfileId, &self.profile_id)
                .attach_printable("Error adding profile id filter")?;
        }
        if !self.connector.is_empty() {
            builder
                .add_filter_in_range_clause(PaymentIntentDimensions::Connector, &self.connector)
                .attach_printable("Error adding connector filter")?;
        }
        if !self.auth_type.is_empty() {
            builder
                .add_filter_in_range_clause(PaymentIntentDimensions::AuthType, &self.auth_type)
                .attach_printable("Error adding auth type filter")?;
        }
        if !self.payment_method.is_empty() {
            builder
                .add_filter_in_range_clause(
                    PaymentIntentDimensions::PaymentMethod,
                    &self.payment_method,
                )
                .attach_printable("Error adding payment method filter")?;
        }
        if !self.payment_method_type.is_empty() {
            builder
                .add_filter_in_range_clause(
                    PaymentIntentDimensions::PaymentMethodType,
                    &self.payment_method_type,
                )
                .attach_printable("Error adding payment method type filter")?;
        }
        if !self.card_network.is_empty() {
            builder
                .add_filter_in_range_clause(
                    PaymentIntentDimensions::CardNetwork,
                    &self.card_network,
                )
                .attach_printable("Error adding card network filter")?;
        }
        if !self.merchant_id.is_empty() {
            builder
                .add_filter_in_range_clause(PaymentIntentDimensions::MerchantId, &self.merchant_id)
                .attach_printable("Error adding merchant id filter")?;
        }
        if !self.card_last_4.is_empty() {
            builder
                .add_filter_in_range_clause(PaymentIntentDimensions::CardLast4, &self.card_last_4)
                .attach_printable("Error adding card last 4 filter")?;
        }
        if !self.card_issuer.is_empty() {
            builder
                .add_filter_in_range_clause(PaymentIntentDimensions::CardIssuer, &self.card_issuer)
                .attach_printable("Error adding card issuer filter")?;
        }
        if !self.error_reason.is_empty() {
            builder
                .add_filter_in_range_clause(
                    PaymentIntentDimensions::ErrorReason,
                    &self.error_reason,
                )
                .attach_printable("Error adding error reason filter")?;
        }
        if !self.customer_id.is_empty() {
            builder
                .add_filter_in_range_clause("customer_id", &self.customer_id)
                .attach_printable("Error adding customer id filter")?;
        }
        Ok(())
    }
}


// File: crates/analytics/src/payment_intents/metrics.rs

use std::collections::HashSet;

use api_models::analytics::{
    payment_intents::{
        PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetrics,
        PaymentIntentMetricsBucketIdentifier,
    },
    Granularity, TimeRange,
};
use diesel_models::enums as storage_enums;
use time::PrimitiveDateTime;

use crate::{
    enums::AuthInfo,
    query::{Aggregate, GroupByClause, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, DBEnumWrapper, LoadRow, MetricsResult},
};

mod payment_intent_count;
mod payment_processed_amount;
mod payments_success_rate;
mod sessionized_metrics;
mod smart_retried_amount;
mod successful_smart_retries;
mod total_smart_retries;

use payment_intent_count::PaymentIntentCount;
use payment_processed_amount::PaymentProcessedAmount;
use payments_success_rate::PaymentsSuccessRate;
use smart_retried_amount::SmartRetriedAmount;
use successful_smart_retries::SuccessfulSmartRetries;
use total_smart_retries::TotalSmartRetries;

#[derive(Debug, PartialEq, Eq, serde::Deserialize, Hash)]
pub struct PaymentIntentMetricRow {
    pub status: Option<DBEnumWrapper<storage_enums::IntentStatus>>,
    pub currency: Option<DBEnumWrapper<storage_enums::Currency>>,
    pub profile_id: Option<String>,
    pub connector: Option<String>,
    pub authentication_type: Option<DBEnumWrapper<storage_enums::AuthenticationType>>,
    pub payment_method: Option<String>,
    pub payment_method_type: Option<String>,
    pub card_network: Option<String>,
    pub merchant_id: Option<String>,
    pub card_last_4: Option<String>,
    pub card_issuer: Option<String>,
    pub error_reason: Option<String>,
    pub first_attempt: Option<i64>,
    pub total: Option<bigdecimal::BigDecimal>,
    pub count: Option<i64>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub start_bucket: Option<PrimitiveDateTime>,
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub end_bucket: Option<PrimitiveDateTime>,
}

pub trait PaymentIntentMetricAnalytics: LoadRow<PaymentIntentMetricRow> {}

#[async_trait::async_trait]
pub trait PaymentIntentMetric<T>
where
    T: AnalyticsDataSource + PaymentIntentMetricAnalytics,
{
    async fn load_metrics(
        &self,
        dimensions: &[PaymentIntentDimensions],
        auth: &AuthInfo,
        filters: &PaymentIntentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricRow)>>;
}

#[async_trait::async_trait]
impl<T> PaymentIntentMetric<T> for PaymentIntentMetrics
where
    T: AnalyticsDataSource + PaymentIntentMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[PaymentIntentDimensions],
        auth: &AuthInfo,
        filters: &PaymentIntentFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricRow)>>
    {
        match self {
            Self::SuccessfulSmartRetries => {
                SuccessfulSmartRetries
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::TotalSmartRetries => {
                TotalSmartRetries
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::SmartRetriedAmount => {
                SmartRetriedAmount
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::PaymentIntentCount => {
                PaymentIntentCount
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::PaymentsSuccessRate => {
                PaymentsSuccessRate
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::PaymentProcessedAmount => {
                PaymentProcessedAmount
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::SessionizedSuccessfulSmartRetries => {
                sessionized_metrics::SuccessfulSmartRetries
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::SessionizedTotalSmartRetries => {
                sessionized_metrics::TotalSmartRetries
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::SessionizedSmartRetriedAmount => {
                sessionized_metrics::SmartRetriedAmount
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::SessionizedPaymentIntentCount => {
                sessionized_metrics::PaymentIntentCount
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::SessionizedPaymentsSuccessRate => {
                sessionized_metrics::PaymentsSuccessRate
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::SessionizedPaymentProcessedAmount => {
                sessionized_metrics::PaymentProcessedAmount
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
            Self::SessionizedPaymentsDistribution => {
                sessionized_metrics::PaymentsDistribution
                    .load_metrics(dimensions, auth, filters, granularity, time_range, pool)
                    .await
            }
        }
    }
}