// file: kb_lib/src/instruction_observation_index.rs

//! Local technical index of observed Solana instructions.
//!
//! This index is not a business materialization table. It is an audit/search
//! aid used to find local corpus evidence by program, decoder, instruction
//! discriminator and instruction name.

/// Result of refreshing the instruction-observation index.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InstructionObservationIndexRefreshResult {
    /// Number of source instruction rows scanned.
    pub scanned_instruction_count: usize,
    /// Number of observation rows upserted.
    pub upserted_observation_count: usize,
}

/// Service that builds and refreshes `k_sol_instruction_observations`.
#[derive(Debug, Clone)]
pub struct InstructionObservationIndexService {
    database: std::sync::Arc<crate::Database>,
}

impl InstructionObservationIndexService {
    /// Creates a new instruction-observation index service.
    pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
        return Self { database };
    }

    /// Refreshes observations for one transaction signature.
    pub async fn refresh_signature(
        &self,
        signature: &str,
    ) -> Result<crate::InstructionObservationIndexRefreshResult, crate::Error> {
        let rows_result = self.list_source_rows_by_signature(signature).await;
        let rows = match rows_result {
            Ok(rows) => rows,
            Err(error) => return Err(error),
        };
        return self.upsert_source_rows(rows).await;
    }

    /// Refreshes observations for the same transaction window used by local replay.
    pub async fn refresh_replay_window(
        &self,
        limit: std::option::Option<i64>,
    ) -> Result<crate::InstructionObservationIndexRefreshResult, crate::Error> {
        let rows_result = self.list_replay_window_source_rows(limit).await;
        let rows = match rows_result {
            Ok(rows) => rows,
            Err(error) => return Err(error),
        };
        return self.upsert_source_rows(rows).await;
    }

    /// Refreshes observations for recently persisted instructions.
    pub async fn refresh_recent(
        &self,
        limit: u32,
    ) -> Result<crate::InstructionObservationIndexRefreshResult, crate::Error> {
        let rows_result = self.list_recent_source_rows(limit).await;
        let rows = match rows_result {
            Ok(rows) => rows,
            Err(error) => return Err(error),
        };
        return self.upsert_source_rows(rows).await;
    }

    async fn upsert_source_rows(
        &self,
        rows: std::vec::Vec<crate::InstructionObservationSourceRow>,
    ) -> Result<crate::InstructionObservationIndexRefreshResult, crate::Error> {
        let mut transaction_ids = std::vec::Vec::<i64>::new();
        for row in &rows {
            if transaction_ids.contains(&row.transaction_id) {
                continue;
            }
            transaction_ids.push(row.transaction_id);
        }
        let delete_result = crate::query_instruction_observations_delete_by_transaction_ids(
            self.database.as_ref(),
            transaction_ids.as_slice(),
        )
        .await;
        if let Err(error) = delete_result {
            return Err(error);
        }
        let mut result = crate::InstructionObservationIndexRefreshResult::default();
        for row in rows {
            result.scanned_instruction_count += 1;
            let dto_option = build_instruction_observation_dto(row);
            let dto = match dto_option {
                Some(dto) => dto,
                None => continue,
            };
            let upsert_result =
                crate::query_instruction_observations_upsert(self.database.as_ref(), &dto).await;
            match upsert_result {
                Ok(_) => result.upserted_observation_count += 1,
                Err(error) => return Err(error),
            }
        }
        return Ok(result);
    }

    async fn list_source_rows_by_signature(
        &self,
        signature: &str,
    ) -> Result<std::vec::Vec<crate::InstructionObservationSourceRow>, crate::Error> {
        return crate::query_instruction_observation_source_rows_list_by_signature(
            self.database.as_ref(),
            signature,
        )
        .await;
    }

    async fn list_replay_window_source_rows(
        &self,
        limit: std::option::Option<i64>,
    ) -> Result<std::vec::Vec<crate::InstructionObservationSourceRow>, crate::Error> {
        return crate::query_instruction_observation_source_rows_list_replay_window(
            self.database.as_ref(),
            limit,
        )
        .await;
    }

    async fn list_recent_source_rows(
        &self,
        limit: u32,
    ) -> Result<std::vec::Vec<crate::InstructionObservationSourceRow>, crate::Error> {
        return crate::query_instruction_observation_source_rows_list_recent(
            self.database.as_ref(),
            limit,
        )
        .await;
    }
}

fn build_instruction_observation_dto(
    row: crate::InstructionObservationSourceRow,
) -> std::option::Option<crate::InstructionObservationDto> {
    let program_id = match row.program_id.clone() {
        Some(program_id) => program_id,
        None => return None,
    };
    let discriminator_hex =
        discriminator_hex_from_data_json(row.data_json.as_ref(), program_id.as_str());
    let decoder_code = resolve_decoder_code(program_id.as_str());
    let instruction_name = resolve_instruction_name(
        program_id.as_str(),
        decoder_code.as_deref(),
        discriminator_hex.as_deref(),
    );
    let observation_key = format!(
        "{}|{}|{}|{}",
        row.signature,
        row.instruction_index,
        option_i64_key(row.inner_instruction_index),
        discriminator_hex.clone().unwrap_or_default()
    );
    return Some(crate::InstructionObservationDto::new(
        observation_key,
        row.transaction_id,
        row.signature,
        row.slot,
        row.block_time,
        row.err_json.is_some(),
        row.instruction_id,
        row.parent_instruction_id,
        row.instruction_index,
        row.inner_instruction_index,
        program_id,
        decoder_code,
        discriminator_hex,
        instruction_name,
        row.accounts_json,
        row.data_json,
        row.pool_account,
        row.decoded_event_kind,
        row.decoded_event_id,
    ));
}

fn resolve_decoder_code(program_id: &str) -> std::option::Option<std::string::String> {
    let entry = crate::dex_support_matrix_entry_by_program_id(program_id);
    match entry {
        Some(entry) => return Some(entry.code.to_string()),
        None => return None,
    }
}

fn resolve_instruction_name(
    program_id: &str,
    decoder_code: std::option::Option<&str>,
    discriminator_hex: std::option::Option<&str>,
) -> std::option::Option<std::string::String> {
    let discriminator_hex = match discriminator_hex {
        Some(discriminator_hex) => discriminator_hex,
        None => return None,
    };
    if program_id == crate::METEORA_DLMM_PROGRAM_ID || decoder_code == Some("meteora_dlmm") {
        let name = match discriminator_hex {
            "e445a52e51cb9a1d" => "anchor_self_cpi_log",
            "b59d59438fb63448" => "add_liquidity",
            "e4a24e1c46db7473" => "add_liquidity2",
            "0703967f94283dc8" => "add_liquidity_by_strategy",
            "03dd95da6f8d76d5" => "add_liquidity_by_strategy2",
            "2905eeaf64e106cd" => "add_liquidity_by_strategy_one_side",
            "1c8cee63e7a21595" => "add_liquidity_by_weight",
            "d13b3f5b6fc899e4" => "add_liquidity_by_weight2",
            "5e9b6797465fdca5" => "add_liquidity_one_side",
            "a1c26754ab47fa9a" => "add_liquidity_one_side_precise",
            "2133a3c975627de7" => "add_liquidity_one_side_precise2",
            "849c841f4328e861" => "cancel_limit_order",
            "a9204f8988e84689" => "claim_fee",
            "70bf65ab1c907fbb" => "claim_fee2",
            "955fb5f25e5a9ea2" => "claim_reward",
            "be037f77b2579db7" => "claim_reward2",
            "44ae5850b5cc13e0" => "close_bin_array",
            "b8d5581fb3658224" => "close_claim_fee_operator_account",
            "397c249b7ef95dab" => "close_limit_order_if_empty",
            "ab09d54a7817031d" => "close_operator_account",
            "7b86510031446262" => "close_position",
            "ae5a2373ba2893e2" => "close_position2",
            "3b7cd4765b986e9d" => "close_position_if_empty",
            "04949164861ab53d" => "close_preset_parameter",
            "27195f6b7411731c" => "close_preset_parameter2",
            "6c92566eb3fe0a68" => "close_token_badge",
            "dd40f695f099e5a3" => "create_operator_account",
            "c2db882019606925" => "decrease_position_length",
            "b46945505f32496c" => "for_idl_type_generation_do_not_call",
            "bc32f9a55d97263f" => "fund_reward",
            "9248aee028fd54ae" => "go_to_a_bin",
            "be3d7d57674f9ead" => "increase_oracle_length",
            "505375d3420d2195" => "increase_position_length",
            "ffd2cc477389e171" => "increase_position_length2",
            "235613b94ed44bd3" => "initialize_bin_array",
            "2f9de2b40cf02147" => "initialize_bin_array_bitmap_extension",
            "2e2729876fb7c840" => "initialize_customizable_permissionless_lb_pair",
            "f349817e3313f16b" => "initialize_customizable_permissionless_lb_pair2",
            "2d9aedd2dd0fa65c" => "initialize_lb_pair",
            "493b2478ed536cc6" => "initialize_lb_pair2",
            "6c66d555fb033515" => "initialize_permission_lb_pair",
            "dbc0ea47bebf6650" => "initialize_position",
            "8f13f291d50f6873" => "initialize_position2",
            "fbbdbef475fe2394" => "initialize_position_by_operator",
            "2e527d92558de499" => "initialize_position_pda",
            "42bc47d3626d0eba" => "initialize_preset_parameter",
            "75c73e67068e1fcb" => "initialize_preset_parameter_v2",
            "5f87c0c4f281e644" => "initialize_reward",
            "fd4dcd5f1be059df" => "initialize_token_badge",
            "6cb021ba92e501c5" => "place_limit_order",
            "5c04b0c177b95309" => "rebalance_liquidity",
            "0a333d2370691855" => "remove_all_liquidity",
            "5055d14818ceb16c" => "remove_liquidity",
            "e6d7527ff165e392" => "remove_liquidity2",
            "1a526698f04a691a" => "remove_liquidity_by_range",
            "cc02c391359191cd" => "remove_liquidity_by_range2",
            "5bf90fa51a81fe7d" => "set_activation_point",
            "43f8e7899a95d9ae" => "set_pair_status",
            "4e3b98d346b72ed0" => "set_pair_status_permissionless",
            "543acb8ba351beba" => "set_permissionless_operation_bits",
            "a53dc9f4829f1664" => "set_pre_activation_duration",
            "398b2f7bd850df0a" => "set_pre_activation_swap_address",
            "f8c69e91e17587c8" => "swap",
            "414b3f4ceb5b5b88" => "swap2",
            "fa49652126cf4bb8" => "swap_exact_out",
            "2bd7f784893cf351" => "swap_exact_out2",
            "38ade6d0ade49ccd" => "swap_with_price_impact",
            "4a62c0d6b1334b33" => "swap_with_price_impact2",
            "4ba8dfa110c3032f" => "update_base_fee_parameters",
            "5ca12ef6ffbd1616" => "update_dynamic_fee_parameters",
            "208eb89a6741b858" => "update_fees_and_reward2",
            "9ae6fa0decd14bdf" => "update_fees_and_rewards",
            "cab8678fb4bf74d9" => "update_position_operator",
            "8aaec4a9d5ebfe6b" => "update_reward_duration",
            "d31c3020d7a02317" => "update_reward_funder",
            "94ce2ac3f7316708" => "withdraw_ineligible_reward",
            "9ec99ebd215da267" => "withdraw_protocol_fee",
            "d59bbb2238b65bf0" => "zap_protocol_fee",
            _ => "instruction_audit",
        };
        return Some(format!("meteora_dlmm.{}", name));
    }
    if program_id == crate::METEORA_DBC_PROGRAM_ID || decoder_code == Some("meteora_dbc") {
        let name = match discriminator_hex {
            "e445a52e51cb9a1d" => "meteora_dbc.anchor_self_cpi_log",
            "e992d18ecf6840bc" => "meteora_dbc.create_pool_legacy",
            "5fb40aac54aee828" => "meteora_dbc.initialize_pool_legacy",
            "a677d1b6d66d3ab5" => "meteora_dbc.launch_pool_legacy",
            "52dcfabd03556b2d" => "meteora_dbc.claim_creator_trading_fee",
            "faee1a048b0a65f8" => "meteora_dbc.claim_partner_pool_creation_fee",
            "a5e4853063f9ff21" => "meteora_dbc.claim_protocol_fee",
            "72cd53bcf0991936" => "meteora_dbc.claim_protocol_pool_creation_fee",
            "08ec5931987db151" => "meteora_dbc.claim_trading_fee",
            "082957235030791a" => "meteora_dbc.close_claim_protocol_fee_operator",
            "ab09d54a7817031d" => "meteora_dbc.close_operator_account",
            "c9cff3724b6f2fbd" => "meteora_dbc.create_config",
            "a75a899a4b2f1154" => "meteora_dbc.create_locker",
            "dd40f695f099e5a3" => "meteora_dbc.create_operator_account",
            "c0a8eabfbce2e3ff" => "meteora_dbc.create_partner_metadata",
            "2d61bb67fe6d7c86" => "meteora_dbc.create_virtual_pool_metadata",
            "a50389071c864c50" => "meteora_dbc.creator_withdraw_surplus",
            "8c55d7b06636684f" => "meteora_dbc.initialize_virtual_pool_with_spl_token",
            "a976334e916edc9b" => "meteora_dbc.initialize_virtual_pool_with_token2022",
            "1b013016b43f76d9" => "meteora_dbc.migrate_meteora_damm",
            "8b85021e5b917f9a" => "meteora_dbc.migrate_meteora_damm_claim_lp_token",
            "b137ee9dfb58a52a" => "meteora_dbc.migrate_meteora_damm_lock_lp_token",
            "9ca9e66735e45040" => "meteora_dbc.migration_damm_v2",
            "6dbd1324c3b7de52" => "meteora_dbc.migration_damm_v2_create_metadata",
            "2f5e7e73dde2c285" => "meteora_dbc.migration_meteora_damm_create_metadata",
            "a8ad4864c962265c" => "meteora_dbc.partner_withdraw_surplus",
            "3688e18aacb6d6a7" => "meteora_dbc.protocol_withdraw_surplus",
            "f8c69e91e17587c8" => "meteora_dbc.swap",
            "414b3f4ceb5b5b88" => "meteora_dbc.swap2",
            "1407a9213a93a621" => "meteora_dbc.transfer_pool_creator",
            "14c6caedebf3b742" => "meteora_dbc.withdraw_leftover",
            "ed8e2d178106dea2" => "meteora_dbc.withdraw_migration_fee",
            "d59bbb2238b65bf0" => "meteora_dbc.zap_protocol_fee",
            _ => return None,
        };
        return Some(name.to_string());
    }
    if program_id == crate::RAYDIUM_AMM_V4_PROGRAM_ID || decoder_code == Some("raydium_amm_v4") {
        let name = match discriminator_hex {
            "00" => "raydium_amm_v4.initialize",
            "01" => "raydium_amm_v4.initialize2_pool",
            "02" => "raydium_amm_v4.monitor_step",
            "03" => "raydium_amm_v4.deposit",
            "04" => "raydium_amm_v4.withdraw",
            "05" => "raydium_amm_v4.migrate_to_open_book",
            "06" => "raydium_amm_v4.set_params",
            "07" => "raydium_amm_v4.withdraw_pnl",
            "08" => "raydium_amm_v4.withdraw_srm",
            "09" => "raydium_amm_v4.swap_base_in",
            "0a" => "raydium_amm_v4.pre_initialize",
            "0b" => "raydium_amm_v4.swap_base_out",
            "0c" => "raydium_amm_v4.simulate_info",
            "0d" => "raydium_amm_v4.admin_cancel_orders",
            "0e" => "raydium_amm_v4.create_config_account",
            "0f" => "raydium_amm_v4.update_config_account",
            "10" => "raydium_amm_v4.swap_base_in_v2",
            "11" => "raydium_amm_v4.swap_base_out_v2",
            _ => return None,
        };
        return Some(name.to_string());
    }
    if program_id == crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID
        || decoder_code == Some("raydium_stable_swap")
    {
        let name = match discriminator_hex {
            "00" => "raydium_stable_swap.initialize",
            "01" => "raydium_stable_swap.init_model_data",
            "02" => "raydium_stable_swap.update_model_data",
            "03" => "raydium_stable_swap.deposit",
            "04" => "raydium_stable_swap.withdraw",
            "05" => "raydium_stable_swap.monitor_step",
            "06" => "raydium_stable_swap.set_params",
            "07" => "raydium_stable_swap.withdraw_pnl",
            "08" => "raydium_stable_swap.withdraw_srm",
            "09" => "raydium_stable_swap.swap_base_in",
            "0a" => "raydium_stable_swap.pre_initialize",
            "0b" => "raydium_stable_swap.swap_base_out",
            "0c" => "raydium_stable_swap.simulate_info",
            "0d" => "raydium_stable_swap.admin_cancel_orders",
            "40c6cde8260871e2" => "raydium_stable_swap.swap_event",
            _ => return None,
        };
        return Some(name.to_string());
    }
    if program_id == crate::RAYDIUM_CPMM_PROGRAM_ID || decoder_code == Some("raydium_cpmm") {
        let name = match discriminator_hex {
            "9c5420764587467b" => "raydium_cpmm.close_permission_pda",
            "1416567bc61cdb84" => "raydium_cpmm.collect_creator_fee",
            "a78a4e95dfc2067e" => "raydium_cpmm.collect_fund_fee",
            "8888fcddc2427e59" => "raydium_cpmm.collect_protocol_fee",
            "8934edd4d7756c68" => "raydium_cpmm.create_amm_config",
            "878802d889a9b5ca" => "raydium_cpmm.create_permission_pda",
            "f223c68952e1f2b6" => "raydium_cpmm.deposit",
            "afaf6d1f0d989bed" => "raydium_cpmm.initialize",
            "3f37fe4131b25979" => "raydium_cpmm.initialize_with_permission",
            "8fbe5adac41e33de" => "raydium_cpmm.swap_base_input",
            "37d96256a34ab4ad" => "raydium_cpmm.swap_base_output",
            "313cae889a1c74c8" => "raydium_cpmm.update_amm_config",
            "82576c062ee0757b" => "raydium_cpmm.update_pool_status",
            "b712469c946da122" => "raydium_cpmm.withdraw",
            _ => return None,
        };
        return Some(name.to_string());
    }
    if program_id == crate::RAYDIUM_CLMM_PROGRAM_ID || decoder_code == Some("raydium_clmm") {
        let name = match discriminator_hex {
            "4c7c800fd55725fa" => "raydium_clmm.close_limit_order",
            "9d20dab7471d1293" => "raydium_clmm.open_limit_order",
            "b19059ecfaba7d63" => "raydium_clmm.increase_limit_order",
            "759d3c674231a300" => "raydium_clmm.decrease_limit_order",
            "7b86510031446262" => "raydium_clmm.close_position",
            "c975989055556cb2" => "raydium_clmm.close_protocol_position",
            "a78a4e95dfc2067e" => "raydium_clmm.collect_fund_fee",
            "8888fcddc2427e59" => "raydium_clmm.collect_protocol_fee",
            "12eda6c52210d590" => "raydium_clmm.collect_remaining_rewards",
            "8934edd4d7756c68" => "raydium_clmm.create_amm_config",
            "2b44d4a7592fa401" => "raydium_clmm.create_customizable_pool",
            "bd0eb5785576e33e" => "raydium_clmm.create_dynamic_fee_config",
            "3f5794216d230868" => "raydium_clmm.create_operation_account",
            "e992d18ecf6840bc" => "raydium_clmm.create_pool",
            "11fb415c88f20ea9" => "raydium_clmm.create_support_mint_associated",
            "a026d06f685b2c01" => "raydium_clmm.decrease_liquidity",
            "3a7fbc3e4f52c460" => "raydium_clmm.decrease_liquidity_v2",
            "2e9cf3760dcdfbb2" => "raydium_clmm.increase_liquidity",
            "851d59df45eeb00a" => "raydium_clmm.increase_liquidity_v2",
            "5f87c0c4f281e644" => "raydium_clmm.initialize_reward",
            "87802f4d0f98f031" => "raydium_clmm.open_position",
            "4db84ad67056f1c7" => "raydium_clmm.open_position_v2",
            "4dffae527d1dc92e" => "raydium_clmm.open_position_with_token22_nft",
            "7034a74b20c9d389" => "raydium_clmm.set_reward_params",
            "cd4e74215c691a60" => "raydium_clmm.settle_limit_order",
            "f8c69e91e17587c8" => "raydium_clmm.swap",
            "457d73daf5baf2c4" => "raydium_clmm.swap_router_base_in",
            "2b04ed0b1ac91e62" => "raydium_clmm.swap_v2",
            "07160c53f22b3079" => "raydium_clmm.transfer_reward_owner",
            "313cae889a1c74c8" => "raydium_clmm.update_amm_config",
            "7f467728bce33d07" => "raydium_clmm.update_operation_account",
            "82576c062ee0757b" => "raydium_clmm.update_pool_status",
            "a3ace0340b9a6adf" => "raydium_clmm.update_reward_infos",
            _ => return None,
        };
        return Some(name.to_string());
    }
    if program_id == crate::RAYDIUM_LAUNCHPAD_PROGRAM_ID
        || decoder_code == Some("raydium_launchpad")
    {
        if discriminator_hex == "e445a52e51cb9a1d" {
            return Some("raydium_launchpad.anchor_self_cpi_log".to_string());
        }
        let layout = crate::dex::raydium_launchpad::account_layout(discriminator_hex, usize::MAX);
        let layout = match layout {
            Some(layout) => layout,
            None => return None,
        };
        return Some(format!("raydium_launchpad.{}", layout.instruction_name));
    }
    if program_id == crate::PUMP_FEES_PROGRAM_ID || decoder_code == Some("pump_fees") {
        let name = match discriminator_hex {
            "e445a52e51cb9a1d" => "anchor_self_cpi_log",
            "e115fb85a11ec7e2" => "claim_social_fee_pda",
            "114df0863abc3595" => "claim_social_fee_pda_v2",
            "dc0abda7a9111945" => "crank_donation_fee_pda",
            "f48b10580eff7a1a" => "create_donation_fee_pda",
            "c34e564c6f34fbd5" => "create_fee_sharing_config",
            "90e03bd34ef8cadc" => "create_social_fee_pda",
            "44b3f45aad3811d9" => "extend_fee_config",
            "e7257e55cf5b3f34" => "get_fees",
            "fa81eca0e3246786" => "initialize_buyback",
            "3ea214857941911b" => "initialize_fee_config",
            "23d78254e9387ca7" => "initialize_fee_program_global",
            "0a02b65f107f81ba" => "reset_fee_sharing_config",
            "a9f511d15e5bf880" => "reset_fee_sharing_config_v2",
            "12e99e27b9cf3a68" => "revoke_fee_sharing_authority",
            "85fa25156ea31a79" => "set_authority",
            "b9d39faed4315804" => "set_claim_rate_limit",
            "c2d9702372de33be" => "set_disable_flags",
            "9336b89a88edb999" => "set_social_claim_authority",
            "8a21cc26cfa19fe2" => "sweep_buyback",
            "ca0a4bc8a422d260" => "transfer_fee_sharing_authority",
            "a1b028d53cb8b3e4" => "update_admin",
            "426271ca7925db6b" => "update_buyback_authority",
            "ba5f87beffc789aa" => "update_buyback_claim_rate_limit",
            "68b867f258976b14" => "update_fee_config",
            "bd0d8863bba4ed23" => "update_fee_shares",
            "6ffb31064e4e6a12" => "update_fee_shares_v2",
            "6ba964b3869b92dd" => "update_stable_fee_config",
            "e317960c4d565e04" => "upsert_fee_tiers",
            "b5a0a2fc4a4ce0dd" => "upsert_stable_fee_tiers",
            _ => return None,
        };
        return Some(format!("pump_fees.{}", name));
    }
    if program_id == crate::PUMP_FUN_PROGRAM_ID || decoder_code == Some("pump_fun") {
        let name = match discriminator_hex {
            "e445a52e51cb9a1d" => "anchor_self_cpi_log",
            "6f79153828185ed1" => "add_quote_mint",
            "4519ab8e39ef0d04" => "admin_set_creator",
            "08d960e79068c005" => "admin_set_idl_authority",
            "d10b7357d5177ccc" => "admin_update_token_incentives",
            "66063d1201daebea" => "buy",
            "c2ab1c46684d5b2f" => "buy_exact_quote_in_v2",
            "38fc74089edfcd5f" => "buy_exact_sol_in",
            "b817ee6167c5d33d" => "buy_v2",
            "253a237ebe35e4c5" => "claim_cashback",
            "7af3cc415e741d37" => "claim_cashback_v2",
            "1004471ccc01281b" => "claim_token_incentives",
            "f945a4da9667548a" => "close_user_volume_accumulator",
            "1416567bc61cdb84" => "collect_creator_fee",
            "cf118af204221338" => "collect_creator_fee_v2",
            "181ec828051c0777" => "create",
            "d6904cec5f8b31b4" => "create_v2",
            "a572670079cef751" => "distribute_creator_fees",
            "ffcb134ff444089f" => "distribute_creator_fees_v2",
            "ea66c2cb96483ee5" => "extend_account",
            "75e17fca865f4423" => "get_minimum_distributable_fee",
            "5e06ca73ff60e8b7" => "init_user_volume_accumulator",
            "afaf6d1f0d989bed" => "initialize",
            "9beae792ec9ea21e" => "migrate",
            "577c34bf3426d6e8" => "migrate_bonding_curve_creator",
            "bbcb121fceedfe29" => "migrate_v2",
            "b141df2658d19e9b" => "remove_quote_mint",
            "33e685a4017f83ad" => "sell",
            "5df6823ce7e940b2" => "sell_v2",
            "fe94ff70cf8eaaa5" => "set_creator",
            "3da9bcbf99952a61" => "set_mayhem_virtual_params",
            "8a60aed93055c5f6" => "set_metaplex_creator",
            "1beab2349302bb8d" => "set_params",
            "6faca2e87259d58e" => "set_reserved_fee_recipients",
            "6587bf6809581460" => "set_virtual_quote_reserves",
            "561fc057a3574fee" => "sync_user_volume_accumulator",
            "7367e0ffbd5956c3" => "toggle_cashback_enabled",
            "1cffe6f0ac6bcbab" => "toggle_create_v2",
            "01096fd0641fffa3" => "toggle_mayhem_mode",
            "fbe0ab92a01a71e9" => "update_buyback_config",
            "e3b54ac4d01561d5" => "update_global_authority",
            _ => return None,
        };
        return Some(name.to_string());
    }
    if program_id == crate::PUMP_SWAP_PROGRAM_ID || decoder_code == Some("pump_swap") {
        let name = match discriminator_hex {
            "e445a52e51cb9a1d" => "anchor_self_cpi_log",
            "f228759149606968" => "admin_set_coin_creator",
            "d10b7357d5177ccc" => "admin_update_token_incentives",
            "66063d1201daebea" => "buy",
            "c62e1552b4d9e870" => "buy_exact_quote_in",
            "253a237ebe35e4c5" => "claim_cashback",
            "1004471ccc01281b" => "claim_token_incentives",
            "f945a4da9667548a" => "close_user_volume_accumulator",
            "a039592ab58b2b42" => "collect_coin_creator_fee",
            "c9cff3724b6f2fbd" => "create_config",
            "e992d18ecf6840bc" => "create_pool",
            "f223c68952e1f2b6" => "deposit",
            "b9adbb5ad80feee9" => "disable",
            "ea66c2cb96483ee5" => "extend_account",
            "5e06ca73ff60e8b7" => "init_user_volume_accumulator",
            "d0089f044aaf103a" => "migrate_pool_coin_creator",
            "33e685a4017f83ad" => "sell",
            "d295802dbc3a4eaf" => "set_coin_creator",
            "6faca2e87259d58e" => "set_reserved_fee_recipients",
            "561fc057a3574fee" => "sync_user_volume_accumulator",
            "7367e0ffbd5956c3" => "toggle_cashback_enabled",
            "01096fd0641fffa3" => "toggle_mayhem_mode",
            "8b348655e4e56cf1" => "transfer_creator_fees_to_pump",
            "01214eb921432c5c" => "transfer_creator_fees_to_pump_v2",
            "a1b028d53cb8b3e4" => "update_admin",
            "fbe0ab92a01a71e9" => "update_buyback_config",
            "68b867f258976b14" => "update_fee_config",
            "b712469c946da122" => "withdraw",
            "cfbdb247a77a44b4" => "set_reserved_fee_recipient",
            "2ddc5d181961ac68" => "admin_set_coin_creator_event",
            "93fa6c78f71d43de" => "admin_update_token_incentives_event",
            "67f4521f2cf57777" => "buy_event",
            "e2d6f62107f293e5" => "claim_cashback_event",
            "4facf631cd5bcee8" => "claim_token_incentives_event",
            "929fbdac925838f4" => "close_user_volume_accumulator_event",
            "e8f5c2eeeada3a59" => "collect_coin_creator_fee_event",
            "6b34598137e25116" => "create_config_event",
            "b1310cd2a076a774" => "create_pool_event",
            "78f83d531f8e6b90" => "deposit_event",
            "6bfdc14ce4ca1b68" => "disable_event",
            "6161d7905d92167c" => "extend_account_event",
            "86240d48e86582d8" => "init_user_volume_accumulator_event",
            "aadd52c793a5f72e" => "migrate_pool_coin_creator_event",
            "2bbcfa12dd4bbb5f" => "reserved_fee_recipients_event",
            "3e2f370aa503dc2a" => "sell_event",
            "f2e7eb664163bdd3" => "set_bonding_curve_coin_creator_event",
            "966bc77b7ccf66e4" => "set_metaplex_coin_creator_event",
            "c57aa77c74515bff" => "sync_user_volume_accumulator_event",
            "e198ab57f63f42ea" => "update_admin_event",
            "5a1741233ef4bcd0" => "update_fee_config_event",
            "1609851aa02c47c0" => "withdraw_event",
            _ => return None,
        };
        return Some(name.to_string());
    }
    return None;
}

fn discriminator_hex_from_data_json(
    data_json: std::option::Option<&std::string::String>,
    program_id: &str,
) -> std::option::Option<std::string::String> {
    let decoded = match decode_data_json_as_bytes(data_json) {
        Some(decoded) => decoded,
        None => return None,
    };
    let discriminator_len = if program_id == crate::RAYDIUM_AMM_V4_PROGRAM_ID
        || program_id == crate::RAYDIUM_STABLE_SWAP_AMM_PROGRAM_ID
    {
        1_usize
    } else {
        8_usize
    };
    if decoded.len() < discriminator_len {
        return None;
    }
    return Some(bytes_to_hex(&decoded[0..discriminator_len]));
}

fn decode_data_json_as_bytes(
    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,
    };
    match parsed {
        serde_json::Value::String(base58_text) => {
            let decoded_result = bs58::decode(base58_text.as_str()).into_vec();
            match decoded_result {
                Ok(decoded) => return Some(decoded),
                Err(_) => return None,
            }
        },
        serde_json::Value::Array(values) => {
            let first = match values.first() {
                Some(first) => first,
                None => return None,
            };
            let base58_text = match first.as_str() {
                Some(base58_text) => base58_text,
                None => return None,
            };
            let decoded_result = bs58::decode(base58_text).into_vec();
            match decoded_result {
                Ok(decoded) => return Some(decoded),
                Err(_) => return None,
            }
        },
        _ => return None,
    }
}

fn bytes_to_hex(bytes: &[u8]) -> std::string::String {
    let mut text = std::string::String::new();
    for byte in bytes {
        text.push_str(format!("{:02x}", byte).as_str());
    }
    return text;
}

fn option_i64_key(value: std::option::Option<i64>) -> std::string::String {
    match value {
        Some(value) => return value.to_string(),
        None => return "-".to_string(),
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn resolves_meteora_dlmm_instruction_observation_names() {
        let anchor_name = super::resolve_instruction_name(
            crate::METEORA_DLMM_PROGRAM_ID,
            Some("meteora_dlmm"),
            Some("e445a52e51cb9a1d"),
        );
        assert_eq!(anchor_name.as_deref(), Some("meteora_dlmm.anchor_self_cpi_log"));
        let swap_exact_out2_name = super::resolve_instruction_name(
            crate::METEORA_DLMM_PROGRAM_ID,
            Some("meteora_dlmm"),
            Some("2bd7f784893cf351"),
        );
        assert_eq!(swap_exact_out2_name.as_deref(), Some("meteora_dlmm.swap_exact_out2"));
        let close_position_name = super::resolve_instruction_name(
            crate::METEORA_DLMM_PROGRAM_ID,
            Some("meteora_dlmm"),
            Some("7b86510031446262"),
        );
        assert_eq!(close_position_name.as_deref(), Some("meteora_dlmm.close_position"));
        let preset_v2_name = super::resolve_instruction_name(
            crate::METEORA_DLMM_PROGRAM_ID,
            Some("meteora_dlmm"),
            Some("75c73e67068e1fcb"),
        );
        assert_eq!(
            preset_v2_name.as_deref(),
            Some("meteora_dlmm.initialize_preset_parameter_v2")
        );
        let unknown_name = super::resolve_instruction_name(
            crate::METEORA_DLMM_PROGRAM_ID,
            Some("meteora_dlmm"),
            Some("ffffffffffffffff"),
        );
        assert_eq!(unknown_name.as_deref(), Some("meteora_dlmm.instruction_audit"));
    }

    #[test]
    fn resolves_meteora_dbc_instruction_observation_names() {
        let anchor_name = super::resolve_instruction_name(
            crate::METEORA_DBC_PROGRAM_ID,
            Some("meteora_dbc"),
            Some("e445a52e51cb9a1d"),
        );
        assert_eq!(anchor_name.as_deref(), Some("meteora_dbc.anchor_self_cpi_log"));
        let swap2_name = super::resolve_instruction_name(
            crate::METEORA_DBC_PROGRAM_ID,
            Some("meteora_dbc"),
            Some("414b3f4ceb5b5b88"),
        );
        assert_eq!(swap2_name.as_deref(), Some("meteora_dbc.swap2"));
        let create_name = super::resolve_instruction_name(
            crate::METEORA_DBC_PROGRAM_ID,
            Some("meteora_dbc"),
            Some("8c55d7b06636684f"),
        );
        assert_eq!(
            create_name.as_deref(),
            Some("meteora_dbc.initialize_virtual_pool_with_spl_token")
        );
    }
}
