// file: kb_lib/src/local_pipeline_diagnostics.rs

//! Local pipeline diagnostics service.

/// Local pipeline diagnostics service.
#[derive(Debug, Clone)]
pub struct LocalPipelineDiagnosticsService {
    database: std::sync::Arc<crate::Database>,
}

impl LocalPipelineDiagnosticsService {
    /// Creates a new local pipeline diagnostics service.
    pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
        return Self { database };
    }

    /// Builds a validation-oriented diagnostics summary from already persisted data.
    ///
    /// This path intentionally skips row-level pair summaries and diagnostic
    /// samples. Those sections are useful for UI inspection, but they can be
    /// expensive on larger SQLite corpora because pair-level joins multiply
    /// decoded events, trades and candle buckets. Validation only needs global
    /// counters, DEX summaries, Raydium surface summaries and classification
    /// summaries.
    pub async fn diagnose_for_validation(
        &self,
    ) -> Result<crate::LocalPipelineDiagnosticSummaryDto, crate::Error> {
        let counters_result = crate::query_local_pipeline_diagnostic_get_counters(self.database.as_ref()).await;
        let counters = match counters_result {
            Ok(counters) => counters,
            Err(error) => return Err(error),
        };
        let dex_summaries_result =
            crate::query_local_pipeline_diagnostic_list_summaries(self.database.as_ref()).await;
        let dex_summaries = match dex_summaries_result {
            Ok(dex_summaries) => dex_summaries,
            Err(error) => return Err(error),
        };
        let raydium_program_instruction_summaries_result =
            crate::query_local_raydium_program_instruction_diagnostic_list_summaries(
                self.database.as_ref(),
            )
            .await;
        let raydium_program_instruction_summaries =
            match raydium_program_instruction_summaries_result {
                Ok(summaries) => summaries,
                Err(error) => return Err(error),
            };
        let raydium_surface_summaries =
            build_raydium_surface_summaries(&dex_summaries, &raydium_program_instruction_summaries);
        let decoded_event_summaries_result =
            crate::query_local_decoded_event_diagnostic_list_summaries(self.database.as_ref())
                .await;
        let decoded_event_summaries = match decoded_event_summaries_result {
            Ok(decoded_event_summaries) => decoded_event_summaries,
            Err(error) => return Err(error),
        };
        let event_classification_summaries_result =
            crate::query_local_event_classification_diagnostic_list_summaries(
                self.database.as_ref(),
            )
            .await;
        let event_classification_summaries = match event_classification_summaries_result {
            Ok(summaries) => summaries,
            Err(error) => return Err(error),
        };
        let event_coverage_summaries_result =
            load_event_coverage_summaries(self.database.as_ref()).await;
        let event_coverage_summaries = match event_coverage_summaries_result {
            Ok(summaries) => summaries,
            Err(error) => return Err(error),
        };
        let blocking_issue_count = counters.actionable_missing_trade_event_count
            + counters.invalid_trade_event_count
            + counters.duplicate_decoded_event_trade_count
            + counters.duplicate_candle_bucket_count;
        let diagnostics_clean = blocking_issue_count == 0;
        return Ok(build_lightweight_diagnostic_summary(
            counters,
            diagnostics_clean,
            blocking_issue_count,
            dex_summaries,
            raydium_surface_summaries,
            decoded_event_summaries,
            event_classification_summaries,
            event_coverage_summaries,
        ));
    }

    /// Builds a local pipeline diagnostics summary from already persisted data.
    pub async fn diagnose(&self) -> Result<crate::LocalPipelineDiagnosticSummaryDto, crate::Error> {
        let sample_limit = 25_i64;
        let counters_result =
            crate::query_local_pipeline_diagnostic_get_counters(self.database.as_ref()).await;
        let counters = match counters_result {
            Ok(counters) => counters,
            Err(error) => return Err(error),
        };
        let dex_summaries_result =
            crate::query_local_pipeline_diagnostic_list_summaries(self.database.as_ref()).await;
        let dex_summaries = match dex_summaries_result {
            Ok(dex_summaries) => dex_summaries,
            Err(error) => return Err(error),
        };
        let raydium_program_instruction_summaries_result =
            crate::query_local_raydium_program_instruction_diagnostic_list_summaries(
                self.database.as_ref(),
            )
            .await;
        let raydium_program_instruction_summaries =
            match raydium_program_instruction_summaries_result {
                Ok(summaries) => summaries,
                Err(error) => return Err(error),
            };
        let raydium_surface_summaries =
            build_raydium_surface_summaries(&dex_summaries, &raydium_program_instruction_summaries);
        let pair_summaries_result =
            crate::query_local_pair_diagnostic_list_summaries(self.database.as_ref()).await;
        let pair_summaries = match pair_summaries_result {
            Ok(pair_summaries) => pair_summaries,
            Err(error) => return Err(error),
        };
        let pair_actionability_summaries_result =
            crate::query_local_pair_actionability_diagnostic_list_summaries(self.database.as_ref())
                .await;
        let pair_actionability_summaries = match pair_actionability_summaries_result {
            Ok(summaries) => summaries,
            Err(error) => return Err(error),
        };
        let pair_trading_readiness_summaries_result =
            crate::query_local_pair_trading_readiness_diagnostic_list_summaries(
                self.database.as_ref(),
            )
            .await;
        let pair_trading_readiness_summaries = match pair_trading_readiness_summaries_result {
            Ok(summaries) => summaries,
            Err(error) => return Err(error),
        };
        let decoded_event_summaries_result =
            crate::query_local_decoded_event_diagnostic_list_summaries(self.database.as_ref())
                .await;
        let decoded_event_summaries = match decoded_event_summaries_result {
            Ok(decoded_event_summaries) => decoded_event_summaries,
            Err(error) => return Err(error),
        };
        let event_classification_summaries_result =
            crate::query_local_event_classification_diagnostic_list_summaries(
                self.database.as_ref(),
            )
            .await;
        let event_classification_summaries = match event_classification_summaries_result {
            Ok(summaries) => summaries,
            Err(error) => return Err(error),
        };
        let event_coverage_summaries_result =
            load_event_coverage_summaries(self.database.as_ref()).await;
        let event_coverage_summaries = match event_coverage_summaries_result {
            Ok(summaries) => summaries,
            Err(error) => return Err(error),
        };
        let missing_trade_event_reason_summaries_result =
            crate::query_local_missing_trade_event_reason_list_summaries(self.database.as_ref())
                .await;
        let missing_trade_event_reason_summaries = match missing_trade_event_reason_summaries_result
        {
            Ok(summaries) => summaries,
            Err(error) => return Err(error),
        };
        let launch_origin_samples_result =
            crate::query_local_launch_origin_diagnostic_list_samples(
                self.database.as_ref(),
                sample_limit,
            )
            .await;
        let launch_origin_samples = match launch_origin_samples_result {
            Ok(samples) => samples,
            Err(error) => return Err(error),
        };
        let pool_origin_samples_result = crate::query_local_pool_origin_diagnostic_list_samples(
            self.database.as_ref(),
            sample_limit,
        )
        .await;
        let pool_origin_samples = match pool_origin_samples_result {
            Ok(samples) => samples,
            Err(error) => return Err(error),
        };
        let token_metadata_gap_samples_result =
            crate::query_local_token_metadata_gap_diagnostic_list_samples(
                self.database.as_ref(),
                sample_limit,
            )
            .await;
        let token_metadata_gap_samples = match token_metadata_gap_samples_result {
            Ok(samples) => samples,
            Err(error) => return Err(error),
        };
        let non_actionable_pair_summaries_result =
            crate::query_local_non_actionable_pair_diagnostic_list_summaries(
                self.database.as_ref(),
                sample_limit,
            )
            .await;
        let non_actionable_pair_summaries = match non_actionable_pair_summaries_result {
            Ok(summaries) => summaries,
            Err(error) => return Err(error),
        };
        let missing_trade_event_samples_result =
            crate::query_local_missing_trade_event_diagnostic_list_samples(
                self.database.as_ref(),
                sample_limit,
            )
            .await;
        let missing_trade_event_samples = match missing_trade_event_samples_result {
            Ok(samples) => samples,
            Err(error) => return Err(error),
        };
        let duplicate_decoded_event_trade_samples_result =
            crate::query_local_duplicate_decoded_event_trade_diagnostic_list_samples(
                self.database.as_ref(),
                sample_limit,
            )
            .await;
        let duplicate_decoded_event_trade_samples =
            match duplicate_decoded_event_trade_samples_result {
                Ok(samples) => samples,
                Err(error) => return Err(error),
            };
        let multi_trade_signature_pair_samples_result =
            crate::query_local_multi_trade_signature_pair_diagnostic_list_samples(
                self.database.as_ref(),
                sample_limit,
            )
            .await;
        let multi_trade_signature_pair_samples = match multi_trade_signature_pair_samples_result {
            Ok(samples) => samples,
            Err(error) => return Err(error),
        };
        let pair_without_trade_samples_result =
            crate::query_local_pair_without_trade_diagnostic_list_samples(
                self.database.as_ref(),
                sample_limit,
            )
            .await;
        let pair_without_trade_samples = match pair_without_trade_samples_result {
            Ok(samples) => samples,
            Err(error) => return Err(error),
        };
        let pair_without_candle_samples_result =
            crate::query_local_pair_without_candle_diagnostic_list_samples(
                self.database.as_ref(),
                sample_limit,
            )
            .await;
        let pair_without_candle_samples = match pair_without_candle_samples_result {
            Ok(samples) => samples,
            Err(error) => return Err(error),
        };
        let blocking_issue_count = counters.actionable_missing_trade_event_count
            + counters.invalid_trade_event_count
            + counters.duplicate_decoded_event_trade_count
            + counters.duplicate_candle_bucket_count;
        let diagnostics_clean = blocking_issue_count == 0;
        let event_coverage_aggregate =
            aggregate_event_coverage_summaries(&event_coverage_summaries);
        return Ok(crate::LocalPipelineDiagnosticSummaryDto {
            transaction_count: counters.transaction_count,
            ok_transaction_count: counters.ok_transaction_count,
            failed_transaction_count: counters.failed_transaction_count,
            decoded_event_count: counters.decoded_event_count,
            decoded_trade_candidate_count: counters.decoded_trade_candidate_count,
            decoded_candle_candidate_count: counters.decoded_candle_candidate_count,
            decoded_non_trade_useful_event_count: counters.decoded_non_trade_useful_event_count,
            decoded_non_actionable_trade_event_count: counters
                .decoded_non_actionable_trade_event_count,
            decoded_unknown_event_count: counters.decoded_unknown_event_count,
            liquidity_event_count: counters.liquidity_event_count,
            pool_lifecycle_event_count: counters.pool_lifecycle_event_count,
            fee_event_count: counters.fee_event_count,
            reward_event_count: counters.reward_event_count,
            pool_admin_event_count: counters.pool_admin_event_count,
            event_coverage_listed_entry_count: event_coverage_aggregate.listed_entry_count,
            event_coverage_decoded_entry_count: event_coverage_aggregate.decoded_entry_count,
            event_coverage_observed_entry_count: event_coverage_aggregate.observed_entry_count,
            event_coverage_materialized_entry_count: event_coverage_aggregate
                .materialized_entry_count,
            event_coverage_total_observed_count: event_coverage_aggregate.total_observed_count,
            event_coverage_total_materialized_count: event_coverage_aggregate
                .total_materialized_count,
            event_coverage_trade_count: event_coverage_aggregate.trade_count,
            event_coverage_audit_only_entry_count: event_coverage_aggregate.audit_only_entry_count,
            event_coverage_missing_db_target_entry_count: event_coverage_aggregate
                .missing_db_target_entry_count,
            event_coverage_upstream_git_unverified_entry_count: event_coverage_aggregate
                .upstream_git_unverified_entry_count,
            event_coverage_upstream_git_mapped_unverified_entry_count: event_coverage_aggregate
                .upstream_git_mapped_unverified_entry_count,
            event_coverage_upstream_git_local_corpus_observed_entry_count: event_coverage_aggregate
                .upstream_git_local_corpus_observed_entry_count,
            event_coverage_upstream_git_local_corpus_materialized_entry_count:
                event_coverage_aggregate.upstream_git_local_corpus_materialized_entry_count,
            event_coverage_summaries,
            diagnostics_clean,
            blocking_issue_count,
            missing_trade_event_count: counters.missing_trade_event_count,
            decoded_trade_candidate_without_trade_event_count: counters
                .decoded_trade_candidate_without_trade_event_count,
            decoded_trade_candidate_without_trade_event_on_ok_transaction_count: counters
                .decoded_trade_candidate_without_trade_event_on_ok_transaction_count,
            decoded_trade_candidate_without_trade_event_on_failed_transaction_count: counters
                .decoded_trade_candidate_without_trade_event_on_failed_transaction_count,
            actionable_missing_trade_event_count: counters.actionable_missing_trade_event_count,
            ignored_failed_transaction_trade_candidate_count: counters
                .ignored_failed_transaction_trade_candidate_count,
            decoded_trade_candidate_without_amount_payload_count: counters
                .decoded_trade_candidate_without_amount_payload_count,
            trade_event_count: counters.trade_event_count,
            invalid_trade_event_count: counters.invalid_trade_event_count,
            pair_candle_count: counters.pair_candle_count,
            duplicate_decoded_event_trade_count: counters.duplicate_decoded_event_trade_count,
            multi_trade_signature_pair_count: counters.multi_trade_signature_pair_count,
            duplicate_candle_bucket_count: counters.duplicate_candle_bucket_count,
            token_count: counters.token_count,
            token_metadata_missing_count: counters.token_metadata_missing_count,
            tradable_token_metadata_missing_count: counters.tradable_token_metadata_missing_count,
            quote_token_metadata_missing_count: counters.quote_token_metadata_missing_count,
            pair_symbol_fallback_count: counters.pair_symbol_fallback_count,
            pair_symbol_resolved_count: counters.pair_symbol_resolved_count,
            wsol_quote_pair_count: counters.wsol_quote_pair_count,
            stable_quote_pair_count: counters.stable_quote_pair_count,
            pool_count: counters.pool_count,
            pair_count: counters.pair_count,
            pair_gap_counter_semantics: "blocking_actionable_pairs_only".to_string(),
            literal_pair_without_trade_count: counters.literal_pair_without_trade_count,
            literal_pair_without_candle_count: counters.literal_pair_without_candle_count,
            trade_materialized_pair_count: counters.trade_materialized_pair_count,
            candle_materialized_pair_count: counters.candle_materialized_pair_count,
            actionable_pair_count: counters.actionable_pair_count,
            candle_bucket_timeframe_count: counters.candle_bucket_timeframe_count,
            candles_are_bucketed: counters.candle_bucket_timeframe_count > 0,
            blocking_pair_without_trade_count: counters.blocking_pair_without_trade_count,
            blocking_pair_without_candle_count: counters.blocking_pair_without_candle_count,
            pair_without_trade_count: counters.pair_without_trade_count,
            pair_without_candle_count: counters.pair_without_candle_count,
            dex_summaries,
            raydium_surface_summaries,
            pair_summaries,
            pair_actionability_summaries,
            pair_trading_readiness_summaries,
            decoded_event_summaries,
            event_classification_summaries,
            missing_trade_event_reason_summaries,
            launch_origin_samples,
            pool_origin_samples,
            token_metadata_gap_samples,
            non_actionable_pair_count: counters.non_actionable_pair_count,
            non_actionable_pair_summaries,
            missing_trade_event_samples,
            duplicate_decoded_event_trade_samples,
            multi_trade_signature_pair_samples,
            pair_without_trade_samples,
            pair_without_candle_samples,
        });
    }
}

async fn load_event_coverage_summaries(
    database: &crate::Database,
) -> Result<std::vec::Vec<crate::DexEventCoverageSummaryDto>, crate::Error> {
    let refresh_result =
        crate::query_dex_event_coverage_entries_refresh_local_counts(database).await;
    match refresh_result {
        Ok(_) => {},
        Err(error) => return Err(error),
    }
    let summaries_result =
        crate::query_dex_event_coverage_entries_list_summary_by_decoder(database).await;
    match summaries_result {
        Ok(summaries) => return Ok(summaries),
        Err(error) => return Err(error),
    }
}

#[derive(Debug, Clone)]
struct EventCoverageDiagnosticAggregate {
    listed_entry_count: u64,
    decoded_entry_count: u64,
    observed_entry_count: u64,
    materialized_entry_count: u64,
    total_observed_count: u64,
    total_materialized_count: u64,
    trade_count: u64,
    audit_only_entry_count: u64,
    missing_db_target_entry_count: u64,
    upstream_git_unverified_entry_count: u64,
    upstream_git_mapped_unverified_entry_count: u64,
    upstream_git_local_corpus_observed_entry_count: u64,
    upstream_git_local_corpus_materialized_entry_count: u64,
}

fn aggregate_event_coverage_summaries(
    summaries: &[crate::DexEventCoverageSummaryDto],
) -> EventCoverageDiagnosticAggregate {
    let mut aggregate = EventCoverageDiagnosticAggregate {
        listed_entry_count: 0,
        decoded_entry_count: 0,
        observed_entry_count: 0,
        materialized_entry_count: 0,
        total_observed_count: 0,
        total_materialized_count: 0,
        trade_count: 0,
        audit_only_entry_count: 0,
        missing_db_target_entry_count: 0,
        upstream_git_unverified_entry_count: 0,
        upstream_git_mapped_unverified_entry_count: 0,
        upstream_git_local_corpus_observed_entry_count: 0,
        upstream_git_local_corpus_materialized_entry_count: 0,
    };
    for summary in summaries {
        aggregate.listed_entry_count =
            aggregate.listed_entry_count.saturating_add(summary.listed_entry_count);
        aggregate.decoded_entry_count =
            aggregate.decoded_entry_count.saturating_add(summary.decoded_entry_count);
        aggregate.observed_entry_count = aggregate
            .observed_entry_count
            .saturating_add(summary.observed_entry_count);
        aggregate.materialized_entry_count = aggregate
            .materialized_entry_count
            .saturating_add(summary.materialized_entry_count);
        aggregate.total_observed_count = aggregate
            .total_observed_count
            .saturating_add(summary.total_observed_count);
        aggregate.total_materialized_count = aggregate
            .total_materialized_count
            .saturating_add(summary.total_materialized_count);
        aggregate.trade_count = aggregate.trade_count.saturating_add(summary.trade_count);
        aggregate.audit_only_entry_count = aggregate
            .audit_only_entry_count
            .saturating_add(summary.audit_only_entry_count);
        aggregate.missing_db_target_entry_count = aggregate
            .missing_db_target_entry_count
            .saturating_add(summary.missing_db_target_entry_count);
        aggregate.upstream_git_unverified_entry_count = aggregate
            .upstream_git_unverified_entry_count
            .saturating_add(summary.upstream_git_unverified_entry_count);
        aggregate.upstream_git_mapped_unverified_entry_count = aggregate
            .upstream_git_mapped_unverified_entry_count
            .saturating_add(summary.upstream_git_mapped_unverified_entry_count);
        aggregate.upstream_git_local_corpus_observed_entry_count = aggregate
            .upstream_git_local_corpus_observed_entry_count
            .saturating_add(summary.upstream_git_local_corpus_observed_entry_count);
        aggregate.upstream_git_local_corpus_materialized_entry_count = aggregate
            .upstream_git_local_corpus_materialized_entry_count
            .saturating_add(summary.upstream_git_local_corpus_materialized_entry_count);
    }
    return aggregate;
}

fn build_lightweight_diagnostic_summary(
    counters: crate::LocalPipelineDiagnosticCountersDto,
    diagnostics_clean: bool,
    blocking_issue_count: i64,
    dex_summaries: std::vec::Vec<crate::LocalDexDiagnosticSummaryDto>,
    raydium_surface_summaries: std::vec::Vec<crate::LocalRaydiumSurfaceDiagnosticSummaryDto>,
    decoded_event_summaries: std::vec::Vec<crate::LocalDecodedEventDiagnosticSummaryDto>,
    event_classification_summaries: std::vec::Vec<
        crate::LocalEventClassificationDiagnosticSummaryDto,
    >,
    event_coverage_summaries: std::vec::Vec<crate::DexEventCoverageSummaryDto>,
) -> crate::LocalPipelineDiagnosticSummaryDto {
    let event_coverage_aggregate = aggregate_event_coverage_summaries(&event_coverage_summaries);
    return crate::LocalPipelineDiagnosticSummaryDto {
        transaction_count: counters.transaction_count,
        ok_transaction_count: counters.ok_transaction_count,
        failed_transaction_count: counters.failed_transaction_count,
        decoded_event_count: counters.decoded_event_count,
        decoded_trade_candidate_count: counters.decoded_trade_candidate_count,
        decoded_candle_candidate_count: counters.decoded_candle_candidate_count,
        decoded_non_trade_useful_event_count: counters.decoded_non_trade_useful_event_count,
        decoded_non_actionable_trade_event_count: counters.decoded_non_actionable_trade_event_count,
        decoded_unknown_event_count: counters.decoded_unknown_event_count,
        liquidity_event_count: counters.liquidity_event_count,
        pool_lifecycle_event_count: counters.pool_lifecycle_event_count,
        fee_event_count: counters.fee_event_count,
        reward_event_count: counters.reward_event_count,
        pool_admin_event_count: counters.pool_admin_event_count,
        event_coverage_listed_entry_count: event_coverage_aggregate.listed_entry_count,
        event_coverage_decoded_entry_count: event_coverage_aggregate.decoded_entry_count,
        event_coverage_observed_entry_count: event_coverage_aggregate.observed_entry_count,
        event_coverage_materialized_entry_count: event_coverage_aggregate.materialized_entry_count,
        event_coverage_total_observed_count: event_coverage_aggregate.total_observed_count,
        event_coverage_total_materialized_count: event_coverage_aggregate.total_materialized_count,
        event_coverage_trade_count: event_coverage_aggregate.trade_count,
        event_coverage_audit_only_entry_count: event_coverage_aggregate.audit_only_entry_count,
        event_coverage_missing_db_target_entry_count: event_coverage_aggregate
            .missing_db_target_entry_count,
        event_coverage_upstream_git_unverified_entry_count: event_coverage_aggregate
            .upstream_git_unverified_entry_count,
        event_coverage_upstream_git_mapped_unverified_entry_count: event_coverage_aggregate
            .upstream_git_mapped_unverified_entry_count,
        event_coverage_upstream_git_local_corpus_observed_entry_count: event_coverage_aggregate
            .upstream_git_local_corpus_observed_entry_count,
        event_coverage_upstream_git_local_corpus_materialized_entry_count: event_coverage_aggregate
            .upstream_git_local_corpus_materialized_entry_count,
        event_coverage_summaries,
        diagnostics_clean,
        blocking_issue_count,
        missing_trade_event_count: counters.missing_trade_event_count,
        decoded_trade_candidate_without_trade_event_count: counters
            .decoded_trade_candidate_without_trade_event_count,
        decoded_trade_candidate_without_trade_event_on_ok_transaction_count: counters
            .decoded_trade_candidate_without_trade_event_on_ok_transaction_count,
        decoded_trade_candidate_without_trade_event_on_failed_transaction_count: counters
            .decoded_trade_candidate_without_trade_event_on_failed_transaction_count,
        actionable_missing_trade_event_count: counters.actionable_missing_trade_event_count,
        ignored_failed_transaction_trade_candidate_count: counters
            .ignored_failed_transaction_trade_candidate_count,
        decoded_trade_candidate_without_amount_payload_count: counters
            .decoded_trade_candidate_without_amount_payload_count,
        trade_event_count: counters.trade_event_count,
        invalid_trade_event_count: counters.invalid_trade_event_count,
        pair_candle_count: counters.pair_candle_count,
        duplicate_decoded_event_trade_count: counters.duplicate_decoded_event_trade_count,
        multi_trade_signature_pair_count: counters.multi_trade_signature_pair_count,
        duplicate_candle_bucket_count: counters.duplicate_candle_bucket_count,
        token_count: counters.token_count,
        token_metadata_missing_count: counters.token_metadata_missing_count,
        tradable_token_metadata_missing_count: counters.tradable_token_metadata_missing_count,
        quote_token_metadata_missing_count: counters.quote_token_metadata_missing_count,
        pair_symbol_fallback_count: counters.pair_symbol_fallback_count,
        pair_symbol_resolved_count: counters.pair_symbol_resolved_count,
        wsol_quote_pair_count: counters.wsol_quote_pair_count,
        stable_quote_pair_count: counters.stable_quote_pair_count,
        pool_count: counters.pool_count,
        pair_count: counters.pair_count,
        pair_gap_counter_semantics: "blocking_actionable_pairs_only".to_string(),
        literal_pair_without_trade_count: counters.literal_pair_without_trade_count,
        literal_pair_without_candle_count: counters.literal_pair_without_candle_count,
        trade_materialized_pair_count: counters.trade_materialized_pair_count,
        candle_materialized_pair_count: counters.candle_materialized_pair_count,
        actionable_pair_count: counters.actionable_pair_count,
        candle_bucket_timeframe_count: counters.candle_bucket_timeframe_count,
        candles_are_bucketed: counters.candle_bucket_timeframe_count > 0,
        blocking_pair_without_trade_count: counters.blocking_pair_without_trade_count,
        blocking_pair_without_candle_count: counters.blocking_pair_without_candle_count,
        pair_without_trade_count: counters.pair_without_trade_count,
        pair_without_candle_count: counters.pair_without_candle_count,
        dex_summaries,
        raydium_surface_summaries,
        pair_summaries: std::vec::Vec::new(),
        pair_actionability_summaries: std::vec::Vec::new(),
        pair_trading_readiness_summaries: std::vec::Vec::new(),
        decoded_event_summaries,
        event_classification_summaries,
        missing_trade_event_reason_summaries: std::vec::Vec::new(),
        launch_origin_samples: std::vec::Vec::new(),
        pool_origin_samples: std::vec::Vec::new(),
        token_metadata_gap_samples: std::vec::Vec::new(),
        non_actionable_pair_count: counters.non_actionable_pair_count,
        non_actionable_pair_summaries: std::vec::Vec::new(),
        missing_trade_event_samples: std::vec::Vec::new(),
        duplicate_decoded_event_trade_samples: std::vec::Vec::new(),
        multi_trade_signature_pair_samples: std::vec::Vec::new(),
        pair_without_trade_samples: std::vec::Vec::new(),
        pair_without_candle_samples: std::vec::Vec::new(),
    };
}

fn build_raydium_surface_summaries(
    dex_summaries: &[crate::LocalDexDiagnosticSummaryDto],
    program_instruction_summaries: &[crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto],
) -> std::vec::Vec<crate::LocalRaydiumSurfaceDiagnosticSummaryDto> {
    let mut summaries = std::vec::Vec::new();
    for entry in crate::dex_support_matrix_entries() {
        if entry.family != "raydium" {
            continue;
        }
        let dex_summary = find_dex_summary(dex_summaries, entry.code);
        let program_summary = match entry.program_id {
            Some(program_id) => {
                find_raydium_program_summary(program_instruction_summaries, program_id)
            },
            None => None,
        };
        let instruction_count = match program_summary {
            Some(program_summary) => program_summary.instruction_count,
            None => 0,
        };
        let transaction_count = match program_summary {
            Some(program_summary) => program_summary.transaction_count,
            None => 0,
        };
        let latest_slot = match program_summary {
            Some(program_summary) => program_summary.latest_slot,
            None => None,
        };
        let latest_signature = match program_summary {
            Some(program_summary) => program_summary.latest_signature.clone(),
            None => None,
        };
        let decoded_event_count = match dex_summary {
            Some(dex_summary) => dex_summary.decoded_event_count,
            None => 0,
        };
        let trade_event_count = match dex_summary {
            Some(dex_summary) => dex_summary.trade_event_count,
            None => 0,
        };
        let pair_candle_count = match dex_summary {
            Some(dex_summary) => dex_summary.pair_candle_count,
            None => 0,
        };
        summaries.push(crate::LocalRaydiumSurfaceDiagnosticSummaryDto {
            dex_code: entry.code.to_string(),
            display_name: entry.display_name.to_string(),
            surface_role: entry.surface_role.to_string(),
            program_id: match entry.program_id {
                Some(program_id) => Some(program_id.to_string()),
                None => None,
            },
            program_id_status: entry.program_id_status.to_string(),
            status: entry.status.to_string(),
            catalog_enabled: entry.catalog_enabled,
            instruction_count,
            transaction_count,
            decoded_event_count,
            trade_event_count,
            pair_candle_count,
            latest_slot,
            latest_signature,
            observed_in_current_corpus: instruction_count > 0,
            decoded_in_current_corpus: decoded_event_count > 0,
            trade_materialized_in_current_corpus: trade_event_count > 0,
        });
    }
    summaries.sort_by(|left, right| return left.dex_code.cmp(&right.dex_code));
    return summaries;
}

fn find_dex_summary<'a>(
    dex_summaries: &'a [crate::LocalDexDiagnosticSummaryDto],
    dex_code: &str,
) -> std::option::Option<&'a crate::LocalDexDiagnosticSummaryDto> {
    for summary in dex_summaries {
        if summary.dex_code == dex_code {
            return Some(summary);
        }
    }
    return None;
}

fn find_raydium_program_summary<'a>(
    program_instruction_summaries: &'a [crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto],
    program_id: &str,
) -> std::option::Option<&'a crate::LocalRaydiumProgramInstructionDiagnosticSummaryDto> {
    for summary in program_instruction_summaries {
        if summary.program_id.as_str() == program_id {
            return Some(summary);
        }
    }
    return None;
}
