use std::collections::HashMap;

use api_models::analytics::{
    sdk_events::{
        MetricsBucketResponse, SdkEventMetrics, SdkEventMetricsBucketIdentifier, SdkEventsRequest,
    },
    AnalyticsMetadata, GetSdkEventFiltersRequest, GetSdkEventMetricRequest, MetricsResponse,
    SdkEventFiltersResponse,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use router_env::{instrument, logger, tracing};

use super::{
    events::{get_sdk_event, SdkEventsResult},
    SdkEventMetricsAccumulator,
};
use crate::{
    errors::{AnalyticsError, AnalyticsResult},
    sdk_events::SdkEventMetricAccumulator,
    types::FiltersError,
    AnalyticsProvider,
};

#[instrument(skip_all)]
pub async fn sdk_events_core(
    pool: &AnalyticsProvider,
    req: SdkEventsRequest,
    publishable_key: &String,
) -> AnalyticsResult<Vec<SdkEventsResult>> {
    match pool {
        AnalyticsProvider::Sqlx(_) => Err(FiltersError::NotImplemented(
            "SDK Events not implemented for SQLX",
        ))
        .attach_printable("SQL Analytics is not implemented for Sdk Events"),
        AnalyticsProvider::Clickhouse(pool) => get_sdk_event(publishable_key, req, pool).await,
        AnalyticsProvider::CombinedSqlx(_sqlx_pool, ckh_pool)
        | AnalyticsProvider::CombinedCkh(_sqlx_pool, ckh_pool) => {
            get_sdk_event(publishable_key, req, ckh_pool).await
        }
    }
    .switch()
}

#[instrument(skip_all)]
pub async fn get_metrics(
    pool: &AnalyticsProvider,
    publishable_key: &String,
    req: GetSdkEventMetricRequest,
) -> AnalyticsResult<MetricsResponse<MetricsBucketResponse>> {
    let mut metrics_accumulator: HashMap<
        SdkEventMetricsBucketIdentifier,
        SdkEventMetricsAccumulator,
    > = HashMap::new();

    let mut set = tokio::task::JoinSet::new();
    for metric_type in req.metrics.iter().cloned() {
        let req = req.clone();
        let publishable_key_scoped = publishable_key.to_owned();
        let pool = pool.clone();
        set.spawn(async move {
            let data = pool
                .get_sdk_event_metrics(
                    &metric_type,
                    &req.group_by_names.clone(),
                    &publishable_key_scoped,
                    &req.filters,
                    req.time_series.map(|t| t.granularity),
                    &req.time_range,
                )
                .await
                .change_context(AnalyticsError::UnknownError);
            (metric_type, data)
        });
    }

    while let Some((metric, data)) = set
        .join_next()
        .await
        .transpose()
        .change_context(AnalyticsError::UnknownError)?
    {
        logger::info!("Logging Result {:?}", data);
        for (id, value) in data? {
            let metrics_builder = metrics_accumulator.entry(id).or_default();
            match metric {
                SdkEventMetrics::PaymentAttempts => {
                    metrics_builder.payment_attempts.add_metrics_bucket(&value)
                }
                SdkEventMetrics::PaymentMethodsCallCount => metrics_builder
                    .payment_methods_call_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::SdkRenderedCount => metrics_builder
                    .sdk_rendered_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::SdkInitiatedCount => metrics_builder
                    .sdk_initiated_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::PaymentMethodSelectedCount => metrics_builder
                    .payment_method_selected_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::PaymentDataFilledCount => metrics_builder
                    .payment_data_filled_count
                    .add_metrics_bucket(&value),
                SdkEventMetrics::AveragePaymentTime => metrics_builder
                    .average_payment_time
                    .add_metrics_bucket(&value),
                SdkEventMetrics::LoadTime => metrics_builder.load_time.add_metrics_bucket(&value),
            }
        }

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

    let query_data: Vec<MetricsBucketResponse> = metrics_accumulator
        .into_iter()
        .map(|(id, val)| MetricsBucketResponse {
            values: val.collect(),
            dimensions: id,
        })
        .collect();

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

#[allow(dead_code)]
pub async fn get_filters(
    pool: &AnalyticsProvider,
    req: GetSdkEventFiltersRequest,
    publishable_key: &String,
) -> AnalyticsResult<SdkEventFiltersResponse> {
    use api_models::analytics::{sdk_events::SdkEventDimensions, SdkEventFilterValue};

    use super::filters::get_sdk_event_filter_for_dimension;
    use crate::sdk_events::filters::SdkEventFilter;

    let mut res = SdkEventFiltersResponse::default();

    for dim in req.group_by_names {
        let values = match pool {
            AnalyticsProvider::Sqlx(_pool) => Err(FiltersError::NotImplemented(
                "SDK Events not implemented for SQLX",
            ))
            .attach_printable("SQL Analytics is not implemented for SDK Events"),
            AnalyticsProvider::Clickhouse(pool) => {
                get_sdk_event_filter_for_dimension(dim, publishable_key, &req.time_range, pool)
                    .await
            }
            AnalyticsProvider::CombinedSqlx(_sqlx_pool, ckh_pool)
            | AnalyticsProvider::CombinedCkh(_sqlx_pool, ckh_pool) => {
                get_sdk_event_filter_for_dimension(dim, publishable_key, &req.time_range, ckh_pool)
                    .await
            }
        }
        .change_context(AnalyticsError::UnknownError)?
        .into_iter()
        .filter_map(|fil: SdkEventFilter| match dim {
            SdkEventDimensions::PaymentMethod => fil.payment_method,
            SdkEventDimensions::Platform => fil.platform,
            SdkEventDimensions::BrowserName => fil.browser_name,
            SdkEventDimensions::Source => fil.source,
            SdkEventDimensions::Component => fil.component,
            SdkEventDimensions::PaymentExperience => fil.payment_experience,
        })
        .collect::<Vec<String>>();
        res.query_data.push(SdkEventFilterValue {
            dimension: dim,
            values,
        })
    }

    Ok(res)
}
