// file: kb_lib/src/dex_catalog.rs

//! Internal DEX catalog and persistence helpers.
//!
//! This module centralizes known DEX metadata used by detection,
//! decoding and future launch-surface integrations.
//!
//! It does not decode instructions and does not classify events.

/// Static metadata for one known DEX entry.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) struct DexCatalogItem {
    /// Stable internal DEX code.
    pub(crate) code: &'static str,
    /// Human-readable DEX name.
    pub(crate) name: &'static str,
    /// Main Solana program id.
    pub(crate) program_id: std::option::Option<&'static str>,
    /// Optional router program id.
    pub(crate) router_program_id: std::option::Option<&'static str>,
    /// Whether this DEX is currently enabled for detection.
    pub(crate) is_enabled: bool,
}

/// Returns metadata for one known DEX or launch-backed swap surface.
pub(crate) fn dex_catalog_item_by_code(
    code: &str,
) -> std::option::Option<crate::dex_catalog::DexCatalogItem> {
    let matrix_entry = match crate::dex_support_matrix_entry_by_code(code) {
        Some(matrix_entry) => matrix_entry,
        None => return None,
    };
    return Some(crate::dex_catalog::DexCatalogItem {
        code: matrix_entry.code,
        name: matrix_entry.display_name,
        program_id: matrix_entry.program_id,
        router_program_id: matrix_entry.router_program_id,
        is_enabled: matrix_entry.catalog_enabled,
    });
}

/// Ensures that one known DEX exists in storage and returns its internal id.
pub(crate) async fn ensure_known_dex(
    database: &crate::Database,
    code: &str,
) -> Result<i64, crate::Error> {
    let dex_result = crate::query_dexs_get_by_code(database, code).await;
    let dex_option = match dex_result {
        Ok(dex_option) => dex_option,
        Err(error) => return Err(error),
    };
    match dex_option {
        Some(dex) => match dex.id {
            Some(dex_id) => return Ok(dex_id),
            None => {
                return Err(crate::Error::InvalidState(format!("{} dex has no internal id", code)));
            },
        },
        None => {
            let catalog_item_option = crate::dex_catalog::dex_catalog_item_by_code(code);
            let catalog_item = match catalog_item_option {
                Some(catalog_item) => catalog_item,
                None => {
                    return Err(crate::Error::InvalidState(format!(
                        "unknown dex catalog code '{}'",
                        code
                    )));
                },
            };
            let program_id = match catalog_item.program_id {
                Some(program_id) => Some(program_id.to_string()),
                None => None,
            };
            let router_program_id = match catalog_item.router_program_id {
                Some(router_program_id) => Some(router_program_id.to_string()),
                None => None,
            };
            let dex_dto = crate::DexDto::new(
                catalog_item.code.to_string(),
                catalog_item.name.to_string(),
                program_id,
                router_program_id,
                catalog_item.is_enabled,
            );
            return crate::query_dexs_upsert(database, &dex_dto).await;
        },
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn known_active_dexes_are_available_from_catalog() {
        let codes = [
            "raydium_amm_v4",
            "raydium_cpmm",
            "raydium_clmm",
            "pump_fun",
            "pump_swap",
            "meteora_dbc",
            "meteora_dlmm",
            "meteora_damm_v1",
            "meteora_damm_v2",
            "orca_whirlpools",
            "fluxbeam",
            "dexlab",
        ];

        for code in codes {
            let item_option = crate::dex_catalog::dex_catalog_item_by_code(code);
            let item = match item_option {
                Some(item) => item,
                None => {
                    panic!("expected known dex catalog item for '{}'", code);
                },
            };
            assert_eq!(item.code, code);
            assert!(item.is_enabled);
            assert!(item.program_id.is_some());
        }
    }

    #[test]
    fn planned_launch_surfaces_are_present_but_disabled() {
        let codes = [
            "raydium_launchpad",
            "letsbonk",
            "boop_fun",
            "moonshot",
            "believe",
            "heaven",
        ];
        for code in codes {
            let item_option = crate::dex_catalog::dex_catalog_item_by_code(code);
            let item = match item_option {
                Some(item) => item,
                None => {
                    panic!("expected planned launch surface catalog item for '{}'", code);
                },
            };
            assert_eq!(item.code, code);
            assert!(!item.is_enabled);
        }
    }

    #[test]
    fn catalog_entries_are_derived_from_support_matrix() {
        let item = match crate::dex_catalog::dex_catalog_item_by_code("pump_swap") {
            Some(item) => item,
            None => panic!("expected pump_swap catalog item"),
        };
        let matrix_entry = match crate::dex_support_matrix_entry_by_code("pump_swap") {
            Some(matrix_entry) => matrix_entry,
            None => panic!("expected pump_swap matrix entry"),
        };
        assert_eq!(item.code, matrix_entry.code);
        assert_eq!(item.name, matrix_entry.display_name);
        assert_eq!(item.program_id, matrix_entry.program_id);
        assert_eq!(item.router_program_id, matrix_entry.router_program_id);
        assert_eq!(item.is_enabled, matrix_entry.catalog_enabled);
    }

    #[test]
    fn unknown_dex_code_is_not_silently_accepted() {
        let item_option = crate::dex_catalog::dex_catalog_item_by_code("zora_solana");
        assert!(item_option.is_none());
    }
}
