// file: kb_lib/src/db/queries/program_instruction_diagnostic.rs

//! Queries for program instruction diagnostics.

/// Lists diagnostic instruction rows for one program id.
pub async fn query_program_instruction_diagnostics_list_by_program_id(
    database: &crate::Database,
    program_id: &str,
    limit: u32,
) -> Result<std::vec::Vec<crate::ProgramInstructionDiagnosticDto>, crate::Error> {
    if limit == 0 {
        return Ok(std::vec::Vec::new());
    }
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result =
                sqlx::query_as::<sqlx::Sqlite, crate::ProgramInstructionDiagnosticEntity>(
                    r#"
SELECT
    tx.id AS transaction_id,
    tx.signature AS signature,
    tx.slot AS slot,
    ins.id AS instruction_id,
    ins.parent_instruction_id AS parent_instruction_id,
    ins.instruction_index AS instruction_index,
    ins.inner_instruction_index AS inner_instruction_index,
    ins.program_id AS program_id,
    ins.program_name AS program_name,
    ins.stack_height AS stack_height,
    ins.accounts_json AS accounts_json,
    ins.data_json AS data_json,
    ins.parsed_type AS parsed_type,
    ins.parsed_json AS parsed_json,
    tx.meta_json AS meta_json,
    tx.transaction_json AS transaction_json
FROM k_sol_chain_instructions ins
JOIN k_sol_chain_transactions tx
    ON tx.id = ins.transaction_id
WHERE ins.program_id = ?
ORDER BY
    tx.slot DESC,
    tx.id DESC,
    ins.instruction_index ASC,
    ins.inner_instruction_index ASC,
    ins.id ASC
LIMIT ?
                    "#,
                )
                .bind(program_id.to_string())
                .bind(i64::from(limit))
                .fetch_all(pool)
                .await;
            let entities = match query_result {
                Ok(entities) => entities,
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot list program instruction diagnostics for program_id '{}' on sqlite: {}",
                        program_id, error
                    )));
                },
            };
            let mut dtos = std::vec::Vec::new();
            for entity in entities {
                let dto_result = crate::ProgramInstructionDiagnosticDto::try_from(entity);
                let dto = match dto_result {
                    Ok(dto) => dto,
                    Err(error) => return Err(error),
                };
                dtos.push(dto);
            }
            return Ok(dtos);
        },
    }
}

fn build_program_instruction_discriminator_summaries(
    rows: std::vec::Vec<crate::ProgramInstructionDiscriminatorRowEntity>,
) -> Result<std::vec::Vec<crate::ProgramInstructionDiscriminatorSummaryDto>, crate::Error> {
    let mut grouped = std::collections::BTreeMap::<
        crate::db::dtos::ProgramInstructionDiscriminatorSummaryKey,
        crate::db::dtos::ProgramInstructionDiscriminatorSummaryAccumulator,
    >::new();
    for row in rows {
        let summary_row_result = build_summary_row_from_discriminator_entity(row);
        let summary_row = match summary_row_result {
            Ok(summary_row) => summary_row,
            Err(error) => return Err(error),
        };
        let existing = grouped.get_mut(&summary_row.key);
        match existing {
            Some(existing) => {
                existing.occurrence_count += 1;
                if summary_row.decoded_event_count > 0 {
                    existing.decoded_event_count += 1;
                }
                existing.transaction_signatures.insert(summary_row.latest_signature.clone());
                if summary_row.latest_instruction_id > existing.latest_instruction_id {
                    existing.latest_slot = summary_row.latest_slot;
                    existing.latest_signature = summary_row.latest_signature;
                    existing.latest_instruction_id = summary_row.latest_instruction_id;
                    existing.latest_instruction_index = summary_row.latest_instruction_index;
                    existing.latest_inner_instruction_index =
                        summary_row.latest_inner_instruction_index;
                    existing.latest_parsed_type = summary_row.latest_parsed_type;
                    existing.latest_decoded_event_kind = summary_row.latest_decoded_event_kind;
                    existing.latest_data_json_preview = summary_row.latest_data_json_preview;
                    existing.latest_accounts_json_preview =
                        summary_row.latest_accounts_json_preview;
                }
            },
            None => {
                grouped.insert(summary_row.key.clone(), summary_row);
            },
        }
    }
    let mut summaries = std::vec::Vec::new();
    for (_, accumulator) in grouped {
        let transaction_count_result = u64::try_from(accumulator.transaction_signatures.len());
        let transaction_count = match transaction_count_result {
            Ok(transaction_count) => transaction_count,
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot convert discriminator summary transaction_count to u64: {}",
                    error
                )));
            },
        };
        let undecoded_occurrence_count =
            accumulator.occurrence_count.saturating_sub(accumulator.decoded_event_count);
        summaries.push(crate::ProgramInstructionDiscriminatorSummaryDto {
            program_id: accumulator.key.program_id,
            discriminator_hex: accumulator.key.discriminator_hex,
            known_instruction_name: accumulator.known_instruction_name,
            accounts_count: accumulator.key.accounts_count,
            stack_height: accumulator.key.stack_height,
            is_inner_instruction: accumulator.key.is_inner_instruction,
            occurrence_count: accumulator.occurrence_count,
            transaction_count,
            decoded_event_count: accumulator.decoded_event_count,
            undecoded_occurrence_count,
            latest_slot: accumulator.latest_slot,
            latest_signature: accumulator.latest_signature,
            latest_instruction_id: accumulator.latest_instruction_id,
            latest_instruction_index: accumulator.latest_instruction_index,
            latest_inner_instruction_index: accumulator.latest_inner_instruction_index,
            latest_parsed_type: accumulator.latest_parsed_type,
            latest_decoded_event_kind: accumulator.latest_decoded_event_kind,
            latest_data_json_preview: accumulator.latest_data_json_preview,
            latest_accounts_json_preview: accumulator.latest_accounts_json_preview,
        });
    }
    summaries.sort_by(|left, right| {
        return right
            .undecoded_occurrence_count
            .cmp(&left.undecoded_occurrence_count)
            .then(right.transaction_count.cmp(&left.transaction_count))
            .then(right.occurrence_count.cmp(&left.occurrence_count))
            .then(right.latest_instruction_id.cmp(&left.latest_instruction_id));
    });
    return Ok(summaries);
}

fn build_summary_row_from_discriminator_entity(
    row: crate::ProgramInstructionDiscriminatorRowEntity,
) -> Result<crate::db::dtos::ProgramInstructionDiscriminatorSummaryAccumulator, crate::Error> {
    let program_id = match row.program_id.clone() {
        Some(program_id) => program_id,
        None => "unknown".to_string(),
    };
    let accounts_count = accounts_count_from_json(row.accounts_json.as_str());
    let stack_height = match row.stack_height {
        Some(stack_height) => match u32::try_from(stack_height) {
            Ok(stack_height) => Some(stack_height),
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot convert discriminator summary stack_height '{}' to u32: {}",
                    stack_height, error
                )));
            },
        },
        None => None,
    };
    let instruction_index = match u32::try_from(row.instruction_index) {
        Ok(instruction_index) => instruction_index,
        Err(error) => {
            return Err(crate::Error::Db(format!(
                "cannot convert discriminator summary instruction_index '{}' to u32: {}",
                row.instruction_index, error
            )));
        },
    };
    let inner_instruction_index = match row.inner_instruction_index {
        Some(inner_instruction_index) => match u32::try_from(inner_instruction_index) {
            Ok(inner_instruction_index) => Some(inner_instruction_index),
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot convert discriminator summary inner_instruction_index '{}' to u32: {}",
                    inner_instruction_index, error
                )));
            },
        },
        None => None,
    };
    let latest_slot = match row.slot {
        Some(slot) => match u64::try_from(slot) {
            Ok(slot) => Some(slot),
            Err(error) => {
                return Err(crate::Error::Db(format!(
                    "cannot convert discriminator summary slot '{}' to u64: {}",
                    slot, error
                )));
            },
        },
        None => None,
    };
    let decoded_data = decode_instruction_data_json(row.data_json.as_ref());
    let discriminator_hex = match decoded_data {
        Some(decoded_data) => first_8_bytes_hex(decoded_data.as_slice()),
        None => None,
    };
    let known_instruction_name = known_instruction_name_for_program_discriminator(
        program_id.as_str(),
        discriminator_hex.as_deref(),
    );
    let mut transaction_signatures = std::collections::BTreeSet::new();
    transaction_signatures.insert(row.signature.clone());
    let decoded_event_count = if row.decoded_event_id.is_some() { 1_u64 } else { 0_u64 };
    let key = crate::db::dtos::ProgramInstructionDiscriminatorSummaryKey {
        program_id,
        discriminator_hex,
        accounts_count,
        stack_height,
        is_inner_instruction: row.parent_instruction_id.is_some(),
    };
    return Ok(crate::db::dtos::ProgramInstructionDiscriminatorSummaryAccumulator {
        key,
        known_instruction_name,
        occurrence_count: 1,
        decoded_event_count,
        transaction_signatures,
        latest_slot,
        latest_signature: row.signature,
        latest_instruction_id: row.instruction_id,
        latest_instruction_index: instruction_index,
        latest_inner_instruction_index: inner_instruction_index,
        latest_parsed_type: row.parsed_type,
        latest_decoded_event_kind: row.decoded_event_kind,
        latest_data_json_preview: preview_text(row.data_json.as_deref(), 300),
        latest_accounts_json_preview: preview_text(Some(row.accounts_json.as_str()), 600),
    });
}

fn accounts_count_from_json(accounts_json: &str) -> u64 {
    let parsed_result = serde_json::from_str::<serde_json::Value>(accounts_json);
    let parsed = match parsed_result {
        Ok(parsed) => parsed,
        Err(_) => return 0,
    };
    let array = match parsed.as_array() {
        Some(array) => array,
        None => return 0,
    };
    let count_result = u64::try_from(array.len());
    match count_result {
        Ok(count) => return count,
        Err(_) => return 0,
    }
}

fn decode_instruction_data_json(
    data_json: std::option::Option<&std::string::String>,
) -> std::option::Option<std::vec::Vec<u8>> {
    let data_json = match data_json {
        Some(data_json) => data_json,
        None => return None,
    };
    let parsed_result = serde_json::from_str::<serde_json::Value>(data_json.as_str());
    let parsed = match parsed_result {
        Ok(parsed) => parsed,
        Err(_) => return None,
    };
    if let serde_json::Value::String(base58_text) = parsed {
        let decoded_result = decode_base58(base58_text.as_str());
        match decoded_result {
            Ok(decoded) => return Some(decoded),
            Err(_) => return None,
        }
    }
    return None;
}

fn decode_base58(input: &str) -> Result<std::vec::Vec<u8>, crate::Error> {
    let decoded_result = bs58::decode(input).into_vec();
    match decoded_result {
        Ok(decoded) => return Ok(decoded),
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot decode instruction data from base58: {}",
                error
            )));
        },
    }
}

fn first_8_bytes_hex(bytes: &[u8]) -> std::option::Option<std::string::String> {
    if bytes.len() < 8 {
        return None;
    }
    return Some(format!(
        "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
    ));
}

fn known_instruction_name_for_program_discriminator(
    program_id: &str,
    discriminator_hex: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
    if program_id != crate::METEORA_DLMM_PROGRAM_ID {
        return None;
    }
    let discriminator_hex = match discriminator_hex {
        Some(discriminator_hex) => discriminator_hex,
        None => return None,
    };
    let name = match discriminator_hex {
        "2d9aedd2dd0fa65c" => "initialize_lb_pair",
        "493b2478ed536cc6" => "initialize_lb_pair2",
        "2e2729876fb7c840" => "initialize_customizable_permissionless_lb_pair",
        "f349817e3313f16b" => "initialize_customizable_permissionless_lb_pair2",
        "f8c69e91e17587c8" => "swap",
        "414b3f4ceb5b5b88" => "swap2",
        "fa49652126cf4bb8" => "swap_exact_out",
        "2bd7f784893cf351" => "swap_exact_out2",
        "38ade6d0ade49ccd" => "swap_with_price_impact",
        "235613b94ed44bd3" => "initialize_bin_array",
        "dbc0ea47bebf6650" => "initialize_position",
        "b59d59438fb63448" => "add_liquidity",
        "5055d14818ceb16c" => "remove_liquidity",
        "e445a52e51cb9a1d" => "anchor_self_cpi_log_event",
        "70bf65ab1c907fbb" => "claim_fee2",
        _ => return None,
    };
    return Some(name.to_string());
}

fn preview_text(
    text: std::option::Option<&str>,
    max_len: usize,
) -> std::option::Option<std::string::String> {
    let text = match text {
        Some(text) => text,
        None => return None,
    };
    if text.len() <= max_len {
        return Some(text.to_string());
    }
    let mut preview = text.chars().take(max_len).collect::<std::string::String>();
    preview.push_str("...");
    return Some(preview);
}

/// Lists instruction discriminator summaries for one program id.
pub async fn query_program_instruction_discriminator_summaries_list_by_program_id(
    database: &crate::Database,
    program_id: &str,
    limit: u32,
) -> Result<std::vec::Vec<crate::ProgramInstructionDiscriminatorSummaryDto>, crate::Error> {
    if limit == 0 {
        return Ok(std::vec::Vec::new());
    }
    match database.connection() {
        crate::DatabaseConnection::Sqlite(pool) => {
            let query_result =
                sqlx::query_as::<sqlx::Sqlite, crate::ProgramInstructionDiscriminatorRowEntity>(
                    r#"
SELECT
    tx.id AS transaction_id,
    tx.signature AS signature,
    tx.slot AS slot,
    ins.id AS instruction_id,
    ins.parent_instruction_id AS parent_instruction_id,
    ins.instruction_index AS instruction_index,
    ins.inner_instruction_index AS inner_instruction_index,
    ins.program_id AS program_id,
    ins.stack_height AS stack_height,
    ins.accounts_json AS accounts_json,
    ins.data_json AS data_json,
    ins.parsed_type AS parsed_type,
    de.id AS decoded_event_id,
    de.event_kind AS decoded_event_kind
FROM k_sol_chain_instructions ins
JOIN k_sol_chain_transactions tx
    ON tx.id = ins.transaction_id
LEFT JOIN k_sol_dex_decoded_events de
    ON de.transaction_id = tx.id
   AND de.instruction_id = ins.id
WHERE ins.program_id = ?
ORDER BY
    tx.slot DESC,
    tx.id DESC,
    ins.instruction_index ASC,
    ins.inner_instruction_index ASC,
    ins.id ASC
LIMIT ?
                    "#,
                )
                .bind(program_id.to_string())
                .bind(i64::from(limit))
                .fetch_all(pool)
                .await;
            let rows = match query_result {
                Ok(rows) => rows,
                Err(error) => {
                    return Err(crate::Error::Db(format!(
                        "cannot list program instruction discriminator diagnostics for program_id '{}' on sqlite: {}",
                        program_id, error
                    )));
                },
            };
            return build_program_instruction_discriminator_summaries(rows);
        },
    }
}
