// file: kb_lib/src/trade_side_resolution.rs

//! Trade-side resolution helpers.
//!
//! This module resolves a normalized `SwapTradeSide` from a decoded event kind
//! and optional decoded payload metadata.

/// Resolves the normalized trade side from payload metadata and event kind.
pub(crate) fn extract_trade_side(
    event_kind: &str,
    payload: &serde_json::Value,
) -> crate::SwapTradeSide {
    let trade_side_option = crate::trade_side_resolution::extract_string_by_candidate_keys(
        payload,
        &["tradeSide", "trade_side"],
    );
    match trade_side_option.as_deref() {
        Some("BuyBase") => return crate::SwapTradeSide::BuyBase,
        Some("buy") => return crate::SwapTradeSide::BuyBase,
        Some("BUY") => return crate::SwapTradeSide::BuyBase,
        Some("SellBase") => return crate::SwapTradeSide::SellBase,
        Some("sell") => return crate::SwapTradeSide::SellBase,
        Some("SELL") => return crate::SwapTradeSide::SellBase,
        _ => {},
    }
    if event_kind.ends_with(".buy") || event_kind.contains(".buy_exact") {
        return crate::SwapTradeSide::BuyBase;
    }
    if event_kind.ends_with(".sell") || event_kind.contains(".sell_exact") {
        return crate::SwapTradeSide::SellBase;
    }
    return crate::SwapTradeSide::Unknown;
}

fn extract_string_by_candidate_keys(
    value: &serde_json::Value,
    candidate_keys: &[&str],
) -> std::option::Option<std::string::String> {
    if let Some(object) = value.as_object() {
        for candidate_key in candidate_keys {
            let direct_option = object.get(*candidate_key);
            if let Some(direct) = direct_option {
                let direct_text_option = direct.as_str();
                if let Some(direct_text) = direct_text_option {
                    return Some(direct_text.to_string());
                }
            }
        }
        for nested_value in object.values() {
            let nested_result = crate::trade_side_resolution::extract_string_by_candidate_keys(
                nested_value,
                candidate_keys,
            );
            if nested_result.is_some() {
                return nested_result;
            }
        }
        return None;
    }
    if let Some(array) = value.as_array() {
        for nested_value in array {
            let nested_result = crate::trade_side_resolution::extract_string_by_candidate_keys(
                nested_value,
                candidate_keys,
            );
            if nested_result.is_some() {
                return nested_result;
            }
        }
    }
    return None;
}

#[cfg(test)]
mod tests {
    #[test]
    fn payload_trade_side_wins_over_event_kind() {
        let payload = serde_json::json!({
            "tradeSide": "SellBase"
        });
        let side = super::extract_trade_side("pump_swap.buy", &payload);
        assert_eq!(side, crate::SwapTradeSide::SellBase);
    }

    #[test]
    fn nested_payload_trade_side_is_resolved() {
        let payload = serde_json::json!({
            "decoded": {
                "meta": {
                    "trade_side": "buy"
                }
            }
        });
        let side = super::extract_trade_side("raydium_cpmm.swap_base_input", &payload);
        assert_eq!(side, crate::SwapTradeSide::BuyBase);
    }

    #[test]
    fn buy_suffix_is_resolved_when_payload_has_no_side() {
        let payload = serde_json::json!({});
        let side = super::extract_trade_side("pump_fun.buy", &payload);
        assert_eq!(side, crate::SwapTradeSide::BuyBase);
    }

    #[test]
    fn sell_suffix_is_resolved_when_payload_has_no_side() {
        let payload = serde_json::json!({});
        let side = super::extract_trade_side("pump_fun.sell", &payload);
        assert_eq!(side, crate::SwapTradeSide::SellBase);
    }

    #[test]
    fn buy_exact_suffix_is_resolved_as_buy_base() {
        let payload = serde_json::json!({});
        let side = super::extract_trade_side("pump_swap.buy_exact_quote_in", &payload);
        assert_eq!(side, crate::SwapTradeSide::BuyBase);
    }

    #[test]
    fn unknown_side_is_returned_when_no_hint_exists() {
        let payload = serde_json::json!({});
        let side = super::extract_trade_side("raydium_cpmm.swap_base_input", &payload);
        assert_eq!(side, crate::SwapTradeSide::Unknown);
    }
}
