// file: kb_lib/src/trade_solana_amounts.rs

//! Solana transaction/meta trade amount extraction helpers.
//!
//! This module contains generic fallback logic based on transaction JSON,
//! transaction meta, account keys, token balances, native balances and
//! instruction-scoped SPL token transfers.

/// Extracted base/quote amounts and optional quote/base price.
pub(crate) type ExtractedTradeAmounts = (
    std::option::Option<std::string::String>,
    std::option::Option<std::string::String>,
    std::option::Option<f64>,
);

/// Extracts base/quote amounts from token-account vault balance deltas.
pub(crate) fn extract_trade_amounts_from_vault_balance_deltas(
    transaction_json: &str,
    meta_json: std::option::Option<&str>,
    base_vault_address: std::option::Option<&str>,
    quote_vault_address: std::option::Option<&str>,
) -> Result<crate::trade_solana_amounts::ExtractedTradeAmounts, crate::Error> {
    let meta_json = match meta_json {
        Some(meta_json) => meta_json,
        None => return Ok((None, None, None)),
    };
    let transaction_value_result = serde_json::from_str::<serde_json::Value>(transaction_json);
    let transaction_value = match transaction_value_result {
        Ok(transaction_value) => transaction_value,
        Err(error) => {
            return Err(crate::Error::Json(format!(
                "cannot parse transaction_json for vault balance amount extraction: {}",
                error
            )));
        },
    };
    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 vault balance amount extraction: {}",
                error
            )));
        },
    };
    let account_keys_result =
        crate::trade_solana_amounts::extract_transaction_account_keys(&transaction_value);
    let account_keys = match account_keys_result {
        Ok(account_keys) => account_keys,
        Err(error) => return Err(error),
    };
    let pre_balances_result = crate::trade_solana_amounts::extract_token_balance_map(
        &meta_value,
        &account_keys,
        "preTokenBalances",
    );
    let pre_balances = match pre_balances_result {
        Ok(pre_balances) => pre_balances,
        Err(error) => return Err(error),
    };
    let post_balances_result = crate::trade_solana_amounts::extract_token_balance_map(
        &meta_value,
        &account_keys,
        "postTokenBalances",
    );
    let post_balances = match post_balances_result {
        Ok(post_balances) => post_balances,
        Err(error) => return Err(error),
    };
    let mut base_amount_raw = None;
    let mut quote_amount_raw = None;
    let mut price_quote_per_base = None;
    if let Some(base_vault_address) = base_vault_address {
        let base_pre = pre_balances.get(base_vault_address);
        let base_post = post_balances.get(base_vault_address);
        let base_pre_raw = match base_pre {
            Some(value) => Some(value.0.clone()),
            None => None,
        };
        let base_post_raw = match base_post {
            Some(value) => Some(value.0.clone()),
            None => None,
        };
        base_amount_raw =
            crate::trade_solana_amounts::compute_amount_delta_abs(base_pre_raw, base_post_raw);
        let base_pre_ui = match base_pre {
            Some(value) => value.1,
            None => None,
        };
        let base_post_ui = match base_post {
            Some(value) => value.1,
            None => None,
        };
        let base_delta_ui =
            crate::trade_solana_amounts::compute_ui_delta_abs(base_pre_ui, base_post_ui);
        if let Some(quote_vault_address) = quote_vault_address {
            let quote_pre = pre_balances.get(quote_vault_address);
            let quote_post = post_balances.get(quote_vault_address);
            let quote_pre_raw = match quote_pre {
                Some(value) => Some(value.0.clone()),
                None => None,
            };
            let quote_post_raw = match quote_post {
                Some(value) => Some(value.0.clone()),
                None => None,
            };
            quote_amount_raw = crate::trade_solana_amounts::compute_amount_delta_abs(
                quote_pre_raw,
                quote_post_raw,
            );
            let quote_pre_ui = match quote_pre {
                Some(value) => value.1,
                None => None,
            };
            let quote_post_ui = match quote_post {
                Some(value) => value.1,
                None => None,
            };
            let quote_delta_ui =
                crate::trade_solana_amounts::compute_ui_delta_abs(quote_pre_ui, quote_post_ui);
            if let (Some(base_delta_ui), Some(quote_delta_ui)) = (base_delta_ui, quote_delta_ui) {
                if base_delta_ui > 0.0 {
                    price_quote_per_base = Some(quote_delta_ui / base_delta_ui);
                }
            }
        }
    }
    return Ok((base_amount_raw, quote_amount_raw, price_quote_per_base));
}

/// Extracts base/quote amounts from instruction-scoped SPL token transfers.
pub(crate) fn extract_trade_amounts_from_instruction_token_transfers(
    meta_json: std::option::Option<&str>,
    instruction_index: std::option::Option<u32>,
    input_vault_address: std::option::Option<&str>,
    output_vault_address: std::option::Option<&str>,
    input_token_account: std::option::Option<&str>,
    output_token_account: std::option::Option<&str>,
    base_vault_address: std::option::Option<&str>,
    quote_vault_address: std::option::Option<&str>,
) -> Result<crate::trade_solana_amounts::ExtractedTradeAmounts, crate::Error> {
    let meta_json = match meta_json {
        Some(meta_json) => meta_json,
        None => return Ok((None, None, None)),
    };
    let instruction_index = match instruction_index {
        Some(instruction_index) => u64::from(instruction_index),
        None => return Ok((None, None, None)),
    };
    let input_vault_address = match input_vault_address {
        Some(input_vault_address) => input_vault_address.trim(),
        None => return Ok((None, None, None)),
    };
    let output_vault_address = match output_vault_address {
        Some(output_vault_address) => output_vault_address.trim(),
        None => return Ok((None, None, None)),
    };
    let input_token_account = match input_token_account {
        Some(input_token_account) => input_token_account.trim(),
        None => return Ok((None, None, None)),
    };
    let output_token_account = match output_token_account {
        Some(output_token_account) => output_token_account.trim(),
        None => return Ok((None, None, None)),
    };
    let base_vault_address = match base_vault_address {
        Some(base_vault_address) => base_vault_address.trim(),
        None => return Ok((None, None, None)),
    };
    let quote_vault_address = match quote_vault_address {
        Some(quote_vault_address) => quote_vault_address.trim(),
        None => return Ok((None, None, None)),
    };
    if input_vault_address.is_empty()
        || output_vault_address.is_empty()
        || input_token_account.is_empty()
        || output_token_account.is_empty()
        || base_vault_address.is_empty()
        || quote_vault_address.is_empty()
    {
        return Ok((None, None, None));
    }
    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 instruction-scoped token transfer amount extraction: {}",
                error
            )));
        },
    };
    let inner_groups_option =
        meta_value.get("innerInstructions").and_then(|value| return value.as_array());
    let inner_groups = match inner_groups_option {
        Some(inner_groups) => inner_groups,
        None => return Ok((None, None, None)),
    };
    let mut input_amount_raw = None;
    let mut output_amount_raw = None;
    for inner_group in inner_groups {
        let group_index_option = inner_group.get("index").and_then(|value| return value.as_u64());
        let group_index = match group_index_option {
            Some(group_index) => group_index,
            None => continue,
        };
        if group_index != instruction_index {
            continue;
        }
        let instructions_option =
            inner_group.get("instructions").and_then(|value| return value.as_array());
        let instructions = match instructions_option {
            Some(instructions) => instructions,
            None => continue,
        };
        for instruction in instructions {
            if !crate::trade_solana_amounts::is_spl_token_transfer_instruction(instruction) {
                continue;
            }
            let parsed = match instruction.get("parsed") {
                Some(parsed) => parsed,
                None => continue,
            };
            let info = match parsed.get("info") {
                Some(info) => info,
                None => continue,
            };
            let source_option =
                crate::trade_solana_amounts::extract_string_by_candidate_keys(info, &["source"]);
            let source = match source_option {
                Some(source) => source,
                None => continue,
            };
            let destination_option = crate::trade_solana_amounts::extract_string_by_candidate_keys(
                info,
                &["destination"],
            );
            let destination = match destination_option {
                Some(destination) => destination,
                None => continue,
            };
            let amount_option = crate::trade_solana_amounts::extract_spl_transfer_amount_raw(info);
            let amount = match amount_option {
                Some(amount) => amount,
                None => continue,
            };
            if input_amount_raw.is_none()
                && crate::trade_solana_amounts::account_equals(source.as_str(), input_token_account)
                && crate::trade_solana_amounts::account_equals(
                    destination.as_str(),
                    input_vault_address,
                )
            {
                input_amount_raw = Some(amount.clone());
                continue;
            }
            if output_amount_raw.is_none()
                && crate::trade_solana_amounts::account_equals(
                    source.as_str(),
                    output_vault_address,
                )
                && crate::trade_solana_amounts::account_equals(
                    destination.as_str(),
                    output_token_account,
                )
            {
                output_amount_raw = Some(amount);
                continue;
            }
        }
    }
    if input_amount_raw.is_none() && output_amount_raw.is_none() {
        return Ok((None, None, None));
    }
    if crate::trade_solana_amounts::account_equals(input_vault_address, base_vault_address)
        && crate::trade_solana_amounts::account_equals(output_vault_address, quote_vault_address)
    {
        return Ok((input_amount_raw, output_amount_raw, None));
    }
    if crate::trade_solana_amounts::account_equals(input_vault_address, quote_vault_address)
        && crate::trade_solana_amounts::account_equals(output_vault_address, base_vault_address)
    {
        return Ok((output_amount_raw, input_amount_raw, None));
    }
    return Ok((None, None, None));
}

/// Extracts Pump.fun amounts from transaction token/native balance deltas.
pub(crate) fn extract_pump_fun_amounts_from_transaction(
    transaction_json: &str,
    meta_json: std::option::Option<&str>,
    base_vault_address: std::option::Option<&str>,
    quote_native_address: std::option::Option<&str>,
) -> Result<crate::trade_solana_amounts::ExtractedTradeAmounts, crate::Error> {
    let meta_json = match meta_json {
        Some(meta_json) => meta_json,
        None => return Ok((None, None, None)),
    };
    let transaction_value_result = serde_json::from_str::<serde_json::Value>(transaction_json);
    let 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_fun amount extraction: {}",
                error
            )));
        },
    };
    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_fun amount extraction: {}",
                error
            )));
        },
    };
    let account_keys_result =
        crate::trade_solana_amounts::extract_transaction_account_keys(&transaction_value);
    let account_keys = match account_keys_result {
        Ok(account_keys) => account_keys,
        Err(error) => return Err(error),
    };
    let pre_balances_result = crate::trade_solana_amounts::extract_token_balance_map(
        &meta_value,
        &account_keys,
        "preTokenBalances",
    );
    let pre_balances = match pre_balances_result {
        Ok(pre_balances) => pre_balances,
        Err(error) => return Err(error),
    };
    let post_balances_result = crate::trade_solana_amounts::extract_token_balance_map(
        &meta_value,
        &account_keys,
        "postTokenBalances",
    );
    let post_balances = match post_balances_result {
        Ok(post_balances) => post_balances,
        Err(error) => return Err(error),
    };
    let mut base_amount_raw = None;
    let mut quote_amount_raw = None;
    let mut price_quote_per_base = None;
    let mut base_delta_ui = None;
    if let Some(base_vault_address) = base_vault_address {
        let base_pre = pre_balances.get(base_vault_address);
        let base_post = post_balances.get(base_vault_address);
        let base_pre_raw = match base_pre {
            Some(value) => Some(value.0.clone()),
            None => None,
        };
        let base_post_raw = match base_post {
            Some(value) => Some(value.0.clone()),
            None => None,
        };
        base_amount_raw =
            crate::trade_solana_amounts::compute_amount_delta_abs(base_pre_raw, base_post_raw);
        let base_pre_ui = match base_pre {
            Some(value) => value.1,
            None => None,
        };
        let base_post_ui = match base_post {
            Some(value) => value.1,
            None => None,
        };
        base_delta_ui =
            crate::trade_solana_amounts::compute_ui_delta_abs(base_pre_ui, base_post_ui);
    }
    if let Some(quote_native_address) = quote_native_address {
        let quote_delta_result =
            crate::trade_solana_amounts::extract_native_balance_delta_by_address(
                &meta_value,
                &account_keys,
                quote_native_address,
            );
        let quote_delta = match quote_delta_result {
            Ok(quote_delta) => quote_delta,
            Err(error) => return Err(error),
        };
        if let Some(quote_delta_lamports) = quote_delta {
            quote_amount_raw = Some(quote_delta_lamports.to_string());
            let quote_delta_ui = quote_delta_lamports as f64 / 1_000_000_000.0;
            if let Some(base_delta_ui) = base_delta_ui {
                if base_delta_ui > 0.0 {
                    price_quote_per_base = Some(quote_delta_ui / base_delta_ui);
                }
            }
        }
    }
    return Ok((base_amount_raw, quote_amount_raw, price_quote_per_base));
}

/// Computes quote/base price from vault balance deltas.
pub(crate) fn compute_price_quote_per_base_with_decimals(
    meta_json: std::option::Option<&str>,
    transaction_json: &str,
    base_vault_address: std::option::Option<&str>,
    quote_vault_address: std::option::Option<&str>,
) -> std::option::Option<f64> {
    let inferred_result =
        crate::trade_solana_amounts::extract_trade_amounts_from_vault_balance_deltas(
            transaction_json,
            meta_json,
            base_vault_address,
            quote_vault_address,
        );
    let inferred = match inferred_result {
        Ok(inferred) => inferred,
        Err(_) => return None,
    };
    return inferred.2;
}

fn extract_spl_transfer_amount_raw(
    info: &serde_json::Value,
) -> std::option::Option<std::string::String> {
    let amount = crate::trade_solana_amounts::extract_scalar_as_string_by_candidate_keys(
        info,
        &["amount"],
    );
    if amount.is_some() {
        return amount;
    }
    let token_amount = match info.get("tokenAmount") {
        Some(token_amount) => token_amount,
        None => return None,
    };
    return crate::trade_solana_amounts::extract_scalar_as_string_by_candidate_keys(
        token_amount,
        &["amount"],
    );
}

fn is_spl_token_transfer_instruction(instruction: &serde_json::Value) -> bool {
    let program_id_option = instruction.get("programId").and_then(|value| return value.as_str());
    if let Some(program_id) = program_id_option {
        let spl_token_program_id = crate::SPL_TOKEN_PROGRAM_ID.to_string();
        let spl_token_2022_program_id = crate::SPL_TOKEN_2022_PROGRAM_ID.to_string();
        if program_id != spl_token_program_id.as_str()
            && program_id != spl_token_2022_program_id.as_str()
        {
            return false;
        }
    }
    let parsed_type_option = instruction
        .get("parsed")
        .and_then(|parsed| return parsed.get("type"))
        .and_then(|value| return value.as_str());
    match parsed_type_option {
        Some("transfer") => return true,
        Some("transferChecked") => return true,
        _ => return false,
    }
}

fn account_equals(left: &str, right: &str) -> bool {
    let left = left.trim();
    let right = right.trim();
    if left.is_empty() || right.is_empty() {
        return false;
    }
    return left == right;
}

fn extract_native_balance_delta_by_address(
    meta_value: &serde_json::Value,
    account_keys: &[std::string::String],
    address: &str,
) -> Result<std::option::Option<u64>, crate::Error> {
    let mut account_index = None;
    for (index, account_key) in account_keys.iter().enumerate() {
        if account_key.as_str() == address {
            account_index = Some(index);
            break;
        }
    }
    let account_index = match account_index {
        Some(account_index) => account_index,
        None => return Ok(None),
    };
    let pre_balances_option =
        meta_value.get("preBalances").and_then(|value| return value.as_array());
    let post_balances_option =
        meta_value.get("postBalances").and_then(|value| return value.as_array());
    let pre_balances = match pre_balances_option {
        Some(pre_balances) => pre_balances,
        None => return Ok(None),
    };
    let post_balances = match post_balances_option {
        Some(post_balances) => post_balances,
        None => return Ok(None),
    };
    if account_index >= pre_balances.len() || account_index >= post_balances.len() {
        return Ok(None);
    }
    let pre_balance = match pre_balances[account_index].as_u64() {
        Some(pre_balance) => pre_balance,
        None => return Ok(None),
    };
    let post_balance = match post_balances[account_index].as_u64() {
        Some(post_balance) => post_balance,
        None => return Ok(None),
    };
    if post_balance >= pre_balance {
        return Ok(Some(post_balance - pre_balance));
    }
    return Ok(Some(pre_balance - post_balance));
}

fn extract_transaction_account_keys(
    transaction_value: &serde_json::Value,
) -> Result<std::vec::Vec<std::string::String>, crate::Error> {
    let candidate_arrays = [
        transaction_value
            .get("message")
            .and_then(|value| return value.get("accountKeys")),
        transaction_value
            .get("transaction")
            .and_then(|value| return value.get("message"))
            .and_then(|value| return value.get("accountKeys")),
        transaction_value
            .get("transaction")
            .and_then(|value| return value.get("transaction"))
            .and_then(|value| return value.get("message"))
            .and_then(|value| return value.get("accountKeys")),
        transaction_value.get("accountKeys"),
    ];
    for candidate_array_option in candidate_arrays {
        let candidate_array = match candidate_array_option {
            Some(candidate_array) => candidate_array,
            None => continue,
        };
        let array = match candidate_array.as_array() {
            Some(array) => array,
            None => continue,
        };
        let mut account_keys = std::vec::Vec::new();
        for item in array {
            if let Some(value) = item.as_str() {
                account_keys.push(value.to_string());
                continue;
            }
            let pubkey_option = item.get("pubkey").and_then(|value| return value.as_str());
            if let Some(pubkey) = pubkey_option {
                account_keys.push(pubkey.to_string());
                continue;
            }
        }
        if !account_keys.is_empty() {
            return Ok(account_keys);
        }
    }
    return Err(crate::Error::Json(
        "cannot extract accountKeys from transaction_json".to_string(),
    ));
}

fn extract_token_balance_map(
    meta_value: &serde_json::Value,
    account_keys: &[std::string::String],
    field_name: &str,
) -> Result<
    std::collections::BTreeMap<
        std::string::String,
        (std::string::String, std::option::Option<f64>),
    >,
    crate::Error,
> {
    let mut result = std::collections::BTreeMap::<
        std::string::String,
        (std::string::String, std::option::Option<f64>),
    >::new();
    let balances_option = meta_value.get(field_name).and_then(|value| return value.as_array());
    let balances = match balances_option {
        Some(balances) => balances,
        None => return Ok(result),
    };
    for balance in balances {
        let account_index_option =
            balance.get("accountIndex").and_then(|value| return value.as_u64());
        let account_index = match account_index_option {
            Some(account_index) => account_index as usize,
            None => continue,
        };
        if account_index >= account_keys.len() {
            continue;
        }
        let account_address = account_keys[account_index].clone();
        let ui_token_amount = match balance.get("uiTokenAmount") {
            Some(ui_token_amount) => ui_token_amount,
            None => continue,
        };
        let raw_amount_option =
            ui_token_amount.get("amount").and_then(|value| return value.as_str());
        let raw_amount = match raw_amount_option {
            Some(raw_amount) => raw_amount.to_string(),
            None => continue,
        };
        let ui_amount_string_option =
            ui_token_amount.get("uiAmountString").and_then(|value| return value.as_str());
        let ui_amount = match ui_amount_string_option {
            Some(ui_amount_string) => {
                let parse_result = ui_amount_string.parse::<f64>();
                match parse_result {
                    Ok(ui_amount) => Some(ui_amount),
                    Err(_) => None,
                }
            },
            None => None,
        };
        result.insert(account_address, (raw_amount, ui_amount));
    }
    return Ok(result);
}

fn compute_amount_delta_abs(
    pre_amount: std::option::Option<std::string::String>,
    post_amount: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
    let pre_amount = match pre_amount {
        Some(pre_amount) => pre_amount,
        None => "0".to_string(),
    };
    let post_amount = match post_amount {
        Some(post_amount) => post_amount,
        None => "0".to_string(),
    };
    let pre_value_result = pre_amount.parse::<i128>();
    let pre_value = match pre_value_result {
        Ok(pre_value) => pre_value,
        Err(_) => return None,
    };
    let post_value_result = post_amount.parse::<i128>();
    let post_value = match post_value_result {
        Ok(post_value) => post_value,
        Err(_) => return None,
    };
    let delta = if post_value >= pre_value {
        post_value - pre_value
    } else {
        pre_value - post_value
    };
    return Some(delta.to_string());
}

fn compute_ui_delta_abs(
    pre_amount: std::option::Option<f64>,
    post_amount: std::option::Option<f64>,
) -> std::option::Option<f64> {
    let pre_amount = match pre_amount {
        Some(pre_amount) => pre_amount,
        None => 0.0,
    };
    let post_amount = match post_amount {
        Some(post_amount) => post_amount,
        None => 0.0,
    };
    let delta = if post_amount >= pre_amount {
        post_amount - pre_amount
    } else {
        pre_amount - post_amount
    };
    return Some(delta);
}

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_solana_amounts::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_solana_amounts::extract_string_by_candidate_keys(
                nested_value,
                candidate_keys,
            );
            if nested_result.is_some() {
                return nested_result;
            }
        }
    }
    return None;
}

fn extract_scalar_as_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 {
                if let Some(direct_text) = direct.as_str() {
                    return Some(direct_text.to_string());
                }
                if let Some(direct_u64) = direct.as_u64() {
                    return Some(direct_u64.to_string());
                }
                if let Some(direct_i64) = direct.as_i64() {
                    return Some(direct_i64.to_string());
                }
            }
        }
        for nested_value in object.values() {
            let nested_result =
                crate::trade_solana_amounts::extract_scalar_as_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_solana_amounts::extract_scalar_as_string_by_candidate_keys(
                    nested_value,
                    candidate_keys,
                );
            if nested_result.is_some() {
                return nested_result;
            }
        }
    }
    return None;
}

#[cfg(test)]
mod tests {
    #[test]
    fn vault_balance_deltas_extract_raw_amounts_and_price() {
        let transaction_json = serde_json::json!({
            "transaction": {
                "message": {
                    "accountKeys": [
                        "BaseVault111",
                        "QuoteVault111"
                    ]
                }
            }
        });
        let meta_json = serde_json::json!({
            "preTokenBalances": [
                {
                    "accountIndex": 0,
                    "uiTokenAmount": {
                        "amount": "1000000",
                        "uiAmountString": "1.0"
                    }
                },
                {
                    "accountIndex": 1,
                    "uiTokenAmount": {
                        "amount": "2000000000",
                        "uiAmountString": "2.0"
                    }
                }
            ],
            "postTokenBalances": [
                {
                    "accountIndex": 0,
                    "uiTokenAmount": {
                        "amount": "1500000",
                        "uiAmountString": "1.5"
                    }
                },
                {
                    "accountIndex": 1,
                    "uiTokenAmount": {
                        "amount": "1000000000",
                        "uiAmountString": "1.0"
                    }
                }
            ]
        });
        let transaction_json_text = transaction_json.to_string();
        let meta_json_text = meta_json.to_string();
        let result = super::extract_trade_amounts_from_vault_balance_deltas(
            transaction_json_text.as_str(),
            Some(meta_json_text.as_str()),
            Some("BaseVault111"),
            Some("QuoteVault111"),
        );
        let amounts = match result {
            Ok(amounts) => amounts,
            Err(error) => panic!("vault delta extraction should succeed: {}", error),
        };
        assert_eq!(amounts.0, Some("500000".to_string()));
        assert_eq!(amounts.1, Some("1000000000".to_string()));
        assert_eq!(amounts.2, Some(2.0));
    }

    #[test]
    fn instruction_transfer_amounts_follow_base_quote_vault_mapping() {
        let meta_json = serde_json::json!({
            "innerInstructions": [
                {
                    "index": 3,
                    "instructions": [
                        {
                            "programId": crate::SPL_TOKEN_PROGRAM_ID,
                            "parsed": {
                                "type": "transfer",
                                "info": {
                                    "source": "UserQuote111",
                                    "destination": "QuoteVault111",
                                    "amount": "2000000000"
                                }
                            }
                        },
                        {
                            "programId": crate::SPL_TOKEN_PROGRAM_ID,
                            "parsed": {
                                "type": "transfer",
                                "info": {
                                    "source": "BaseVault111",
                                    "destination": "UserBase111",
                                    "amount": "500000"
                                }
                            }
                        }
                    ]
                }
            ]
        });
        let meta_json_text = meta_json.to_string();
        let result = super::extract_trade_amounts_from_instruction_token_transfers(
            Some(meta_json_text.as_str()),
            Some(3),
            Some("QuoteVault111"),
            Some("BaseVault111"),
            Some("UserQuote111"),
            Some("UserBase111"),
            Some("BaseVault111"),
            Some("QuoteVault111"),
        );
        let amounts = match result {
            Ok(amounts) => amounts,
            Err(error) => panic!("instruction transfer extraction should succeed: {}", error),
        };
        assert_eq!(amounts.0, Some("500000".to_string()));
        assert_eq!(amounts.1, Some("2000000000".to_string()));
        assert_eq!(amounts.2, None);
    }

    #[test]
    fn instruction_transfer_amounts_read_transfer_checked_token_amount() {
        let meta_json = serde_json::json!({
            "innerInstructions": [
                {
                    "index": 7,
                    "instructions": [
                        {
                            "programId": crate::SPL_TOKEN_PROGRAM_ID,
                            "parsed": {
                                "type": "transferChecked",
                                "info": {
                                    "source": "UserQuote111",
                                    "destination": "QuoteVault111",
                                    "mint": "QuoteMint111",
                                    "tokenAmount": {
                                        "amount": "199900249",
                                        "decimals": 9,
                                        "uiAmountString": "0.199900249"
                                    }
                                }
                            }
                        },
                        {
                            "programId": crate::SPL_TOKEN_PROGRAM_ID,
                            "parsed": {
                                "type": "transferChecked",
                                "info": {
                                    "source": "BaseVault111",
                                    "destination": "UserBase111",
                                    "mint": "BaseMint111",
                                    "tokenAmount": {
                                        "amount": "437138968699",
                                        "decimals": 6,
                                        "uiAmountString": "437138.968699"
                                    }
                                }
                            }
                        }
                    ]
                }
            ]
        });
        let meta_json_text = meta_json.to_string();
        let result = super::extract_trade_amounts_from_instruction_token_transfers(
            Some(meta_json_text.as_str()),
            Some(7),
            Some("QuoteVault111"),
            Some("BaseVault111"),
            Some("UserQuote111"),
            Some("UserBase111"),
            Some("BaseVault111"),
            Some("QuoteVault111"),
        );
        let amounts = match result {
            Ok(amounts) => amounts,
            Err(error) => panic!("transferChecked tokenAmount extraction should succeed: {}", error),
        };
        assert_eq!(amounts.0, Some("437138968699".to_string()));
        assert_eq!(amounts.1, Some("199900249".to_string()));
        assert_eq!(amounts.2, None);
    }

    #[test]
    fn pump_fun_amounts_extract_token_delta_and_native_delta() {
        let transaction_json = serde_json::json!({
            "transaction": {
                "message": {
                    "accountKeys": [
                        "BaseVault111",
                        "NativeQuote111"
                    ]
                }
            }
        });
        let meta_json = serde_json::json!({
            "preTokenBalances": [
                {
                    "accountIndex": 0,
                    "uiTokenAmount": {
                        "amount": "1000000",
                        "uiAmountString": "1.0"
                    }
                }
            ],
            "postTokenBalances": [
                {
                    "accountIndex": 0,
                    "uiTokenAmount": {
                        "amount": "1500000",
                        "uiAmountString": "1.5"
                    }
                }
            ],
            "preBalances": [
                0,
                3000000000u64
            ],
            "postBalances": [
                0,
                2000000000u64
            ]
        });
        let transaction_json_text = transaction_json.to_string();
        let meta_json_text = meta_json.to_string();
        let result = super::extract_pump_fun_amounts_from_transaction(
            transaction_json_text.as_str(),
            Some(meta_json_text.as_str()),
            Some("BaseVault111"),
            Some("NativeQuote111"),
        );
        let amounts = match result {
            Ok(amounts) => amounts,
            Err(error) => panic!("pump_fun extraction should succeed: {}", error),
        };
        assert_eq!(amounts.0, Some("500000".to_string()));
        assert_eq!(amounts.1, Some("1000000000".to_string()));
        assert_eq!(amounts.2, Some(2.0));
    }
}
