// file: kb_lib/src/trade_pump_swap_amounts.rs

//! PumpSwap trade amount recovery helpers.
//!
//! PumpSwap decoded events can miss direct base/quote raw amounts.
//! This module contains fallback extraction strategies based on token
//! balance deltas and pool-owner balance changes.

/// PumpSwap trade amounts recovered from pool-owner token balance deltas.
#[derive(Debug, Clone)]
pub(crate) struct PumpSwapPoolBalanceDeltaAmounts {
    /// Absolute base amount in raw token units.
    pub(crate) base_amount_raw: std::string::String,
    /// Absolute quote amount in raw token units.
    pub(crate) quote_amount_raw: std::string::String,
    /// Quote/base price computed with token decimals.
    pub(crate) price_quote_per_base: f64,
}

/// Resolution state for pool-owner token balance delta recovery.
#[derive(Debug, Clone)]
pub(crate) enum PumpSwapPoolBalanceDeltaResolution {
    /// Amounts were successfully recovered and direction matched the decoded event.
    Matched(PumpSwapPoolBalanceDeltaAmounts),
    /// Deltas exist but their direction contradicts the decoded event side.
    DirectionMismatch,
    /// Required data was missing or unusable.
    MissingData,
}

impl PumpSwapPoolBalanceDeltaResolution {
    /// Returns a stable tracing label for this resolution.
    pub(crate) fn as_label(&self) -> &'static str {
        match self {
            Self::Matched(_) => return "matched",
            Self::DirectionMismatch => return "direction_mismatch",
            Self::MissingData => return "missing_data",
        }
    }
}

/// PumpSwap fallback amount extracted from transaction token balance deltas.
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct PumpSwapBalanceDeltaTradeAmounts {
    /// Base amount in UI token units.
    pub(crate) base_amount: f64,
    /// Quote amount in UI token units.
    pub(crate) quote_amount: f64,
    /// Quote/base price computed from UI amounts.
    pub(crate) price_quote_per_base: f64,
}

#[derive(Debug, Clone)]
struct TokenBalanceRawAmount {
    raw_amount: i128,
    decimals: std::option::Option<u8>,
}

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
struct TokenBalanceDeltaKey {
    account_index: i64,
    mint: std::string::String,
    owner: std::string::String,
}

#[derive(Clone, Debug)]
struct TokenBalanceDeltaAccumulator {
    pre_amount: f64,
    post_amount: f64,
}

/// Builds a transaction JSON value that contains `meta`.
pub(crate) fn build_transaction_value_with_meta_json(
    transaction_json: &str,
    meta_json: std::option::Option<&str>,
) -> Result<serde_json::Value, crate::Error> {
    let transaction_value_result = serde_json::from_str::<serde_json::Value>(transaction_json);
    let mut transaction_value = match transaction_value_result {
        Ok(transaction_value) => transaction_value,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse transaction_json for pump_swap token-balance fallback: {}",
                error
            )));
        },
    };

    let meta_json = match meta_json {
        Some(meta_json) => meta_json,
        None => return Ok(transaction_value),
    };

    let meta_value_result = serde_json::from_str::<serde_json::Value>(meta_json);
    let meta_value = match meta_value_result {
        Ok(meta_value) => meta_value,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse meta_json for pump_swap token-balance fallback: {}",
                error
            )));
        },
    };

    match transaction_value.as_object_mut() {
        Some(object) => {
            object.insert("meta".to_string(), meta_value);
            return Ok(transaction_value);
        },
        None => {},
    }

    return Ok(serde_json::json!({
        "transaction": transaction_value,
        "meta": meta_value
    }));
}

/// Converts a positive UI amount to a raw amount string using decimals.
pub(crate) fn convert_ui_amount_to_raw_string(
    amount: f64,
    decimals: std::option::Option<u8>,
) -> std::option::Option<std::string::String> {
    if !amount.is_finite() {
        return None;
    }
    if amount <= 0.0_f64 {
        return None;
    }

    let decimals = match decimals {
        Some(decimals) => decimals,
        None => return None,
    };

    let scale = 10_f64.powi(i32::from(decimals));
    if !scale.is_finite() {
        return None;
    }
    if scale <= 0.0_f64 {
        return None;
    }

    let raw_amount = amount * scale;
    if !raw_amount.is_finite() {
        return None;
    }
    if raw_amount <= 0.0_f64 {
        return None;
    }

    let rounded = raw_amount.round();
    if rounded <= 0.0_f64 {
        return None;
    }

    return Some(format!("{:.0}", rounded));
}

/// Recovers PumpSwap amounts from full-transaction token balance deltas.
pub(crate) fn try_build_pump_swap_trade_amounts_from_token_balance_deltas(
    transaction_json: &serde_json::Value,
    base_mint: &str,
    quote_mint: &str,
) -> std::option::Option<PumpSwapBalanceDeltaTradeAmounts> {
    if base_mint.is_empty() {
        return std::option::Option::None;
    }
    if quote_mint.is_empty() {
        return std::option::Option::None;
    }
    if base_mint == quote_mint {
        return std::option::Option::None;
    }

    let meta_json = match find_transaction_meta_json(transaction_json) {
        std::option::Option::Some(value) => value,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    let pre_token_balances = match json_array_field(meta_json, "preTokenBalances") {
        std::option::Option::Some(value) => value,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    let post_token_balances = match json_array_field(meta_json, "postTokenBalances") {
        std::option::Option::Some(value) => value,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    let mut deltas: std::collections::BTreeMap<TokenBalanceDeltaKey, TokenBalanceDeltaAccumulator> =
        std::collections::BTreeMap::new();

    apply_token_balance_side_to_delta_map(
        &mut deltas,
        pre_token_balances,
        base_mint,
        quote_mint,
        true,
    );

    apply_token_balance_side_to_delta_map(
        &mut deltas,
        post_token_balances,
        base_mint,
        quote_mint,
        false,
    );

    let mut base_amount = 0.0_f64;
    let mut quote_amount = 0.0_f64;

    for (key, accumulator) in &deltas {
        let delta_abs = (accumulator.post_amount - accumulator.pre_amount).abs();
        if !delta_abs.is_finite() {
            continue;
        }
        if delta_abs <= 0.0_f64 {
            continue;
        }

        if key.mint == base_mint {
            if delta_abs > base_amount {
                base_amount = delta_abs;
            }
        } else if key.mint == quote_mint && delta_abs > quote_amount {
            quote_amount = delta_abs;
        }
    }

    if !base_amount.is_finite() {
        return std::option::Option::None;
    }
    if !quote_amount.is_finite() {
        return std::option::Option::None;
    }
    if base_amount <= 0.0_f64 {
        return std::option::Option::None;
    }
    if quote_amount <= 0.0_f64 {
        return std::option::Option::None;
    }

    let price_quote_per_base = quote_amount / base_amount;
    if !price_quote_per_base.is_finite() {
        return std::option::Option::None;
    }
    if price_quote_per_base <= 0.0_f64 {
        return std::option::Option::None;
    }

    return std::option::Option::Some(PumpSwapBalanceDeltaTradeAmounts {
        base_amount,
        quote_amount,
        price_quote_per_base,
    });
}

/// Recovers PumpSwap amounts from pool-owner pre/post token balances.
pub(crate) fn resolve_pump_swap_trade_amounts_from_pool_balance_deltas(
    meta_json: std::option::Option<&str>,
    pool_owner: &str,
    base_mint: &str,
    quote_mint: &str,
    event_kind: &str,
    base_token_decimals: std::option::Option<u8>,
    quote_token_decimals: std::option::Option<u8>,
) -> Result<PumpSwapPoolBalanceDeltaResolution, crate::Error> {
    let meta_json = match meta_json {
        Some(meta_json) => meta_json,
        None => return Ok(PumpSwapPoolBalanceDeltaResolution::MissingData),
    };

    let pool_owner = pool_owner.trim();
    let base_mint = base_mint.trim();
    let quote_mint = quote_mint.trim();

    if pool_owner.is_empty() || base_mint.is_empty() || quote_mint.is_empty() {
        return Ok(PumpSwapPoolBalanceDeltaResolution::MissingData);
    }
    if base_mint == quote_mint {
        return Ok(PumpSwapPoolBalanceDeltaResolution::MissingData);
    }

    let meta_value_result = serde_json::from_str::<serde_json::Value>(meta_json);
    let meta_value = match meta_value_result {
        Ok(meta_value) => meta_value,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse meta_json for pump_swap pool-owner balance delta extraction: {}",
                error
            )));
        },
    };

    let base_pre = sum_token_balance_raw_amount_by_owner_and_mint(
        &meta_value,
        "preTokenBalances",
        pool_owner,
        base_mint,
    );
    let base_post = sum_token_balance_raw_amount_by_owner_and_mint(
        &meta_value,
        "postTokenBalances",
        pool_owner,
        base_mint,
    );
    let quote_pre = sum_token_balance_raw_amount_by_owner_and_mint(
        &meta_value,
        "preTokenBalances",
        pool_owner,
        quote_mint,
    );
    let quote_post = sum_token_balance_raw_amount_by_owner_and_mint(
        &meta_value,
        "postTokenBalances",
        pool_owner,
        quote_mint,
    );

    if base_pre.is_none() && base_post.is_none() {
        return Ok(PumpSwapPoolBalanceDeltaResolution::MissingData);
    }
    if quote_pre.is_none() && quote_post.is_none() {
        return Ok(PumpSwapPoolBalanceDeltaResolution::MissingData);
    }

    let base_pre_raw = token_balance_raw_amount_value(base_pre.as_ref());
    let base_post_raw = token_balance_raw_amount_value(base_post.as_ref());
    let quote_pre_raw = token_balance_raw_amount_value(quote_pre.as_ref());
    let quote_post_raw = token_balance_raw_amount_value(quote_post.as_ref());

    let base_delta = base_post_raw - base_pre_raw;
    let quote_delta = quote_post_raw - quote_pre_raw;

    if base_delta == 0 || quote_delta == 0 {
        return Ok(PumpSwapPoolBalanceDeltaResolution::MissingData);
    }

    let pool_delta_side = if base_delta < 0 && quote_delta > 0 {
        crate::SwapTradeSide::BuyBase
    } else if base_delta > 0 && quote_delta < 0 {
        crate::SwapTradeSide::SellBase
    } else {
        return Ok(PumpSwapPoolBalanceDeltaResolution::MissingData);
    };

    let event_side = trade_side_from_event_kind(event_kind);
    if event_side != pool_delta_side {
        return Ok(PumpSwapPoolBalanceDeltaResolution::DirectionMismatch);
    }

    let base_amount_abs = i128_abs(base_delta);
    let quote_amount_abs = i128_abs(quote_delta);

    if base_amount_abs <= 0 || quote_amount_abs <= 0 {
        return Ok(PumpSwapPoolBalanceDeltaResolution::MissingData);
    }

    let base_decimals = first_some_u8(
        base_token_decimals,
        first_token_balance_decimals(base_pre.as_ref(), base_post.as_ref()),
    );
    let quote_decimals = first_some_u8(
        quote_token_decimals,
        first_token_balance_decimals(quote_pre.as_ref(), quote_post.as_ref()),
    );

    let price_quote_per_base = compute_price_quote_per_base_from_i128_raw_amounts_with_decimals(
        base_amount_abs,
        quote_amount_abs,
        base_decimals,
        quote_decimals,
    );

    let price_quote_per_base = match price_quote_per_base {
        Some(price_quote_per_base) => price_quote_per_base,
        None => return Ok(PumpSwapPoolBalanceDeltaResolution::MissingData),
    };

    return Ok(PumpSwapPoolBalanceDeltaResolution::Matched(PumpSwapPoolBalanceDeltaAmounts {
        base_amount_raw: base_amount_abs.to_string(),
        quote_amount_raw: quote_amount_abs.to_string(),
        price_quote_per_base,
    }));
}

fn apply_token_balance_side_to_delta_map(
    deltas: &mut std::collections::BTreeMap<TokenBalanceDeltaKey, TokenBalanceDeltaAccumulator>,
    token_balances: &[serde_json::Value],
    base_mint: &str,
    quote_mint: &str,
    is_pre: bool,
) {
    for token_balance in token_balances {
        let mint = match json_string_field(token_balance, "mint") {
            std::option::Option::Some(value) => value,
            std::option::Option::None => {
                continue;
            },
        };

        if mint != base_mint && mint != quote_mint {
            continue;
        }

        let account_index = match json_i64_field(token_balance, "accountIndex") {
            std::option::Option::Some(value) => value,
            std::option::Option::None => {
                continue;
            },
        };

        let owner = match json_string_field(token_balance, "owner") {
            std::option::Option::Some(value) => value,
            std::option::Option::None => std::string::String::new(),
        };

        let ui_amount = match token_balance_ui_amount(token_balance) {
            std::option::Option::Some(value) => value,
            std::option::Option::None => {
                continue;
            },
        };

        if !ui_amount.is_finite() {
            continue;
        }

        let key = TokenBalanceDeltaKey { account_index, mint, owner };
        let entry = deltas.entry(key).or_insert(TokenBalanceDeltaAccumulator {
            pre_amount: 0.0_f64,
            post_amount: 0.0_f64,
        });

        if is_pre {
            entry.pre_amount = ui_amount;
        } else {
            entry.post_amount = ui_amount;
        }
    }
}

fn token_balance_ui_amount(token_balance: &serde_json::Value) -> std::option::Option<f64> {
    let ui_token_amount = match json_object_field(token_balance, "uiTokenAmount") {
        std::option::Option::Some(value) => value,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    match json_f64_field(ui_token_amount, "uiAmount") {
        std::option::Option::Some(value) => {
            return std::option::Option::Some(value);
        },
        std::option::Option::None => {},
    }

    match json_string_field(ui_token_amount, "uiAmountString") {
        std::option::Option::Some(value) => match value.parse::<f64>() {
            std::result::Result::Ok(parsed) => {
                return std::option::Option::Some(parsed);
            },
            std::result::Result::Err(_) => {},
        },
        std::option::Option::None => {},
    }

    let raw_amount = match json_string_field(ui_token_amount, "amount") {
        std::option::Option::Some(value) => value,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    let decimals = match json_i64_field(ui_token_amount, "decimals") {
        std::option::Option::Some(value) => value,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    if decimals < 0_i64 {
        return std::option::Option::None;
    }
    if decimals > 18_i64 {
        return std::option::Option::None;
    }

    let raw_amount_f64 = match raw_amount.parse::<f64>() {
        std::result::Result::Ok(value) => value,
        std::result::Result::Err(_) => {
            return std::option::Option::None;
        },
    };

    let divisor = 10_f64.powi(decimals as i32);
    if divisor <= 0.0_f64 {
        return std::option::Option::None;
    }

    return std::option::Option::Some(raw_amount_f64 / divisor);
}

fn find_transaction_meta_json(
    transaction_json: &serde_json::Value,
) -> std::option::Option<&serde_json::Value> {
    match json_object_field(transaction_json, "meta") {
        std::option::Option::Some(value) => {
            return std::option::Option::Some(value);
        },
        std::option::Option::None => {},
    }

    match json_object_field(transaction_json, "transaction") {
        std::option::Option::Some(transaction_value) => {
            match json_object_field(transaction_value, "meta") {
                std::option::Option::Some(value) => {
                    return std::option::Option::Some(value);
                },
                std::option::Option::None => {},
            }
        },
        std::option::Option::None => {},
    }

    match json_object_field(transaction_json, "result") {
        std::option::Option::Some(result_value) => {
            match json_object_field(result_value, "meta") {
                std::option::Option::Some(value) => {
                    return std::option::Option::Some(value);
                },
                std::option::Option::None => {},
            }

            match json_object_field(result_value, "transaction") {
                std::option::Option::Some(transaction_value) => {
                    match json_object_field(transaction_value, "meta") {
                        std::option::Option::Some(value) => {
                            return std::option::Option::Some(value);
                        },
                        std::option::Option::None => {},
                    }
                },
                std::option::Option::None => {},
            }
        },
        std::option::Option::None => {},
    }

    return std::option::Option::None;
}

fn sum_token_balance_raw_amount_by_owner_and_mint(
    meta_value: &serde_json::Value,
    field_name: &str,
    owner: &str,
    mint: &str,
) -> std::option::Option<TokenBalanceRawAmount> {
    let balances_value = match json_array_field(meta_value, field_name) {
        std::option::Option::Some(balances_value) => balances_value,
        std::option::Option::None => return std::option::Option::None,
    };

    let mut total = 0_i128;
    let mut found = false;
    let mut decimals = std::option::Option::None;

    for balance in balances_value {
        let balance_mint = match json_string_field(balance, "mint") {
            std::option::Option::Some(balance_mint) => balance_mint,
            std::option::Option::None => continue,
        };

        if balance_mint != mint {
            continue;
        }

        let balance_owner = match json_string_field(balance, "owner") {
            std::option::Option::Some(balance_owner) => balance_owner,
            std::option::Option::None => continue,
        };

        if balance_owner != owner {
            continue;
        }

        let ui_token_amount = match json_object_field(balance, "uiTokenAmount") {
            std::option::Option::Some(ui_token_amount) => ui_token_amount,
            std::option::Option::None => continue,
        };

        let amount_text = match json_string_field(ui_token_amount, "amount") {
            std::option::Option::Some(amount_text) => amount_text,
            std::option::Option::None => continue,
        };

        let amount_result = amount_text.parse::<i128>();
        let amount = match amount_result {
            std::result::Result::Ok(amount) => amount,
            std::result::Result::Err(_) => continue,
        };

        total += amount;
        found = true;

        if decimals.is_none() {
            let decimals_i64 = match json_i64_field(ui_token_amount, "decimals") {
                std::option::Option::Some(decimals_i64) => decimals_i64,
                std::option::Option::None => continue,
            };

            let decimals_u8 = match u8::try_from(decimals_i64) {
                std::result::Result::Ok(decimals_u8) => decimals_u8,
                std::result::Result::Err(_) => continue,
            };

            decimals = std::option::Option::Some(decimals_u8);
        }
    }

    if !found {
        return std::option::Option::None;
    }

    return std::option::Option::Some(TokenBalanceRawAmount { raw_amount: total, decimals });
}

fn token_balance_raw_amount_value(value: std::option::Option<&TokenBalanceRawAmount>) -> i128 {
    match value {
        std::option::Option::Some(value) => return value.raw_amount,
        std::option::Option::None => return 0_i128,
    }
}

fn first_token_balance_decimals(
    left: std::option::Option<&TokenBalanceRawAmount>,
    right: std::option::Option<&TokenBalanceRawAmount>,
) -> std::option::Option<u8> {
    if let std::option::Option::Some(left) = left {
        if left.decimals.is_some() {
            return left.decimals;
        }
    }

    if let std::option::Option::Some(right) = right {
        return right.decimals;
    }

    return std::option::Option::None;
}

fn first_some_u8(
    left: std::option::Option<u8>,
    right: std::option::Option<u8>,
) -> std::option::Option<u8> {
    match left {
        std::option::Option::Some(left) => return std::option::Option::Some(left),
        std::option::Option::None => return right,
    }
}

fn trade_side_from_event_kind(event_kind: &str) -> crate::SwapTradeSide {
    if event_kind.ends_with(".buy") {
        return crate::SwapTradeSide::BuyBase;
    }
    if event_kind.ends_with(".sell") {
        return crate::SwapTradeSide::SellBase;
    }
    return crate::SwapTradeSide::Unknown;
}

fn i128_abs(i128_abs: i128) -> i128 {
    if i128_abs < 0_i128 {
        return -i128_abs;
    }
    return i128_abs;
}

fn compute_price_quote_per_base_from_i128_raw_amounts_with_decimals(
    base_amount_raw: i128,
    quote_amount_raw: i128,
    base_decimals: std::option::Option<u8>,
    quote_decimals: std::option::Option<u8>,
) -> std::option::Option<f64> {
    if base_amount_raw <= 0_i128 || quote_amount_raw <= 0_i128 {
        return std::option::Option::None;
    }

    let base_decimals = match base_decimals {
        std::option::Option::Some(base_decimals) => base_decimals,
        std::option::Option::None => return std::option::Option::None,
    };

    let quote_decimals = match quote_decimals {
        std::option::Option::Some(quote_decimals) => quote_decimals,
        std::option::Option::None => return std::option::Option::None,
    };

    let base_scale = 10_f64.powi(i32::from(base_decimals));
    let quote_scale = 10_f64.powi(i32::from(quote_decimals));

    if !base_scale.is_finite() || !quote_scale.is_finite() {
        return std::option::Option::None;
    }
    if base_scale <= 0.0_f64 || quote_scale <= 0.0_f64 {
        return std::option::Option::None;
    }

    let base_amount = base_amount_raw as f64 / base_scale;
    let quote_amount = quote_amount_raw as f64 / quote_scale;

    if !base_amount.is_finite() || !quote_amount.is_finite() {
        return std::option::Option::None;
    }
    if base_amount <= 0.0_f64 || quote_amount <= 0.0_f64 {
        return std::option::Option::None;
    }

    let price_quote_per_base = quote_amount / base_amount;
    if !price_quote_per_base.is_finite() {
        return std::option::Option::None;
    }
    if price_quote_per_base <= 0.0_f64 {
        return std::option::Option::None;
    }

    return std::option::Option::Some(price_quote_per_base);
}

fn json_object_field<'a>(
    value: &'a serde_json::Value,
    field_name: &str,
) -> std::option::Option<&'a serde_json::Value> {
    match value {
        serde_json::Value::Object(object) => match object.get(field_name) {
            std::option::Option::Some(field_value) => {
                return std::option::Option::Some(field_value);
            },
            std::option::Option::None => return std::option::Option::None,
        },
        _ => return std::option::Option::None,
    }
}

fn json_array_field<'a>(
    value: &'a serde_json::Value,
    field_name: &str,
) -> std::option::Option<&'a [serde_json::Value]> {
    let field_value = match json_object_field(value, field_name) {
        std::option::Option::Some(found) => found,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    match field_value {
        serde_json::Value::Array(values) => return std::option::Option::Some(values.as_slice()),
        _ => return std::option::Option::None,
    }
}

fn json_string_field(
    value: &serde_json::Value,
    field_name: &str,
) -> std::option::Option<std::string::String> {
    let field_value = match json_object_field(value, field_name) {
        std::option::Option::Some(found) => found,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    match field_value {
        serde_json::Value::String(text) => return std::option::Option::Some(text.clone()),
        serde_json::Value::Number(number) => return std::option::Option::Some(number.to_string()),
        _ => return std::option::Option::None,
    }
}

fn json_i64_field(value: &serde_json::Value, field_name: &str) -> std::option::Option<i64> {
    let field_value = match json_object_field(value, field_name) {
        std::option::Option::Some(found) => found,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    match field_value {
        serde_json::Value::Number(number) => return number.as_i64(),
        serde_json::Value::String(text) => match text.parse::<i64>() {
            std::result::Result::Ok(parsed) => return std::option::Option::Some(parsed),
            std::result::Result::Err(_) => return std::option::Option::None,
        },
        _ => return std::option::Option::None,
    }
}

fn json_f64_field(value: &serde_json::Value, field_name: &str) -> std::option::Option<f64> {
    let field_value = match json_object_field(value, field_name) {
        std::option::Option::Some(found) => found,
        std::option::Option::None => {
            return std::option::Option::None;
        },
    };

    match field_value {
        serde_json::Value::Number(number) => return number.as_f64(),
        serde_json::Value::String(text) => match text.parse::<f64>() {
            std::result::Result::Ok(parsed) => return std::option::Option::Some(parsed),
            std::result::Result::Err(_) => return std::option::Option::None,
        },
        _ => return std::option::Option::None,
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn pump_swap_balance_delta_amounts_are_recovered_from_meta() {
        let transaction_json = serde_json::json!({
            "meta": {
                "preTokenBalances": [
                    {
                        "accountIndex": 1,
                        "mint": "BASE111",
                        "owner": "owner_a",
                        "uiTokenAmount": {
                            "uiAmount": 10.0,
                            "uiAmountString": "10",
                            "decimals": 6,
                            "amount": "10000000"
                        }
                    },
                    {
                        "accountIndex": 2,
                        "mint": "QUOTE111",
                        "owner": "owner_b",
                        "uiTokenAmount": {
                            "uiAmount": 2.0,
                            "uiAmountString": "2",
                            "decimals": 9,
                            "amount": "2000000000"
                        }
                    }
                ],
                "postTokenBalances": [
                    {
                        "accountIndex": 1,
                        "mint": "BASE111",
                        "owner": "owner_a",
                        "uiTokenAmount": {
                            "uiAmount": 15.0,
                            "uiAmountString": "15",
                            "decimals": 6,
                            "amount": "15000000"
                        }
                    },
                    {
                        "accountIndex": 2,
                        "mint": "QUOTE111",
                        "owner": "owner_b",
                        "uiTokenAmount": {
                            "uiAmount": 1.5,
                            "uiAmountString": "1.5",
                            "decimals": 9,
                            "amount": "1500000000"
                        }
                    }
                ]
            }
        });

        let amounts = match super::try_build_pump_swap_trade_amounts_from_token_balance_deltas(
            &transaction_json,
            "BASE111",
            "QUOTE111",
        ) {
            std::option::Option::Some(value) => value,
            std::option::Option::None => {
                panic!("expected pump_swap balance delta amounts");
            },
        };

        assert_eq!(amounts.base_amount, 5.0_f64);
        assert_eq!(amounts.quote_amount, 0.5_f64);
        assert_eq!(amounts.price_quote_per_base, 0.1_f64);
    }

    #[test]
    fn pump_swap_balance_delta_amounts_use_raw_amount_when_ui_amount_is_null() {
        let transaction_json = serde_json::json!({
            "meta": {
                "preTokenBalances": [
                    {
                        "accountIndex": 1,
                        "mint": "BASE111",
                        "owner": "owner_a",
                        "uiTokenAmount": {
                            "uiAmount": null,
                            "decimals": 6,
                            "amount": "10000000"
                        }
                    },
                    {
                        "accountIndex": 2,
                        "mint": "QUOTE111",
                        "owner": "owner_b",
                        "uiTokenAmount": {
                            "uiAmount": null,
                            "decimals": 9,
                            "amount": "2000000000"
                        }
                    }
                ],
                "postTokenBalances": [
                    {
                        "accountIndex": 1,
                        "mint": "BASE111",
                        "owner": "owner_a",
                        "uiTokenAmount": {
                            "uiAmount": null,
                            "decimals": 6,
                            "amount": "15000000"
                        }
                    },
                    {
                        "accountIndex": 2,
                        "mint": "QUOTE111",
                        "owner": "owner_b",
                        "uiTokenAmount": {
                            "uiAmount": null,
                            "decimals": 9,
                            "amount": "1500000000"
                        }
                    }
                ]
            }
        });

        let amounts = match super::try_build_pump_swap_trade_amounts_from_token_balance_deltas(
            &transaction_json,
            "BASE111",
            "QUOTE111",
        ) {
            std::option::Option::Some(value) => value,
            std::option::Option::None => {
                panic!("expected pump_swap balance delta amounts");
            },
        };

        assert_eq!(amounts.base_amount, 5.0_f64);
        assert_eq!(amounts.quote_amount, 0.5_f64);
        assert_eq!(amounts.price_quote_per_base, 0.1_f64);
    }

    #[test]
    fn pump_swap_balance_delta_amounts_return_none_when_base_delta_is_zero() {
        let transaction_json = serde_json::json!({
            "meta": {
                "preTokenBalances": [
                    {
                        "accountIndex": 1,
                        "mint": "BASE111",
                        "owner": "owner_a",
                        "uiTokenAmount": {
                            "uiAmount": 10.0,
                            "uiAmountString": "10",
                            "decimals": 6,
                            "amount": "10000000"
                        }
                    },
                    {
                        "accountIndex": 2,
                        "mint": "QUOTE111",
                        "owner": "owner_b",
                        "uiTokenAmount": {
                            "uiAmount": 2.0,
                            "uiAmountString": "2",
                            "decimals": 9,
                            "amount": "2000000000"
                        }
                    }
                ],
                "postTokenBalances": [
                    {
                        "accountIndex": 1,
                        "mint": "BASE111",
                        "owner": "owner_a",
                        "uiTokenAmount": {
                            "uiAmount": 10.0,
                            "uiAmountString": "10",
                            "decimals": 6,
                            "amount": "10000000"
                        }
                    },
                    {
                        "accountIndex": 2,
                        "mint": "QUOTE111",
                        "owner": "owner_b",
                        "uiTokenAmount": {
                            "uiAmount": 1.5,
                            "uiAmountString": "1.5",
                            "decimals": 9,
                            "amount": "1500000000"
                        }
                    }
                ]
            }
        });

        let amounts = super::try_build_pump_swap_trade_amounts_from_token_balance_deltas(
            &transaction_json,
            "BASE111",
            "QUOTE111",
        );

        assert_eq!(amounts, std::option::Option::None);
    }

    #[test]
    fn pump_swap_pool_owner_delta_accepts_buy_direction() {
        let meta_json = serde_json::json!({
            "preTokenBalances": [
                {
                    "accountIndex": 1,
                    "mint": "BASE111",
                    "owner": "Pool111",
                    "uiTokenAmount": {
                        "amount": "7967771862",
                        "decimals": 9
                    }
                },
                {
                    "accountIndex": 2,
                    "mint": "QUOTE111",
                    "owner": "Pool111",
                    "uiTokenAmount": {
                        "amount": "8970607",
                        "decimals": 9
                    }
                }
            ],
            "postTokenBalances": [
                {
                    "accountIndex": 1,
                    "mint": "BASE111",
                    "owner": "Pool111",
                    "uiTokenAmount": {
                        "amount": "7573340642",
                        "decimals": 9
                    }
                },
                {
                    "accountIndex": 2,
                    "mint": "QUOTE111",
                    "owner": "Pool111",
                    "uiTokenAmount": {
                        "amount": "9438980",
                        "decimals": 9
                    }
                }
            ]
        });

        let meta_json_text = meta_json.to_string();

        let result = super::resolve_pump_swap_trade_amounts_from_pool_balance_deltas(
            Some(meta_json_text.as_str()),
            "Pool111",
            "BASE111",
            "QUOTE111",
            "pump_swap.buy",
            Some(9),
            Some(9),
        );

        let resolution = match result {
            Ok(resolution) => resolution,
            Err(error) => panic!("pump_swap delta resolution must succeed: {}", error),
        };

        match resolution {
            super::PumpSwapPoolBalanceDeltaResolution::Matched(amounts) => {
                assert_eq!(amounts.base_amount_raw, "394431220");
                assert_eq!(amounts.quote_amount_raw, "468373");
                assert!(amounts.price_quote_per_base > 0.00118);
                assert!(amounts.price_quote_per_base < 0.00119);
            },
            _ => panic!("expected matched pump_swap buy delta"),
        }
    }

    #[test]
    fn pump_swap_pool_owner_delta_rejects_wrong_direction() {
        let meta_json = serde_json::json!({
            "preTokenBalances": [
                {
                    "accountIndex": 1,
                    "mint": "BASE111",
                    "owner": "Pool111",
                    "uiTokenAmount": {
                        "amount": "7967771862",
                        "decimals": 9
                    }
                },
                {
                    "accountIndex": 2,
                    "mint": "QUOTE111",
                    "owner": "Pool111",
                    "uiTokenAmount": {
                        "amount": "8970607",
                        "decimals": 9
                    }
                }
            ],
            "postTokenBalances": [
                {
                    "accountIndex": 1,
                    "mint": "BASE111",
                    "owner": "Pool111",
                    "uiTokenAmount": {
                        "amount": "7573340642",
                        "decimals": 9
                    }
                },
                {
                    "accountIndex": 2,
                    "mint": "QUOTE111",
                    "owner": "Pool111",
                    "uiTokenAmount": {
                        "amount": "9438980",
                        "decimals": 9
                    }
                }
            ]
        });

        let meta_json_text = meta_json.to_string();

        let result = super::resolve_pump_swap_trade_amounts_from_pool_balance_deltas(
            Some(meta_json_text.as_str()),
            "Pool111",
            "BASE111",
            "QUOTE111",
            "pump_swap.sell",
            Some(9),
            Some(9),
        );

        let resolution = match result {
            Ok(resolution) => resolution,
            Err(error) => panic!("pump_swap delta resolution must succeed: {}", error),
        };

        match resolution {
            super::PumpSwapPoolBalanceDeltaResolution::DirectionMismatch => {},
            _ => panic!("expected pump_swap direction mismatch"),
        }
    }

    #[test]
    fn pump_swap_pool_owner_delta_ignores_non_pool_owner() {
        let meta_json = serde_json::json!({
            "preTokenBalances": [
                {
                    "accountIndex": 1,
                    "mint": "BASE111",
                    "owner": "OtherOwner111",
                    "uiTokenAmount": {
                        "amount": "1000",
                        "decimals": 6
                    }
                },
                {
                    "accountIndex": 2,
                    "mint": "QUOTE111",
                    "owner": "OtherOwner111",
                    "uiTokenAmount": {
                        "amount": "2000",
                        "decimals": 6
                    }
                }
            ],
            "postTokenBalances": [
                {
                    "accountIndex": 1,
                    "mint": "BASE111",
                    "owner": "OtherOwner111",
                    "uiTokenAmount": {
                        "amount": "900",
                        "decimals": 6
                    }
                },
                {
                    "accountIndex": 2,
                    "mint": "QUOTE111",
                    "owner": "OtherOwner111",
                    "uiTokenAmount": {
                        "amount": "2100",
                        "decimals": 6
                    }
                }
            ]
        });

        let meta_json_text = meta_json.to_string();

        let result = super::resolve_pump_swap_trade_amounts_from_pool_balance_deltas(
            Some(meta_json_text.as_str()),
            "Pool111",
            "BASE111",
            "QUOTE111",
            "pump_swap.buy",
            Some(6),
            Some(6),
        );

        let resolution = match result {
            Ok(resolution) => resolution,
            Err(error) => panic!("pump_swap delta resolution must succeed: {}", error),
        };
        match resolution {
            super::PumpSwapPoolBalanceDeltaResolution::MissingData => {},
            _ => panic!("expected missing data for non-pool owner"),
        }
    }
}
