// file: kb_lib/src/upstream_registry_match.rs

//! Matching, filtering and summarizing helpers for the upstream Git registry.

/// Returns all static upstream registry entries as owned DTOs.
pub(crate) fn upstream_registry_all_entries() -> std::vec::Vec<crate::UpstreamRegistryEntryDto> {
    let mut entries = std::vec::Vec::new();
    for entry in crate::upstream_registry_generated::UPSTREAM_REGISTRY_ENTRIES {
        entries.push(entry.to_dto());
    }
    return entries;
}

/// Searches static upstream registry entries with optional filters.
pub(crate) fn upstream_registry_search(
    request: &crate::UpstreamRegistrySearchRequestDto,
) -> crate::UpstreamRegistrySearchResultDto {
    let total_entry_count = crate::upstream_registry_generated::UPSTREAM_REGISTRY_ENTRIES.len();
    let mut entries = std::vec::Vec::new();
    for entry in crate::upstream_registry_generated::UPSTREAM_REGISTRY_ENTRIES {
        if !matches_request(entry, request) {
            continue;
        }
        entries.push(entry.to_dto());
        if let Some(limit) = request.limit {
            if entries.len() >= limit {
                break;
            }
        }
    }
    let summary = summarize(total_entry_count, entries.as_slice());
    return crate::UpstreamRegistrySearchResultDto {
        request: request.clone(),
        summary,
        entries,
    };
}

fn matches_request(
    entry: &crate::UpstreamRegistryEntry,
    request: &crate::UpstreamRegistrySearchRequestDto,
) -> bool {
    if !matches_string_filter(entry.decoder_code, &request.decoder_code) {
        return false;
    }
    if !matches_optional_string_filter(entry.program_id, &request.program_id) {
        return false;
    }
    if !matches_string_filter(entry.program_family, &request.program_family) {
        return false;
    }
    if !matches_string_filter(entry.surface_kind, &request.surface_kind) {
        return false;
    }
    if !matches_string_filter(entry.entry_kind, &request.entry_kind) {
        return false;
    }
    if !matches_string_filter(entry.proof_status, &request.proof_status) {
        return false;
    }
    return true;
}

fn matches_string_filter(value: &str, filter: &std::option::Option<std::string::String>) -> bool {
    let filter_value = match filter {
        Some(filter_value) => filter_value.trim(),
        None => return true,
    };
    if filter_value.is_empty() {
        return true;
    }
    return value.eq_ignore_ascii_case(filter_value);
}

fn matches_optional_string_filter(
    value: std::option::Option<&str>,
    filter: &std::option::Option<std::string::String>,
) -> bool {
    let filter_value = match filter {
        Some(filter_value) => filter_value.trim(),
        None => return true,
    };
    if filter_value.is_empty() {
        return true;
    }
    let value = match value {
        Some(value) => value,
        None => return false,
    };
    return value.eq_ignore_ascii_case(filter_value);
}

fn summarize(
    total_entry_count: usize,
    entries: &[crate::UpstreamRegistryEntryDto],
) -> crate::UpstreamRegistrySummaryDto {
    let mut entries_with_program_id_count = 0_usize;
    let mut entries_with_discriminator_count = 0_usize;
    let mut program_entry_count = 0_usize;
    let mut instruction_entry_count = 0_usize;
    let mut event_entry_count = 0_usize;
    let mut account_entry_count = 0_usize;
    let mut upstream_git_unverified_count = 0_usize;
    let mut upstream_git_mapped_unverified_count = 0_usize;
    let mut upstream_git_local_corpus_observed_count = 0_usize;
    let mut upstream_git_local_corpus_materialized_count = 0_usize;
    let mut upstream_git_layout_unverified_count = 0_usize;

    for entry in entries {
        if entry.program_id.is_some() {
            entries_with_program_id_count += 1;
        }
        if entry.discriminator_hex.is_some() {
            entries_with_discriminator_count += 1;
        }
        match entry.entry_kind.as_str() {
            crate::ENTRY_KIND_PROGRAM => program_entry_count += 1,
            crate::ENTRY_KIND_INSTRUCTION => instruction_entry_count += 1,
            crate::ENTRY_KIND_EVENT => event_entry_count += 1,
            crate::ENTRY_KIND_ACCOUNT => account_entry_count += 1,
            _ => (),
        }
        match entry.proof_status.as_str() {
            crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED => upstream_git_unverified_count += 1,
            crate::PROOF_STATUS_UPSTREAM_GIT_MAPPED_UNVERIFIED => {
                upstream_git_mapped_unverified_count += 1;
            },
            crate::PROOF_STATUS_UPSTREAM_GIT_LOCAL_CORPUS_OBSERVED => {
                upstream_git_local_corpus_observed_count += 1;
            },
            crate::PROOF_STATUS_UPSTREAM_GIT_LOCAL_CORPUS_MATERIALIZED => {
                upstream_git_local_corpus_materialized_count += 1;
            },
            crate::PROOF_STATUS_UPSTREAM_GIT_LAYOUT_UNVERIFIED => {
                upstream_git_layout_unverified_count += 1;
            },
            _ => (),
        }
    }

    return crate::UpstreamRegistrySummaryDto {
        total_entry_count,
        returned_entry_count: entries.len(),
        entries_with_program_id_count,
        entries_with_discriminator_count,
        program_entry_count,
        instruction_entry_count,
        event_entry_count,
        account_entry_count,
        upstream_git_unverified_count,
        upstream_git_mapped_unverified_count,
        upstream_git_local_corpus_observed_count,
        upstream_git_local_corpus_materialized_count,
        upstream_git_layout_unverified_count,
    };
}

/// Matches raw base58 instruction data against upstream registry discriminator entries.
pub(crate) fn upstream_registry_match_instruction_data(
    program_id: &str,
    data_base58: std::option::Option<&str>,
) -> std::option::Option<crate::UpstreamRegistryEntryDto> {
    let data = decode_base58_instruction_data(data_base58);
    let data = match data {
        Some(data) => data,
        None => return None,
    };
    let mut selected_entry: std::option::Option<&crate::UpstreamRegistryEntry> = None;
    let mut selected_len = 0_usize;
    for entry in crate::upstream_registry_generated::UPSTREAM_REGISTRY_ENTRIES {
        if entry.entry_kind != crate::ENTRY_KIND_INSTRUCTION {
            continue;
        }
        let entry_program_id = match entry.program_id {
            Some(entry_program_id) => entry_program_id,
            None => continue,
        };
        if entry_program_id != program_id {
            continue;
        }
        let discriminator_hex = match entry.discriminator_hex {
            Some(discriminator_hex) => discriminator_hex,
            None => continue,
        };
        let discriminator_len = match discriminator_len_usize(entry.discriminator_len) {
            Some(discriminator_len) => discriminator_len,
            None => continue,
        };
        if !data_prefix_matches_discriminator(data.as_slice(), discriminator_len, discriminator_hex)
        {
            continue;
        }
        if discriminator_len > selected_len {
            selected_entry = Some(entry);
            selected_len = discriminator_len;
        }
    }
    let selected_entry = match selected_entry {
        Some(selected_entry) => selected_entry,
        None => return None,
    };
    return Some(selected_entry.to_dto());
}

fn discriminator_len_usize(
    discriminator_len: std::option::Option<u16>,
) -> std::option::Option<usize> {
    let discriminator_len = match discriminator_len {
        Some(discriminator_len) => discriminator_len,
        None => return None,
    };
    if discriminator_len == 0 {
        return None;
    }
    return Some(usize::from(discriminator_len));
}

fn decode_base58_instruction_data(
    data_base58: std::option::Option<&str>,
) -> std::option::Option<std::vec::Vec<u8>> {
    let data_base58 = match data_base58 {
        Some(data_base58) => data_base58.trim(),
        None => return None,
    };
    if data_base58.is_empty() {
        return None;
    }
    let decoded_result = bs58::decode(data_base58).into_vec();
    match decoded_result {
        Ok(decoded) => return Some(decoded),
        Err(_) => return None,
    }
}

fn data_prefix_matches_discriminator(
    data: &[u8],
    discriminator_len: usize,
    discriminator_hex: &str,
) -> bool {
    if data.len() < discriminator_len {
        return false;
    }
    let data_prefix_hex = bytes_prefix_to_hex(data, discriminator_len);
    return data_prefix_hex == discriminator_hex;
}

fn bytes_prefix_to_hex(data: &[u8], len: usize) -> std::string::String {
    let mut text = std::string::String::new();
    let mut index = 0_usize;
    while index < len {
        let byte = data[index];
        text.push_str(format!("{byte:02x}").as_str());
        index += 1;
    }
    return text;
}

#[cfg(test)]
mod tests {

    #[test]
    fn registry_contains_openbook_v2_idl_instruction_discriminators_without_local_verification() {
        let expected = [
            ("create_market", "67e261ebc8bcfbfe"),
            ("close_market", "589af8ba300e7bf4"),
            ("create_open_orders_account", "ccb5afde287dbc47"),
            ("close_open_orders_account", "b04a73d236b35b67"),
            ("place_order", "33c29baf6d82606a"),
            ("place_take_order", "032c47031ac7cb55"),
            ("consume_events", "dd91b1341f2f3fc9"),
            ("consume_given_events", "d1e336046dac2947"),
            ("cancel_order", "5f81edf00831df84"),
            ("cancel_order_by_client_order_id", "73b2c908afb77b77"),
            ("cancel_all_orders", "c453f3ab1164a08f"),
            ("deposit", "f223c68952e1f2b6"),
            ("settle_funds", "ee40a3604bab1021"),
            ("sweep_fees", "afe1624776422294"),
        ];
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        for (entry_name, discriminator_hex) in expected {
            let mut found = false;
            for entry in all_entries.as_slice() {
                if entry.decoder_code == "openbook_v2"
                    && entry.program_id.as_deref() == Some(crate::OPENBOOK_V2_PROGRAM_ID)
                    && entry.entry_kind == crate::ENTRY_KIND_INSTRUCTION
                    && entry.entry_name == entry_name
                    && entry.discriminator_hex.as_deref() == Some(discriminator_hex)
                    && entry.discriminator_len == Some(8)
                    && entry.proof_status == crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED
                {
                    found = true;
                    break;
                }
            }
            assert!(
                found,
                "missing OpenBook v2 discriminator entry '{}', '{}'",
                entry_name, discriminator_hex
            );
        }
    }

    #[test]
    fn openbook_v2_place_take_order_discriminator_matches_raw_data() {
        let data =
            bs58::encode([3_u8, 44_u8, 71_u8, 3_u8, 26_u8, 199_u8, 203_u8, 85_u8]).into_string();
        let matched = crate::upstream_registry_match::upstream_registry_match_instruction_data(
            crate::OPENBOOK_V2_PROGRAM_ID,
            Some(data.as_str()),
        );
        let matched = match matched {
            Some(matched) => matched,
            None => panic!("OpenBook v2 place_take_order discriminator must match"),
        };
        assert_eq!(matched.decoder_code, "openbook_v2".to_string());
        assert_eq!(matched.entry_name, "place_take_order".to_string());
        assert_eq!(matched.discriminator_hex, Some("032c47031ac7cb55".to_string()));
    }

    #[test]
    fn registry_contains_priority_family_program_seeds() {
        let expected_codes = [
            "meteora_damm_v2",
            "meteora_dbc",
            "meteora_dlmm",
            "meteora_vault",
            "raydium_amm_v4",
            "raydium_clmm",
            "raydium_cpmm",
            "raydium_launchpad",
            "raydium_liquidity_locking",
            "raydium_stable_swap",
            "orca_whirlpools",
            "fluxbeam",
            "lifinity_v2",
            "phoenix_v1",
            "openbook_v2",
            "stabble_stable_swap",
            "stabble_weighted_swap",
            "bonkswap",
            "boop_fun",
            "moonshot",
            "heaven",
            "okx_dex",
            "pancake_swap",
            "vertigo",
            "virtuals",
            "wavebreak",
            "onchain_labs_dex_v1",
            "onchain_labs_dex_v2",
            "jupiter_swap",
            "jupiter_dca",
            "jupiter_limit_order",
            "jupiter_limit_order_2",
            "jupiter_perpetuals",
            "jupiter_lend",
            "kamino_lending",
            "kamino_vault",
            "kamino_farms",
            "kamino_limit_order",
            "drift_v2",
            "marginfi_v2",
            "dflow_aggregator_v4",
            "zeta",
            "system_program",
            "token_program",
            "token_2022",
            "associated_token_account",
            "address_lookup_table",
            "memo_program",
            "stake_program",
            "mpl_token_metadata",
            "mpl_core",
            "bubblegum",
            "name_service",
            "marinade_finance",
            "solayer_restaking_program",
            "swig",
            "sharky",
            "circle_message_transmitter_v2",
            "circle_token_messenger_v2",
        ];
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        for expected_code in expected_codes {
            let mut found = false;
            for entry in all_entries.as_slice() {
                if entry.decoder_code == expected_code
                    && entry.entry_kind == crate::ENTRY_KIND_PROGRAM
                {
                    found = true;
                    break;
                }
            }
            assert!(found, "missing upstream registry code '{}'", expected_code);
        }
    }

    #[test]
    fn registry_has_no_duplicate_entry_keys() {
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        let mut seen = std::collections::BTreeSet::new();
        for entry in all_entries.as_slice() {
            let key = (
                entry.decoder_code.as_str(),
                entry.program_id.as_deref(),
                entry.entry_kind.as_str(),
                entry.entry_name.as_str(),
                entry.discriminator_hex.as_deref(),
            );
            assert!(
                seen.insert(key),
                "duplicate upstream registry entry: decoder={} program_id={:?} kind={} name={} discriminator={:?}",
                entry.decoder_code,
                entry.program_id,
                entry.entry_kind,
                entry.entry_name,
                entry.discriminator_hex
            );
        }
    }

    #[test]
    fn registry_has_no_duplicate_program_entry_for_same_decoder_and_program_id() {
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        let mut seen = std::collections::BTreeSet::new();
        for entry in all_entries.as_slice() {
            if entry.entry_kind != crate::ENTRY_KIND_PROGRAM {
                continue;
            }
            let key = (entry.decoder_code.as_str(), entry.program_id.as_deref());
            assert!(
                seen.insert(key),
                "duplicate upstream registry program entry: decoder={} program_id={:?}",
                entry.decoder_code,
                entry.program_id
            );
        }
    }

    #[test]
    fn registry_has_no_duplicate_program_id_for_program_rows() {
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        let mut seen = std::collections::BTreeMap::<&str, &str>::new();
        for entry in all_entries.as_slice() {
            if entry.entry_kind != crate::ENTRY_KIND_PROGRAM {
                continue;
            }
            let program_id = match entry.program_id.as_deref() {
                Some(program_id) => program_id,
                None => continue,
            };
            match seen.insert(program_id, entry.decoder_code.as_str()) {
                Some(previous_decoder) => panic!(
                    "duplicate upstream registry program_id {} for {} and {}",
                    program_id, previous_decoder, entry.decoder_code
                ),
                None => (),
            }
        }
    }

    #[test]
    fn registry_has_no_duplicate_program_discriminator_keys() {
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        let mut seen = std::collections::BTreeSet::new();
        for entry in all_entries.as_slice() {
            if entry.entry_kind == crate::ENTRY_KIND_PROGRAM {
                continue;
            }
            let program_id = match entry.program_id.as_deref() {
                Some(program_id) => program_id,
                None => continue,
            };
            let discriminator_hex = match entry.discriminator_hex.as_deref() {
                Some(discriminator_hex) => discriminator_hex,
                None => continue,
            };
            let key = (
                program_id,
                entry.entry_kind.as_str(),
                entry.entry_name.as_str(),
                discriminator_hex,
            );
            assert!(
                seen.insert(key),
                "duplicate upstream registry discriminator key: program_id={} kind={} name={} discriminator={}",
                program_id,
                entry.entry_kind,
                entry.entry_name,
                discriminator_hex
            );
        }
    }

    #[test]
    fn registry_does_not_claim_local_corpus_verification_in_bootstrap_tranche() {
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        for entry in all_entries.as_slice() {
            assert_eq!(entry.proof_status, crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED);
            assert_ne!(entry.proof_status, crate::PROOF_STATUS_UPSTREAM_GIT_LOCAL_CORPUS_OBSERVED);
            assert_ne!(
                entry.proof_status,
                crate::PROOF_STATUS_UPSTREAM_GIT_LOCAL_CORPUS_MATERIALIZED
            );
        }
    }

    #[test]
    fn registry_contains_all_meteora_damm_v2_instruction_discriminators_without_local_verification()
    {
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        let expected_entries = [
            ("add_liquidity", "b59d59438fb63448"),
            ("claim_partner_fee", "61ce27695e5e7e94"),
            ("claim_position_fee", "b4269a118521a2d3"),
            ("claim_protocol_fee", "a5e4853063f9ff21"),
            ("claim_reward", "955fb5f25e5a9ea2"),
            ("cpi_event", "bcd8a66c1aa68eb6"),
            ("initialize_pool", "5fb40aac54aee828"),
            ("remove_liquidity", "5055d14818ceb16c"),
            ("swap", "f8c69e91e17587c8"),
            ("swap2", "414b3f4ceb5b5b88"),
            ("update_pool_fees", "76d9cbb33c084659"),
            ("withdraw_ineligible_reward", "94ce2ac3f7316708"),
        ];

        for expected_entry in expected_entries {
            let mut found = false;
            for entry in all_entries.as_slice() {
                if entry.decoder_code == "meteora_damm_v2"
                    && entry.entry_kind == crate::ENTRY_KIND_INSTRUCTION
                    && entry.entry_name == expected_entry.0
                    && entry.discriminator_hex.as_deref() == Some(expected_entry.1)
                    && entry.discriminator_len == Some(8)
                    && entry.proof_status == crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED
                {
                    found = true;
                    break;
                }
            }
            assert!(
                found,
                "missing upstream Git discriminator entry '{}', '{}'",
                expected_entry.0, expected_entry.1
            );
        }
    }

    #[test]
    fn registry_contains_meteora_damm_v2_event_discriminators_without_local_verification() {
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        let expected_entries =
            [("evt_liquidity_change", "c5ab4e7fe0d3570d"), ("evt_swap2", "bd4233a826507599")];

        for expected_entry in expected_entries {
            let mut found = false;
            for entry in all_entries.as_slice() {
                if entry.decoder_code == "meteora_damm_v2"
                    && entry.entry_kind == crate::ENTRY_KIND_EVENT
                    && entry.entry_name == expected_entry.0
                    && entry.discriminator_hex.as_deref() == Some(expected_entry.1)
                    && entry.discriminator_len == Some(8)
                    && entry.proof_status == crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED
                {
                    found = true;
                    break;
                }
            }
            assert!(found, "missing upstream Git event entry '{}'", expected_entry.0);
        }
    }

    #[test]
    fn matches_anchor_instruction_data_by_program_id_and_discriminator() {
        let data =
            [0xb5_u8, 0x9d_u8, 0x59_u8, 0x43_u8, 0x8f_u8, 0xb6_u8, 0x34_u8, 0x48_u8, 0x01_u8];
        let data_base58 = bs58::encode(data).into_string();
        let matched = crate::upstream_registry_match::upstream_registry_match_instruction_data(
            crate::METEORA_DAMM_V2_PROGRAM_ID,
            Some(data_base58.as_str()),
        );
        let matched = match matched {
            Some(matched) => matched,
            None => panic!("missing meteora_damm_v2 add_liquidity registry match"),
        };
        assert_eq!(matched.decoder_code, "meteora_damm_v2");
        assert_eq!(matched.entry_name, "add_liquidity");
        assert_eq!(matched.discriminator_hex.as_deref(), Some("b59d59438fb63448"));
        assert_eq!(matched.proof_status, crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED);
    }

    #[test]
    fn matches_one_byte_instruction_data_by_program_id_and_discriminator() {
        let data = [0x00_u8, 0x10_u8, 0x20_u8];
        let data_base58 = bs58::encode(data).into_string();
        let matched = crate::upstream_registry_match::upstream_registry_match_instruction_data(
            crate::PHOENIX_V1_PROGRAM_ID,
            Some(data_base58.as_str()),
        );
        let matched = match matched {
            Some(matched) => matched,
            None => panic!("missing phoenix_v1 swap registry match"),
        };
        assert_eq!(matched.decoder_code, "phoenix_v1");
        assert_eq!(matched.entry_name, "swap");
        assert_eq!(matched.discriminator_hex.as_deref(), Some("00"));
        assert_eq!(matched.discriminator_len, Some(1));
    }

    #[test]
    fn registry_can_filter_by_program_id_and_family() {
        let request = crate::UpstreamRegistrySearchRequestDto {
            decoder_code: None,
            program_id: Some(crate::RAYDIUM_CPMM_PROGRAM_ID.to_string()),
            program_family: Some("raydium".to_string()),
            surface_kind: None,
            entry_kind: None,
            proof_status: None,
            limit: None,
        };
        let result = crate::upstream_registry_match::upstream_registry_search(&request);
        assert!(result.entries.len() >= 2);
        for entry in result.entries.as_slice() {
            assert_eq!(entry.decoder_code, "raydium_cpmm");
        }
    }

    #[test]
    fn registry_uses_generic_upstream_git_status_names_only() {
        let deprecated_external_repo_prefix = format!("{}{}", "car", "bon");
        let forbidden_terms = [deprecated_external_repo_prefix];
        let all_entries = crate::upstream_registry_match::upstream_registry_all_entries();
        for entry in all_entries.as_slice() {
            let payload = format!(
                "{} {} {} {} {}",
                entry.decoder_code,
                entry.program_family,
                entry.surface_kind,
                entry.proof_status,
                entry.notes
            );
            for forbidden_term in forbidden_terms.as_slice() {
                assert!(
                    !payload.to_ascii_lowercase().contains(forbidden_term.as_str()),
                    "forbidden registry term found: {}",
                    forbidden_term
                );
            }
        }
    }
}
