// file: kb_lib/src/dex_detection_route.rs

//! Routing from decoded DEX events to business-level detection handlers.

/// Internal route selected for one decoded DEX event.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum DexDetectionRoute {
    /// Raydium AMM v4 initialize2 pool route.
    RaydiumAmmV4Initialize2Pool,
    /// Raydium AMM v4 trade route.
    RaydiumAmmV4Trade,
    /// Raydium CPMM trade route.
    RaydiumCpmmTrade,
    /// Raydium Stable Swap trade route.
    RaydiumStableSwapTrade,
    /// Raydium Stable Swap pool lifecycle route.
    RaydiumStableSwapPool,
    /// Raydium CLMM trade route.
    RaydiumClmmTrade,
    /// Raydium Launchpad pool or bonding-curve creation route.
    RaydiumLaunchpadPool,
    /// Pump.fun create token route.
    PumpFunCreateV2Token,
    /// Pump.fun trade route.
    PumpFunTrade,
    /// PumpSwap trade route.
    PumpSwapTrade,
    /// Incomplete PumpSwap event route.
    SkipIncompletePumpSwapTrade,
    /// Meteora DBC pool route.
    MeteoraDbcPool,
    /// Meteora DLMM pool route.
    MeteoraDlmmPool,
    /// Meteora DAMM v1 pool route.
    MeteoraDammV1Pool,
    /// Meteora DAMM v2 pool route.
    MeteoraDammV2Pool,
    /// Orca Whirlpools pool route.
    OrcaWhirlpoolsPool,
    /// FluxBeam pool route.
    FluxbeamPool,
    /// DexLab pool route.
    DexlabPool,
}

/// Selects the business-level detection route for one decoded DEX event.
pub(crate) fn dex_detection_route(
    decoded_event: &crate::DexDecodedEventDto,
) -> std::option::Option<crate::dex_detection_route::DexDetectionRoute> {
    match (decoded_event.protocol_name.as_str(), decoded_event.event_kind.as_str()) {
        ("raydium_amm_v4", "raydium_amm_v4.initialize2_pool") => {
            return Some(
                crate::dex_detection_route::DexDetectionRoute::RaydiumAmmV4Initialize2Pool,
            );
        },
        ("raydium_amm_v4", "raydium_amm_v4.swap_base_in") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumAmmV4Trade);
        },
        ("raydium_amm_v4", "raydium_amm_v4.swap_base_out") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumAmmV4Trade);
        },
        ("raydium_amm_v4", "raydium_amm_v4.swap_base_in_v2") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumAmmV4Trade);
        },
        ("raydium_amm_v4", "raydium_amm_v4.swap_base_out_v2") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumAmmV4Trade);
        },
        ("raydium_cpmm", "raydium_cpmm.swap_base_input") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumCpmmTrade);
        },
        ("raydium_cpmm", "raydium_cpmm.swap_base_output") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumCpmmTrade);
        },
        ("raydium_stable_swap", "raydium_stable_swap.initialize") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapPool);
        },
        ("raydium_stable_swap", "raydium_stable_swap.pre_initialize") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapPool);
        },
        ("raydium_stable_swap", "raydium_stable_swap.swap_base_in") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapTrade);
        },
        ("raydium_stable_swap", "raydium_stable_swap.swap_base_out") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumStableSwapTrade);
        },
        ("raydium_clmm", "raydium_clmm.swap") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumClmmTrade);
        },
        ("raydium_clmm", "raydium_clmm.swap_v2") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumClmmTrade);
        },
        ("raydium_launchpad", "raydium_launchpad.initialize") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool);
        },
        ("raydium_launchpad", "raydium_launchpad.initialize_v2") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool);
        },
        ("raydium_launchpad", "raydium_launchpad.initialize_with_token_2022") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool);
        },
        ("raydium_launchpad", "raydium_launchpad.buy_exact_in") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool);
        },
        ("raydium_launchpad", "raydium_launchpad.buy_exact_out") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool);
        },
        ("raydium_launchpad", "raydium_launchpad.sell_exact_in") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool);
        },
        ("raydium_launchpad", "raydium_launchpad.sell_exact_out") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool);
        },
        ("pump_fun", "pump_fun.create_v2_token") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunCreateV2Token);
        },
        ("pump_fun", "pump_fun.buy") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
        },
        ("pump_fun", "pump_fun.sell") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
        },
        ("pump_fun", "pump_fun.buy_exact_sol_in") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
        },
        ("pump_fun", "pump_fun.buy_exact_quote_in_v2") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
        },
        ("pump_fun", "pump_fun.buy_v2") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
        },
        ("pump_fun", "pump_fun.sell_v2") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
        },
        ("pump_fun", "pump_fun.trade_event") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpFunTrade);
        },
        ("pump_swap", "pump_swap.buy") => {
            if crate::dex_detection_route::is_incomplete_pump_swap_decoded_event(decoded_event) {
                return Some(
                    crate::dex_detection_route::DexDetectionRoute::SkipIncompletePumpSwapTrade,
                );
            }
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpSwapTrade);
        },
        ("pump_swap", "pump_swap.buy_exact_quote_in") => {
            if crate::dex_detection_route::is_incomplete_pump_swap_decoded_event(decoded_event) {
                return Some(
                    crate::dex_detection_route::DexDetectionRoute::SkipIncompletePumpSwapTrade,
                );
            }
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpSwapTrade);
        },
        ("pump_swap", "pump_swap.sell") => {
            if crate::dex_detection_route::is_incomplete_pump_swap_decoded_event(decoded_event) {
                return Some(
                    crate::dex_detection_route::DexDetectionRoute::SkipIncompletePumpSwapTrade,
                );
            }
            return Some(crate::dex_detection_route::DexDetectionRoute::PumpSwapTrade);
        },
        ("meteora_dbc", "meteora_dbc.create_pool") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDbcPool);
        },
        ("meteora_dbc", "meteora_dbc.swap") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDbcPool);
        },
        ("meteora_dbc", "meteora_dbc.swap2") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDbcPool);
        },
        ("meteora_dlmm", "meteora_dlmm.create_pool") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDlmmPool);
        },
        ("meteora_dlmm", "meteora_dlmm.swap")
        | ("meteora_dlmm", "meteora_dlmm.swap2")
        | ("meteora_dlmm", "meteora_dlmm.swap_exact_out")
        | ("meteora_dlmm", "meteora_dlmm.swap_exact_out2")
        | ("meteora_dlmm", "meteora_dlmm.swap_with_price_impact")
        | ("meteora_dlmm", "meteora_dlmm.swap_with_price_impact2") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDlmmPool);
        },
        ("meteora_damm_v1", "meteora_damm_v1.create_pool") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDammV1Pool);
        },
        ("meteora_damm_v1", "meteora_damm_v1.swap") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDammV1Pool);
        },
        ("meteora_damm_v2", "meteora_damm_v2.create_pool") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDammV2Pool);
        },
        ("meteora_damm_v2", "meteora_damm_v2.swap") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::MeteoraDammV2Pool);
        },
        ("orca_whirlpools", "orca_whirlpools.create_pool") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::OrcaWhirlpoolsPool);
        },
        ("orca_whirlpools", "orca_whirlpools.swap") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::OrcaWhirlpoolsPool);
        },
        ("fluxbeam", "fluxbeam.create_pool") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::FluxbeamPool);
        },
        ("fluxbeam", "fluxbeam.swap") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::FluxbeamPool);
        },
        ("dexlab", "dexlab.create_pool") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::DexlabPool);
        },
        ("dexlab", "dexlab.swap") => {
            return Some(crate::dex_detection_route::DexDetectionRoute::DexlabPool);
        },
        _ => return None,
    }
}

/// Returns true when a route materializes a pool and therefore requires
/// pool account plus both token mints before it can run safely.
pub(crate) fn dex_detection_route_requires_full_pool_context(
    route: crate::dex_detection_route::DexDetectionRoute,
) -> bool {
    match route {
        crate::dex_detection_route::DexDetectionRoute::PumpFunCreateV2Token => return false,
        crate::dex_detection_route::DexDetectionRoute::SkipIncompletePumpSwapTrade => return false,
        _ => return true,
    }
}

/// Returns true when a decoded event has the minimum fields required for
/// deterministic pool, pair and listing materialization.
pub(crate) fn decoded_event_has_full_pool_context(
    decoded_event: &crate::DexDecodedEventDto,
) -> bool {
    if decoded_event.pool_account.is_none() {
        return false;
    }
    if decoded_event.token_a_mint.is_none() {
        return false;
    }
    if decoded_event.token_b_mint.is_none() {
        return false;
    }
    return true;
}

fn is_incomplete_pump_swap_decoded_event(decoded_event: &crate::DexDecodedEventDto) -> bool {
    return !crate::dex_detection_route::decoded_event_has_full_pool_context(decoded_event);
}

#[cfg(test)]
mod tests {
    fn make_decoded_event(
        protocol_name: &str,
        event_kind: &str,
        pool_account: std::option::Option<&str>,
        token_a_mint: std::option::Option<&str>,
        token_b_mint: std::option::Option<&str>,
    ) -> crate::DexDecodedEventDto {
        return crate::DexDecodedEventDto::new(
            1,
            Some(1),
            protocol_name.to_string(),
            "Program1111111111111111111111111111111111".to_string(),
            event_kind.to_string(),
            pool_account.map(std::string::ToString::to_string),
            None,
            token_a_mint.map(std::string::ToString::to_string),
            token_b_mint.map(std::string::ToString::to_string),
            None,
            "{}".to_string(),
        );
    }

    #[test]
    fn complete_pool_context_requires_pool_and_two_token_mints() {
        let complete = make_decoded_event(
            "meteora_damm_v2",
            "meteora_damm_v2.swap",
            Some("Pool111"),
            Some("TokenA111"),
            Some("TokenB111"),
        );
        assert!(crate::dex_detection_route::decoded_event_has_full_pool_context(&complete));

        let missing_token_a = make_decoded_event(
            "meteora_damm_v2",
            "meteora_damm_v2.swap",
            Some("Pool111"),
            None,
            Some("TokenB111"),
        );
        assert!(!crate::dex_detection_route::decoded_event_has_full_pool_context(
            &missing_token_a
        ));
    }

    #[test]
    fn meteora_swap_route_requires_full_pool_context() {
        let event = make_decoded_event(
            "meteora_damm_v2",
            "meteora_damm_v2.swap",
            Some("Pool111"),
            None,
            Some("TokenB111"),
        );
        let route_option = crate::dex_detection_route::dex_detection_route(&event);
        let route = match route_option {
            Some(route) => route,
            None => panic!("route must be selected"),
        };
        assert!(crate::dex_detection_route::dex_detection_route_requires_full_pool_context(
            route
        ));
        assert!(!crate::dex_detection_route::decoded_event_has_full_pool_context(&event));
    }

    #[test]
    fn meteora_dbc_swap2_routes_to_pool_detection() {
        let event = make_decoded_event(
            "meteora_dbc",
            "meteora_dbc.swap2",
            Some("Pool111"),
            Some("TokenA111"),
            Some("TokenB111"),
        );
        let route_option = crate::dex_detection_route::dex_detection_route(&event);
        let route = match route_option {
            Some(route) => route,
            None => panic!("route must be selected"),
        };
        assert_eq!(route, crate::dex_detection_route::DexDetectionRoute::MeteoraDbcPool);
        assert!(crate::dex_detection_route::dex_detection_route_requires_full_pool_context(
            route
        ));
    }

    #[test]
    fn raydium_launchpad_initialize_route_requires_full_pool_context() {
        let event = make_decoded_event(
            "raydium_launchpad",
            "raydium_launchpad.initialize",
            Some("Pool111"),
            Some("TokenA111"),
            Some("TokenB111"),
        );
        let route_option = crate::dex_detection_route::dex_detection_route(&event);
        let route = match route_option {
            Some(route) => route,
            None => panic!("route must be selected"),
        };
        assert_eq!(route, crate::dex_detection_route::DexDetectionRoute::RaydiumLaunchpadPool);
        assert!(crate::dex_detection_route::dex_detection_route_requires_full_pool_context(
            route
        ));
    }


    #[test]
    fn pump_swap_buy_exact_quote_in_routes_to_pool_detection_when_complete() {
        let event = make_decoded_event(
            "pump_swap",
            "pump_swap.buy_exact_quote_in",
            Some("PumpSwapPool111"),
            Some("TokenA111"),
            Some("TokenB111"),
        );
        let route_option = crate::dex_detection_route::dex_detection_route(&event);
        let route = match route_option {
            Some(route) => route,
            None => panic!("route must be selected"),
        };
        assert_eq!(route, crate::dex_detection_route::DexDetectionRoute::PumpSwapTrade);
        assert!(crate::dex_detection_route::dex_detection_route_requires_full_pool_context(
            route
        ));
    }

    #[test]
    fn pump_swap_buy_exact_quote_in_incomplete_route_is_skipped() {
        let event = make_decoded_event(
            "pump_swap",
            "pump_swap.buy_exact_quote_in",
            Some("PumpSwapPool111"),
            Some("TokenA111"),
            None,
        );
        let route_option = crate::dex_detection_route::dex_detection_route(&event);
        let route = match route_option {
            Some(route) => route,
            None => panic!("route must be selected"),
        };
        assert_eq!(
            route,
            crate::dex_detection_route::DexDetectionRoute::SkipIncompletePumpSwapTrade
        );
    }

    #[test]
    fn pump_fun_create_token_route_does_not_require_full_pool_context() {
        let event = make_decoded_event(
            "pump_fun",
            "pump_fun.create_v2_token",
            Some("BondingCurve111"),
            Some("TokenA111"),
            None,
        );
        let route_option = crate::dex_detection_route::dex_detection_route(&event);
        let route = match route_option {
            Some(route) => route,
            None => panic!("route must be selected"),
        };
        assert!(!crate::dex_detection_route::dex_detection_route_requires_full_pool_context(
            route
        ));
    }
}
