//! CEL field mappings for stream filter expressions
//!
//! This module provides field name mappings used to convert human-readable
//! field names to their short form for server-side CEL filter expressions.

use std::collections::HashMap;

use once_cell::sync::Lazy;

/// Field mapping type: maps long field names to short field names
pub type FieldMapping = HashMap<&'static str, &'static str>;

/// Method field mappings type: maps subscription method names to their field mappings
pub type MethodFieldMappings = HashMap<&'static str, FieldMapping>;

/// CEL field mappings organized by subscription method
pub static CEL_FIELD_MAPPINGS: Lazy<MethodFieldMappings> = Lazy::new(|| {
    let mut mappings = HashMap::new();

    // Wallet balance subscription fields
    mappings.insert(
        "subscribe_wallet_balance",
        HashMap::from([
            ("walletAddress", "a"),
            ("tokenAddress", "ta"),
            ("tokenPriceInUsd", "tpiu"),
            ("balance", "b"),
            ("timestamp", "t"),
        ]),
    );

    // Token candle subscription fields
    mappings.insert(
        "subscribe_token_candles",
        HashMap::from([
            ("open", "o"),
            ("close", "c"),
            ("high", "h"),
            ("low", "l"),
            ("volume", "v"),
            ("resolution", "r"),
            ("time", "t"),
            ("number", "n"),
        ]),
    );

    // Token stats subscription fields
    mappings.insert(
        "subscribe_token_stats",
        HashMap::from([
            ("address", "a"),
            ("timestamp", "t"),
            ("buys1m", "b1m"),
            ("sells1m", "s1m"),
            ("buyers1m", "be1m"),
            ("sellers1m", "se1m"),
            ("buyVolumeInUsd1m", "bviu1m"),
            ("sellVolumeInUsd1m", "sviu1m"),
            ("price1m", "p1m"),
            ("openInUsd1m", "oiu1m"),
            ("closeInUsd1m", "ciu1m"),
            ("buys5m", "b5m"),
            ("sells5m", "s5m"),
            ("buyers5m", "be5m"),
            ("sellers5m", "se5m"),
            ("buyVolumeInUsd5m", "bviu5m"),
            ("sellVolumeInUsd5m", "sviu5m"),
            ("price5m", "p5m"),
            ("openInUsd5m", "oiu5m"),
            ("closeInUsd5m", "ciu5m"),
            ("buys15m", "b15m"),
            ("sells15m", "s15m"),
            ("buyers15m", "be15m"),
            ("sellers15m", "se15m"),
            ("buyVolumeInUsd15m", "bviu15m"),
            ("sellVolumeInUsd15m", "sviu15m"),
            ("price15m", "p15m"),
            ("openInUsd15m", "oiu15m"),
            ("closeInUsd15m", "ciu15m"),
            ("buys30m", "b30m"),
            ("sells30m", "s30m"),
            ("buyers30m", "be30m"),
            ("sellers30m", "se30m"),
            ("buyVolumeInUsd30m", "bviu30m"),
            ("sellVolumeInUsd30m", "sviu30m"),
            ("price30m", "p30m"),
            ("openInUsd30m", "oiu30m"),
            ("closeInUsd30m", "ciu30m"),
            ("buys1h", "b1h"),
            ("sells1h", "s1h"),
            ("buyers1h", "be1h"),
            ("sellers1h", "se1h"),
            ("buyVolumeInUsd1h", "bviu1h"),
            ("sellVolumeInUsd1h", "sviu1h"),
            ("price1h", "p1h"),
            ("openInUsd1h", "oiu1h"),
            ("closeInUsd1h", "ciu1h"),
            ("buys4h", "b4h"),
            ("sells4h", "s4h"),
            ("buyers4h", "be4h"),
            ("sellers4h", "se4h"),
            ("buyVolumeInUsd4h", "bviu4h"),
            ("sellVolumeInUsd4h", "sviu4h"),
            ("price4h", "p4h"),
            ("openInUsd4h", "oiu4h"),
            ("closeInUsd4h", "ciu4h"),
            ("buys24h", "b24h"),
            ("sells24h", "s24h"),
            ("buyers24h", "be24h"),
            ("sellers24h", "se24h"),
            ("buyVolumeInUsd24h", "bviu24h"),
            ("sellVolumeInUsd24h", "sviu24h"),
            ("price24h", "p24h"),
            ("price", "p"),
            ("openInUsd24h", "oiu24h"),
            ("closeInUsd24h", "ciu24h"),
        ]),
    );

    // Token holder subscription fields
    mappings.insert(
        "subscribe_token_holders",
        HashMap::from([
            ("tokenAddress", "a"),
            ("holders", "h"),
            ("top100Amount", "t100a"),
            ("top10Amount", "t10a"),
            ("top100Holders", "t100h"),
            ("top10Holders", "t10h"),
            ("top100Ratio", "t100r"),
            ("top10Ratio", "t10r"),
            ("timestamp", "ts"),
        ]),
    );

    // New token subscription fields (dex-new-token, supports CEL filter)
    mappings.insert(
        "subscribe_new_token",
        HashMap::from([
            ("tokenAddress", "a"),
            ("name", "n"),
            ("symbol", "s"),
            ("decimals", "dec"),
            ("imageUrl", "iu"),
            ("description", "de"),
            ("createdAtMs", "cts"),
            ("coingeckoCoinId", "cgi"),
            ("socialMedia.twitter", "sm.tw"),
            ("socialMedia.telegram", "sm.tg"),
            ("socialMedia.website", "sm.w"),
            ("socialMedia.tiktok", "sm.tt"),
            ("socialMedia.discord", "sm.dc"),
            ("socialMedia.facebook", "sm.fb"),
            ("socialMedia.github", "sm.gh"),
            ("socialMedia.instagram", "sm.ig"),
            ("socialMedia.linkedin", "sm.li"),
            ("socialMedia.medium", "sm.md"),
            ("socialMedia.reddit", "sm.rd"),
            ("socialMedia.youtube", "sm.yt"),
            ("socialMedia.bitbucket", "sm.bb"),
            ("launchFrom.programAddress", "lf.pa"),
            ("launchFrom.protocolFamily", "lf.pf"),
            ("launchFrom.protocolName", "lf.pn"),
            ("migratedTo.programAddress", "mt.pa"),
            ("migratedTo.protocolFamily", "mt.pf"),
            ("migratedTo.protocolName", "mt.pn"),
        ]),
    );

    // Token supply subscription fields
    mappings.insert(
        "subscribe_token_supply",
        HashMap::from([("tokenAddress", "a"), ("supply", "s"), ("timestamp", "ts")]),
    );

    // DEX pool balance subscription fields
    mappings.insert(
        "subscribe_dex_pool_balance",
        HashMap::from([
            ("poolAddress", "a"),
            ("tokenAAddress", "taa"),
            ("tokenALiquidityInUsd", "taliu"),
            ("tokenBAddress", "tba"),
            ("tokenBLiquidityInUsd", "tbliu"),
        ]),
    );

    // Token liquidity subscription fields
    mappings.insert(
        "subscribe_token_liquidity",
        HashMap::from([
            ("tokenAddress", "a"),
            ("metricType", "t"),
            ("value", "v"),
            ("timestamp", "ts"),
        ]),
    );

    // New token metadata subscription fields (dex-new-tokens-metadata)
    mappings.insert(
        "subscribe_new_tokens_metadata",
        HashMap::from([
            ("tokenAddress", "a"),
            ("name", "n"),
            ("decimals", "dec"),
            ("symbol", "s"),
            ("imageUrl", "iu"),
            ("description", "de"),
            ("createdAtMs", "cts"),
            ("coingeckoCoinId", "cgi"),
            ("socialMedia.twitter", "sm.tw"),
            ("socialMedia.telegram", "sm.tg"),
            ("socialMedia.website", "sm.w"),
            ("socialMedia.tiktok", "sm.tt"),
            ("socialMedia.discord", "sm.dc"),
            ("socialMedia.facebook", "sm.fb"),
            ("socialMedia.github", "sm.gh"),
            ("socialMedia.instagram", "sm.ig"),
            ("socialMedia.linkedin", "sm.li"),
            ("socialMedia.medium", "sm.md"),
            ("socialMedia.reddit", "sm.rd"),
            ("socialMedia.youtube", "sm.yt"),
            ("socialMedia.bitbucket", "sm.bb"),
            ("launchFrom.programAddress", "lf.pa"),
            ("launchFrom.protocolFamily", "lf.pf"),
            ("launchFrom.protocolName", "lf.pn"),
            ("migratedTo.programAddress", "mt.pa"),
            ("migratedTo.protocolFamily", "mt.pf"),
            ("migratedTo.protocolName", "mt.pn"),
        ]),
    );

    // New tokens list subscription fields (dex-new-tokens)
    mappings.insert(
        "subscribe_new_tokens",
        HashMap::from([
            ("tokenAddress", "a"),
            ("name", "n"),
            ("decimals", "dec"),
            ("symbol", "s"),
            ("imageUrl", "iu"),
            ("description", "de"),
            ("createdAtMs", "cts"),
            ("coingeckoCoinId", "cgi"),
            ("socialMedia.twitter", "sm.tw"),
            ("socialMedia.telegram", "sm.tg"),
            ("socialMedia.website", "sm.w"),
            ("socialMedia.tiktok", "sm.tt"),
            ("socialMedia.discord", "sm.dc"),
            ("socialMedia.facebook", "sm.fb"),
            ("socialMedia.github", "sm.gh"),
            ("socialMedia.instagram", "sm.ig"),
            ("socialMedia.linkedin", "sm.li"),
            ("socialMedia.medium", "sm.md"),
            ("socialMedia.reddit", "sm.rd"),
            ("socialMedia.youtube", "sm.yt"),
            ("socialMedia.bitbucket", "sm.bb"),
            ("launchFrom.programAddress", "lf.pa"),
            ("launchFrom.protocolFamily", "lf.pf"),
            ("launchFrom.protocolName", "lf.pn"),
            ("migratedTo.programAddress", "mt.pa"),
            ("migratedTo.protocolFamily", "mt.pf"),
            ("migratedTo.protocolName", "mt.pn"),
        ]),
    );

    // Token trade subscription fields
    mappings.insert(
        "subscribe_token_trades",
        HashMap::from([
            ("tokenAddress", "a"),
            ("timestamp", "t"),
            ("kind", "k"),
            ("buyAmount", "ba"),
            ("buyAmountInUsd", "baiu"),
            ("buyTokenAddress", "btma"),
            ("buyTokenName", "btn"),
            ("buyTokenSymbol", "bts"),
            ("buyWalletAddress", "bwa"),
            ("sellAmount", "sa"),
            ("sellAmountInUsd", "saiu"),
            ("sellTokenAddress", "stma"),
            ("sellTokenName", "stn"),
            ("sellTokenSymbol", "sts"),
            ("sellWalletAddress", "swa"),
            ("txHash", "h"),
        ]),
    );

    // Wallet token PnL subscription fields
    mappings.insert(
        "subscribe_wallet_pnl",
        HashMap::from([
            ("walletAddress", "a"),
            ("tokenAddress", "ta"),
            ("tokenPriceInUsd", "tpiu"),
            ("timestamp", "t"),
            ("opentime", "ot"),
            ("lasttime", "lt"),
            ("closetime", "ct"),
            ("buyAmount", "ba"),
            ("buyAmountInUsd", "baiu"),
            ("buyCount", "bs"),
            ("buyCount30d", "bs30d"),
            ("buyCount7d", "bs7d"),
            ("sellAmount", "sa"),
            ("sellAmountInUsd", "saiu"),
            ("sellCount", "ss"),
            ("sellCount30d", "ss30d"),
            ("sellCount7d", "ss7d"),
            ("heldDurationTimestamp", "hdts"),
            ("averageBuyPriceInUsd", "abpiu"),
            ("averageSellPriceInUsd", "aspiu"),
            ("unrealizedProfitInUsd", "upiu"),
            ("unrealizedProfitRatio", "upr"),
            ("realizedProfitInUsd", "rpiu"),
            ("realizedProfitRatio", "rpr"),
            ("totalRealizedProfitInUsd", "trpiu"),
            ("totalRealizedProfitRatio", "trr"),
        ]),
    );

    // Wallet trade subscription fields
    mappings.insert(
        "subscribe_wallet_trade",
        HashMap::from([
            ("tokenAddress", "a"),
            ("timestamp", "t"),
            ("kind", "k"),
            ("buyAmount", "ba"),
            ("buyAmountInUsd", "baiu"),
            ("buyTokenAddress", "btma"),
            ("buyTokenName", "btn"),
            ("buyTokenSymbol", "bts"),
            ("buyWalletAddress", "bwa"),
            ("sellAmount", "sa"),
            ("sellAmountInUsd", "saiu"),
            ("sellTokenAddress", "stma"),
            ("sellTokenName", "stn"),
            ("sellTokenSymbol", "sts"),
            ("sellWalletAddress", "swa"),
            ("txHash", "h"),
        ]),
    );

    // Token max liquidity subscription fields
    mappings.insert(
        "subscribe_token_max_liquidity",
        HashMap::from([
            ("tokenAddress", "a"),
            ("poolAddress", "p"),
            ("liquidityInUsd", "liu"),
            ("liquidityInNative", "lin"),
            ("timestamp", "ts"),
        ]),
    );

    // Token total liquidity subscription fields
    mappings.insert(
        "subscribe_token_total_liquidity",
        HashMap::from([
            ("tokenAddress", "a"),
            ("liquidityInUsd", "liu"),
            ("liquidityInNative", "lin"),
            ("poolCount", "pc"),
            ("timestamp", "ts"),
        ]),
    );

    mappings
});

/// Get field mappings for a specific subscription method
pub fn get_field_mappings(method_name: &str) -> Option<&FieldMapping> {
    CEL_FIELD_MAPPINGS.get(method_name)
}

/// Replace long field names with short field names in filter expressions
/// Automatically adds meta. prefix if not present
/// Supports nested field paths (e.g., launchFrom.protocolFamily -> lf.pf)
pub fn replace_filter_fields(filter: &str, method_name: &str) -> String {
    if filter.is_empty() {
        return filter.to_string();
    }

    let field_mappings = match get_field_mappings(method_name) {
        Some(m) => m,
        None => return filter.to_string(),
    };

    let mut result = filter.to_string();

    // Sort entries by key length descending to replace longer (nested) paths first
    let mut entries: Vec<_> = field_mappings.iter().collect();
    entries.sort_by_key(|entry| std::cmp::Reverse(entry.0.len()));

    for (long_field, short_field) in entries {
        // Handle two cases: with and without meta. prefix
        // Pattern 1: meta.fieldName (with meta. prefix) — check first to avoid double meta.
        let pattern1 = format!(r"\bmeta\.{}\b", regex::escape(long_field));
        if let Ok(re) = regex::Regex::new(&pattern1) {
            result = re
                .replace_all(&result, format!("meta.{}", short_field))
                .to_string();
        }

        // Pattern 2: fieldName (without meta. prefix)
        let pattern2 = format!(r"\b{}\b", regex::escape(long_field));
        if let Ok(re) = regex::Regex::new(&pattern2) {
            result = re
                .replace_all(&result, format!("meta.{}", short_field))
                .to_string();
        }
    }

    result
}

/// Get available field names for a specific subscription method
pub fn get_available_fields(method_name: &str) -> Vec<&'static str> {
    match get_field_mappings(method_name) {
        Some(mappings) => mappings.keys().copied().collect(),
        None => Vec::new(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_field_mappings() {
        let mappings = get_field_mappings("subscribe_token_candles");
        assert!(mappings.is_some());
        let mappings = mappings.unwrap();
        assert_eq!(mappings.get("open"), Some(&"o"));
        assert_eq!(mappings.get("close"), Some(&"c"));
    }

    #[test]
    fn test_replace_filter_fields() {
        let filter = "open > 100 && close < 200";
        let result = replace_filter_fields(filter, "subscribe_token_candles");
        assert!(result.contains("meta.o"));
        assert!(result.contains("meta.c"));
    }

    #[test]
    fn test_get_available_fields() {
        let fields = get_available_fields("subscribe_token_candles");
        assert!(fields.contains(&"open"));
        assert!(fields.contains(&"close"));
        assert!(fields.contains(&"high"));
        assert!(fields.contains(&"low"));
    }
}
