// file: kb_lib/src/trade_metric_update.rs

//! Trade metric update and basic trade-pricing helpers.
//!
//! This module contains pure helpers used by trade aggregation:
//! pricing validation, raw amount accumulation and pair metric updates.

/// Returns true when a decoded trade has enough positive values to be persisted.
pub(crate) fn is_priced_trade_event(
    base_amount_raw: std::option::Option<&str>,
    quote_amount_raw: std::option::Option<&str>,
    price_quote_per_base: std::option::Option<f64>,
) -> bool {
    let base_amount_raw = match base_amount_raw {
        Some(base_amount_raw) => base_amount_raw.trim(),
        None => return false,
    };
    if base_amount_raw.is_empty() {
        return false;
    }
    let base_amount_result = base_amount_raw.parse::<i128>();
    let base_amount = match base_amount_result {
        Ok(base_amount) => base_amount,
        Err(_) => return false,
    };
    if base_amount <= 0 {
        return false;
    }
    let quote_amount_raw = match quote_amount_raw {
        Some(quote_amount_raw) => quote_amount_raw.trim(),
        None => return false,
    };
    if quote_amount_raw.is_empty() {
        return false;
    }
    let quote_amount_result = quote_amount_raw.parse::<i128>();
    let quote_amount = match quote_amount_result {
        Ok(quote_amount) => quote_amount,
        Err(_) => return false,
    };
    if quote_amount <= 0 {
        return false;
    }
    let price = match price_quote_per_base {
        Some(price) => price,
        None => return false,
    };
    if !price.is_finite() {
        return false;
    }
    return price > 0.0;
}

/// Converts an optional Solana slot to an optional signed database slot.
pub(crate) fn convert_slot_to_i64(slot: std::option::Option<u64>) -> std::option::Option<i64> {
    match slot {
        Some(slot) => match i64::try_from(slot) {
            Ok(slot) => return Some(slot),
            Err(_) => return None,
        },
        None => return None,
    }
}

/// Applies one newly-created trade event to a pair metric.
pub(crate) fn apply_trade_to_pair_metric(
    metric: &mut crate::PairMetricDto,
    slot: std::option::Option<i64>,
    signature: std::string::String,
    trade_side: crate::SwapTradeSide,
    base_amount_raw: std::option::Option<std::string::String>,
    quote_amount_raw: std::option::Option<std::string::String>,
    price_quote_per_base: std::option::Option<f64>,
) {
    metric.trade_count += 1;
    if trade_side == crate::SwapTradeSide::BuyBase {
        metric.buy_count += 1;
    }
    if trade_side == crate::SwapTradeSide::SellBase {
        metric.sell_count += 1;
    }
    if metric.first_slot.is_none() {
        metric.first_slot = slot;
    }
    if metric.first_signature.is_none() {
        metric.first_signature = Some(signature.clone());
    }
    metric.last_slot = slot;
    metric.last_signature = Some(signature);
    metric.cumulative_base_amount_raw = crate::trade_metric_update::add_raw_amounts(
        metric.cumulative_base_amount_raw.clone(),
        base_amount_raw,
    );
    metric.cumulative_quote_amount_raw = crate::trade_metric_update::add_raw_amounts(
        metric.cumulative_quote_amount_raw.clone(),
        quote_amount_raw,
    );
    if price_quote_per_base.is_some() {
        metric.last_price_quote_per_base = price_quote_per_base;
    }
    metric.updated_at = chrono::Utc::now();
}

/// Adds two optional raw integer amount strings.
pub(crate) fn add_raw_amounts(
    left: std::option::Option<std::string::String>,
    right: std::option::Option<std::string::String>,
) -> std::option::Option<std::string::String> {
    match (left, right) {
        (None, None) => return None,
        (Some(left), None) => return Some(left),
        (None, Some(right)) => return Some(right),
        (Some(left), Some(right)) => {
            let left_value_result = left.parse::<i128>();
            let left_value = match left_value_result {
                Ok(left_value) => left_value,
                Err(_) => return Some(left),
            };
            let right_value_result = right.parse::<i128>();
            let right_value = match right_value_result {
                Ok(right_value) => right_value,
                Err(_) => return Some(left),
            };
            return Some((left_value + right_value).to_string());
        },
    }
}

/// Computes quote/base price from raw amounts and token decimals.
pub(crate) fn compute_price_quote_per_base_from_raw_amounts_with_decimals(
    base_amount_raw: std::option::Option<&str>,
    quote_amount_raw: std::option::Option<&str>,
    base_decimals: std::option::Option<u8>,
    quote_decimals: std::option::Option<u8>,
) -> std::option::Option<f64> {
    let base_decimals = match base_decimals {
        Some(base_decimals) => base_decimals,
        None => return None,
    };
    let quote_decimals = match quote_decimals {
        Some(quote_decimals) => quote_decimals,
        None => return None,
    };
    let base_amount_raw = match base_amount_raw {
        Some(base_amount_raw) => base_amount_raw.trim(),
        None => return None,
    };
    let quote_amount_raw = match quote_amount_raw {
        Some(quote_amount_raw) => quote_amount_raw.trim(),
        None => return None,
    };
    if base_amount_raw.is_empty() || quote_amount_raw.is_empty() {
        return None;
    }
    let base_amount_result = base_amount_raw.parse::<f64>();
    let base_amount = match base_amount_result {
        Ok(base_amount) => base_amount,
        Err(_) => return None,
    };
    let quote_amount_result = quote_amount_raw.parse::<f64>();
    let quote_amount = match quote_amount_result {
        Ok(quote_amount) => quote_amount,
        Err(_) => return None,
    };
    if base_amount <= 0.0 || quote_amount <= 0.0 {
        return None;
    }
    let base_scale = 10_f64.powi(i32::from(base_decimals));
    let quote_scale = 10_f64.powi(i32::from(quote_decimals));
    if base_scale <= 0.0 || quote_scale <= 0.0 {
        return None;
    }
    let base_ui_amount = base_amount / base_scale;
    let quote_ui_amount = quote_amount / quote_scale;
    if base_ui_amount <= 0.0 || quote_ui_amount <= 0.0 {
        return None;
    }
    return Some(quote_ui_amount / base_ui_amount);
}

/// Computes quote/base price from raw amount strings without decimals.
pub(crate) fn compute_price_quote_per_base_from_raw_amounts(
    base_amount_raw: std::option::Option<&str>,
    quote_amount_raw: std::option::Option<&str>,
) -> std::option::Option<f64> {
    let base_amount_raw = match base_amount_raw {
        Some(base_amount_raw) => base_amount_raw.trim(),
        None => return None,
    };
    let quote_amount_raw = match quote_amount_raw {
        Some(quote_amount_raw) => quote_amount_raw.trim(),
        None => return None,
    };
    if base_amount_raw.is_empty() || quote_amount_raw.is_empty() {
        return None;
    }
    let base_amount_result = base_amount_raw.parse::<f64>();
    let base_amount = match base_amount_result {
        Ok(base_amount) => base_amount,
        Err(_) => return None,
    };
    let quote_amount_result = quote_amount_raw.parse::<f64>();
    let quote_amount = match quote_amount_result {
        Ok(quote_amount) => quote_amount,
        Err(_) => return None,
    };
    if base_amount <= 0.0 {
        return None;
    }
    return Some(quote_amount / base_amount);
}

#[cfg(test)]
mod tests {
    #[test]
    fn priced_trade_event_rejects_unpriced_values() {
        let result = super::is_priced_trade_event(None, Some("2500"), Some(2.5));
        assert!(!result);
        let result = super::is_priced_trade_event(Some("1000"), None, Some(2.5));
        assert!(!result);
        let result = super::is_priced_trade_event(Some("1000"), Some("2500"), None);
        assert!(!result);
        let result = super::is_priced_trade_event(Some("0"), Some("2500"), Some(2.5));
        assert!(!result);
        let result = super::is_priced_trade_event(Some("1000"), Some("0"), Some(2.5));
        assert!(!result);
        let result = super::is_priced_trade_event(Some("-1"), Some("2500"), Some(2.5));
        assert!(!result);
        let result = super::is_priced_trade_event(Some("1000"), Some("-1"), Some(2.5));
        assert!(!result);
        let result = super::is_priced_trade_event(Some("abc"), Some("2500"), Some(2.5));
        assert!(!result);
        let result = super::is_priced_trade_event(Some("1000"), Some("abc"), Some(2.5));
        assert!(!result);
        let result = super::is_priced_trade_event(Some("1000"), Some("2500"), Some(0.0));
        assert!(!result);

        let result = super::is_priced_trade_event(Some("1000"), Some("2500"), Some(f64::NAN));
        assert!(!result);
        let result = super::is_priced_trade_event(Some("1000"), Some("2500"), Some(2.5));
        assert!(result);
    }

    #[test]
    fn raw_amounts_are_added_when_both_are_valid() {
        let result = super::add_raw_amounts(Some("1000".to_string()), Some("2500".to_string()));
        assert_eq!(result, Some("3500".to_string()));
    }

    #[test]
    fn raw_amount_addition_keeps_left_when_right_is_invalid() {
        let result = super::add_raw_amounts(Some("1000".to_string()), Some("abc".to_string()));
        assert_eq!(result, Some("1000".to_string()));
    }

    #[test]
    fn price_with_decimals_is_computed() {
        let price = super::compute_price_quote_per_base_from_raw_amounts_with_decimals(
            Some("1000000"),
            Some("2500000000"),
            Some(6),
            Some(9),
        );
        assert_eq!(price, Some(2.5));
    }

    #[test]
    fn price_without_decimals_is_computed() {
        let price =
            super::compute_price_quote_per_base_from_raw_amounts(Some("1000"), Some("2500"));
        assert_eq!(price, Some(2.5));
    }

    #[test]
    fn overflowing_slot_is_ignored() {
        let slot = super::convert_slot_to_i64(Some(u64::MAX));
        assert_eq!(slot, None);
    }
}
