//! MEV Sandwich Attack Simulator
//!
//! Detects sandwich attack risk for pending swaps by analyzing:
//! - Trade size relative to pool liquidity
//! - Current slippage tolerance
//! - Historical bot activity patterns
//!
//! # How Sandwich Attacks Work
//!
//! 1. Attacker sees victim's pending tx (e.g. buy 1 ETH worth of USDC)
//! 2. Attacker front-runs: buys USDC before victim → price goes up
//! 3. Victim's tx executes at worse price (MEV extracted)
//! 4. Attacker back-runs: sells USDC at inflated price → profit

use anyhow::Result;
use swap_kit_types::{SimulateRequest, SimulateResponse};

/// Simulate MEV exposure for a given swap.
///
/// Estimates sandwich attack risk based on trade parameters.
/// In production, this would connect to an Ethereum node and analyze
/// the mempool for pending transactions targeting the same pool.
pub async fn simulate(req: &SimulateRequest) -> Result<SimulateResponse> {
    // Parse amounts securely: reject malformed or overflowing inputs
    let from_amount: u128 = req
        .from_amount
        .parse()
        .map_err(|_| anyhow::anyhow!("Invalid from_amount: must be a positive integer within u128 bounds"))?;
        
    let amount_out: u128 = req
        .amount_out
        .parse()
        .map_err(|_| anyhow::anyhow!("Invalid amount_out: must be a positive integer within u128 bounds"))?;

    if from_amount == 0 {
        return Err(anyhow::anyhow!("from_amount must be greater than zero"));
    }

    let slippage_bps: u64 = req.slippage_bps as u64;

    // Classify risk based on trade size and slippage
    let is_mainnet = req.chain_id == 1;

    let sandwich_risk = classify_risk(from_amount, slippage_bps, is_mainnet);

    // MEV estimate: sandwich attacker can extract up to (slippage_bps / 10000) * amount_out
    // But typically extracts 60-80% of available slippage
    let mev_fraction = (slippage_bps * 70) / 10000; // 70% of slippage tolerance
    let estimated_mev = (amount_out as u128)
        .checked_mul(mev_fraction as u128)
        .map(|v| v / 10000)
        .unwrap_or(amount_out); // Cap at amount_out on overflow

    // Recommend reducing slippage if MEV risk is high
    let recommended_slippage = if sandwich_risk == "high" {
        // Reduce slippage to minimum viable — makes sandwich unprofitable
        std::cmp::min(slippage_bps as u32, 30)
    } else if sandwich_risk == "medium" {
        std::cmp::min(slippage_bps as u32, 50)
    } else {
        slippage_bps as u32
    };

    Ok(SimulateResponse {
        sandwich_risk: sandwich_risk.to_string(),
        estimated_mev_wei: estimated_mev.to_string(),
        recommended_slippage_bps: recommended_slippage,
        detected_bots: vec![], // In production: scan recent blocks for known bot patterns
    })
}

/// Returns a safe default response when simulation fails.
/// Returns "unknown" risk to honestly signal the simulation could not complete,
/// rather than misleadingly reporting "low" risk.
pub fn safe_default() -> SimulateResponse {
    SimulateResponse {
        sandwich_risk: "unknown".to_string(),
        estimated_mev_wei: "0".to_string(),
        recommended_slippage_bps: 30, // Conservative default when we can't analyze
        detected_bots: vec![],
    }
}

/// Classify sandwich risk level using integer arithmetic.
///
/// Avoids f64 precision loss (H-8) by comparing `from_amount` directly
/// against ETH-denominated thresholds expressed in wei.
fn classify_risk(from_amount: u128, slippage_bps: u64, is_mainnet: bool) -> &'static str {
    // Thresholds in wei
    let ten_eth: u128 = 10_000_000_000_000_000_000;  // 10 ETH
    let one_eth: u128 = 1_000_000_000_000_000_000;   // 1 ETH

    let base_score: u64 = if from_amount > ten_eth {
        if slippage_bps > 100 { 3 } else { 2 }
    } else if from_amount > one_eth {
        if slippage_bps > 50 { 2 } else { 1 }
    } else {
        if slippage_bps > 200 { 1 } else { 0 }
    };

    let effective = if is_mainnet { base_score } else { base_score / 2 };

    match effective {
        3.. => "high",
        2 => "medium",
        1 => "low",
        _ => "none",
    }
}

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

    #[tokio::test]
    async fn test_simulate_low_risk() {
        let req = SimulateRequest {
            from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
            to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
            from_amount: "100000000000000000".to_string(), // 0.1 ETH
            chain_id: 1,
            protocol: "uniswap-v4".to_string(),
            amount_out: "200000000".to_string(), // 200 USDC
            slippage_bps: 50,
        };

        let result = simulate(&req).await.unwrap();
        assert!(result.sandwich_risk == "none" || result.sandwich_risk == "low");
    }

    #[tokio::test]
    async fn test_simulate_high_risk() {
        let req = SimulateRequest {
            from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
            to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
            from_amount: "100000000000000000000".to_string(), // 100 ETH
            chain_id: 1,
            protocol: "uniswap-v4".to_string(),
            amount_out: "200000000000".to_string(), // 200k USDC
            slippage_bps: 200,
        };

        let result = simulate(&req).await.unwrap();
        assert_eq!(result.sandwich_risk, "high");
        assert!(result.recommended_slippage_bps <= 30);
    }

    #[test]
    fn test_safe_default() {
        let result = safe_default();
        assert_eq!(result.sandwich_risk, "unknown");
        assert_eq!(result.estimated_mev_wei, "0");
        assert_eq!(result.recommended_slippage_bps, 30);
    }

    #[tokio::test]
    async fn test_simulate_negative_amount() {
        let req = SimulateRequest {
            from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
            to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
            from_amount: "-100000000000000000".to_string(), // Negative
            chain_id: 1,
            protocol: "uniswap-v4".to_string(),
            amount_out: "200000000".to_string(),
            slippage_bps: 50,
        };
        let result = simulate(&req).await;
        // Negative amount fails parsing
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_simulate_massive_amount() {
        let req = SimulateRequest {
            from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
            to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
            from_amount: "99999999999999999999999999999999999999999999999999999999999".to_string(), // Exceeds u128::MAX
            chain_id: 1,
            protocol: "uniswap-v4".to_string(),
            amount_out: "200000000".to_string(),
            slippage_bps: 50,
        };
        let result = simulate(&req).await;
        // Exceeds u128::MAX fails parsing
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_simulate_invalid_type_amount() {
        let req = SimulateRequest {
            from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
            to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
            from_amount: "invalid_amount".to_string(),
            chain_id: 1,
            protocol: "uniswap-v4".to_string(),
            amount_out: "invalid_out".to_string(),
            slippage_bps: 50,
        };
        let result = simulate(&req).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_simulate_zero_amount() {
        let req = SimulateRequest {
            from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
            to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
            from_amount: "0".to_string(),
            chain_id: 1,
            protocol: "uniswap-v4".to_string(),
            amount_out: "200000000".to_string(),
            slippage_bps: 50,
        };
        let result = simulate(&req).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_simulate_overflow_mev() {
        let req = SimulateRequest {
            from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
            to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
            from_amount: "100000000000000000000".to_string(),
            chain_id: 1,
            protocol: "uniswap-v4".to_string(),
            amount_out: "340282366920938463463374607431768211455".to_string(), // u128::MAX
            slippage_bps: 2000,
        };
        let result = simulate(&req).await.unwrap();
        // checked_mul overflows → capped at amount_out
        assert_eq!(result.estimated_mev_wei, u128::MAX.to_string());
    }
}
