use dashmap::DashMap;
use std::sync::Arc;
use tracing::warn;

use super::{FeedSnapshot, MetricsStore};

/// Default CTF Exchange contract addresses on Polygon.
pub const CTF_EXCHANGE: &str = "0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e";
pub const NEG_RISK_CTF_EXCHANGE: &str = "0xc5d563a36ae78145c45a50134d48a1215220f80a";

/// A decoded pending trade from the mempool.
#[derive(Debug, Clone)]
pub struct PendingTrade {
    pub token_id: String,
    pub amount_usdc: f64,
    pub is_buy: bool,
}

/// Known function selectors for CTF exchange contracts.
/// fillOrder(Order,uint256) = 0xfe729aaf
/// fillOrders(Order[],uint256[]) = 0xd798eff6
const FILL_ORDER_SELECTOR: &str = "fe729aaf";
const FILL_ORDERS_SELECTOR: &str = "d798eff6";

/// Decode calldata from a CTF exchange transaction.
///
/// Extracts token_id, USDC amount, and buy/sell direction from ABI-encoded
/// `fillOrder` or `fillOrders` calldata.
///
/// Returns `None` for unrecognized selectors or malformed data.
pub fn decode_ctf_calldata(calldata: &str) -> Option<PendingTrade> {
    let hex = calldata.strip_prefix("0x").unwrap_or(calldata);

    // Need at least 4 bytes selector + some data
    if hex.len() < 8 + 64 {
        return None;
    }

    let selector = &hex[..8];

    match selector {
        FILL_ORDER_SELECTOR | FILL_ORDERS_SELECTOR => {
            // The Order struct in Polymarket CTF has fields:
            //   salt, maker, signer, taker, tokenId, makerAmount, takerAmount, ...
            // tokenId is at offset ~128 bytes (4th word in the tuple after struct offset)
            // makerAmount at offset ~160 bytes (5th word)
            // takerAmount at offset ~192 bytes (6th word)
            // side at offset ~224 bytes (7th word) -- 0=buy, 1=sell

            let data = &hex[8..];

            // For fillOrder, the data starts with an offset pointer to the Order struct
            // Skip the offset word (32 bytes = 64 hex chars) to get to struct data
            // Then: salt(32) + maker(32) + signer(32) + taker(32) + tokenId(32) + makerAmount(32) + takerAmount(32) + ...

            // We need enough data for offset + 7 words minimum
            if data.len() < 64 + 7 * 64 {
                return None;
            }

            // Parse offset to struct data
            let struct_start = 64; // skip the offset pointer word

            // tokenId is at word 4 (0-indexed) within the struct
            let token_id_start = struct_start + 4 * 64;
            let token_id_end = token_id_start + 64;
            if token_id_end > data.len() {
                return None;
            }
            let token_id_hex = &data[token_id_start..token_id_end];
            // Parse as decimal string (strip leading zeros)
            let token_id = u128::from_str_radix(token_id_hex.trim_start_matches('0'), 16)
                .unwrap_or(0);
            let token_id_str = token_id.to_string();

            // makerAmount at word 5 (USDC amount, 6 decimals)
            let maker_start = struct_start + 5 * 64;
            let maker_end = maker_start + 64;
            if maker_end > data.len() {
                return None;
            }
            let maker_hex = &data[maker_start..maker_end];
            let maker_raw = u128::from_str_radix(
                maker_hex.trim_start_matches('0'),
                16,
            )
            .unwrap_or(0);
            let amount_usdc = maker_raw as f64 / 1_000_000.0;

            // side at word 7 (0 = buy, 1 = sell)
            let side_start = struct_start + 7 * 64;
            let side_end = side_start + 64;
            let is_buy = if side_end <= data.len() {
                let side_hex = &data[side_start..side_end];
                let side_val = u64::from_str_radix(
                    side_hex.trim_start_matches('0'),
                    16,
                )
                .unwrap_or(0);
                side_val == 0
            } else {
                true // default to buy if data is short
            };

            Some(PendingTrade {
                token_id: token_id_str,
                amount_usdc,
                is_buy,
            })
        }
        _ => None, // Unknown selector
    }
}

/// Parse a pending block response and extract CTF trades.
pub fn parse_pending_block(body: &str, ctf_addresses: &[String]) -> Vec<PendingTrade> {
    let json: serde_json::Value = match serde_json::from_str(body) {
        Ok(v) => v,
        Err(_) => return Vec::new(),
    };

    let result = match json.get("result") {
        Some(v) => v,
        None => return Vec::new(),
    };

    let transactions = match result.get("transactions") {
        Some(serde_json::Value::Array(txs)) => txs,
        _ => return Vec::new(),
    };

    let mut trades = Vec::new();

    for tx in transactions {
        let to_addr = match tx.get("to").and_then(|v| v.as_str()) {
            Some(addr) => addr.to_lowercase(),
            None => continue,
        };

        // Check if this transaction is to a CTF contract
        let is_ctf = ctf_addresses.iter().any(|a| a.to_lowercase() == to_addr);
        if !is_ctf {
            continue;
        }

        let input = match tx.get("input").and_then(|v| v.as_str()) {
            Some(data) => data,
            None => continue,
        };

        if let Some(trade) = decode_ctf_calldata(input) {
            trades.push(trade);
        }
    }

    trades
}

/// Mempool watcher feed.
///
/// Polls `eth_getBlockByNumber("pending", true)` via JSON-RPC to observe
/// pending transactions to Polymarket's CTF exchange contracts. Extracts
/// trade information and publishes aggregate metrics as a FeedSnapshot.
///
/// Snapshot fields:
/// - `price`: number of pending trades
/// - `volume_24h`: total pending USDC volume
/// - `bid`: largest pending buy amount
/// - `ask`: largest pending sell amount
/// - `last_trade_size`: largest single trade amount
pub async fn run_mempool_feed(
    name: String,
    rpc_url: String,
    ctf_addresses: Vec<String>,
    interval_secs: f64,
    snapshots: Arc<DashMap<String, FeedSnapshot>>,
    metrics: MetricsStore,
    global_shutdown: Arc<tokio::sync::Notify>,
    feed_shutdown: Arc<tokio::sync::Notify>,
) {
    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(10))
        .connect_timeout(std::time::Duration::from_secs(5))
        .build()
        .unwrap_or_else(|_| reqwest::Client::new());

    let clamped = clamp_interval(interval_secs);
    let interval = tokio::time::Duration::from_secs_f64(clamped);

    let source = format!("mempool:{}", name);

    if let Some(mut m) = metrics.get_mut(&name) {
        m.is_connected = true;
    }

    loop {
        tokio::select! {
            _ = global_shutdown.notified() => break,
            _ = feed_shutdown.notified() => break,
            _ = tokio::time::sleep(interval) => {
                let payload = serde_json::json!({
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "eth_getBlockByNumber",
                    "params": ["pending", true]
                });

                match client.post(&rpc_url).json(&payload).send().await {
                    Ok(resp) => {
                        if !resp.status().is_success() {
                            warn!(feed = %name, status = %resp.status(), "Mempool HTTP error");
                            if let Some(mut m) = metrics.get_mut(&name) {
                                m.error_count += 1;
                                m.last_error = format!("HTTP {}", resp.status());
                            }
                            continue;
                        }

                        match resp.text().await {
                            Ok(body) => {
                                let trades = parse_pending_block(&body, &ctf_addresses);

                                let trade_count = trades.len() as f64;
                                let total_usdc: f64 = trades.iter().map(|t| t.amount_usdc).sum();
                                let largest_buy = trades.iter()
                                    .filter(|t| t.is_buy)
                                    .map(|t| t.amount_usdc)
                                    .fold(0.0_f64, f64::max);
                                let largest_sell = trades.iter()
                                    .filter(|t| !t.is_buy)
                                    .map(|t| t.amount_usdc)
                                    .fold(0.0_f64, f64::max);
                                let largest_trade = trades.iter()
                                    .map(|t| t.amount_usdc)
                                    .fold(0.0_f64, f64::max);

                                let now = now_secs();
                                let snap = FeedSnapshot {
                                    price: trade_count,
                                    timestamp: now,
                                    source: source.clone(),
                                    bid: largest_buy,
                                    ask: largest_sell,
                                    volume_24h: total_usdc,
                                    last_trade_size: largest_trade,
                                    last_trade_is_buy: trades.last().map(|t| t.is_buy).unwrap_or(false),
                                };
                                snapshots.insert(name.clone(), snap);

                                if let Some(mut m) = metrics.get_mut(&name) {
                                    m.update_count += 1;
                                    m.last_update_time = now;
                                }
                            }
                            Err(e) => {
                                warn!(feed = %name, error = %e, "Mempool body read failed");
                                if let Some(mut m) = metrics.get_mut(&name) {
                                    m.error_count += 1;
                                    m.last_error = e.to_string();
                                }
                            }
                        }
                    }
                    Err(e) => {
                        warn!(feed = %name, error = %e, "Mempool request failed");
                        if let Some(mut m) = metrics.get_mut(&name) {
                            m.error_count += 1;
                            m.last_error = e.to_string();
                        }
                    }
                }
            }
        }
    }

    if let Some(mut m) = metrics.get_mut(&name) {
        m.is_connected = false;
    }
}

fn now_secs() -> f64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs_f64()
}

fn clamp_interval(interval: f64) -> f64 {
    if interval.is_nan() || interval.is_infinite() || interval <= 0.0 {
        2.0
    } else {
        interval.max(0.5)
    }
}

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

    #[test]
    fn test_decode_unknown_selector() {
        // Unknown function selector
        let calldata = "0xdeadbeef0000000000000000000000000000000000000000000000000000000000000000";
        assert!(decode_ctf_calldata(calldata).is_none());
    }

    #[test]
    fn test_decode_short_data() {
        // Too short to contain meaningful data
        assert!(decode_ctf_calldata("0xfe729aaf").is_none());
        assert!(decode_ctf_calldata("0x").is_none());
        assert!(decode_ctf_calldata("").is_none());
    }

    #[test]
    fn test_decode_fillorder_selector_recognized() {
        // Build minimal valid calldata for fillOrder
        // selector (8) + offset (64) + 8 words of struct data (8*64=512) = 584 chars
        let mut data = String::from("0xfe729aaf");
        // offset pointer
        data.push_str(&"0".repeat(64));
        // 8 words: salt, maker, signer, taker, tokenId, makerAmount, takerAmount, side
        // salt
        data.push_str(&"0".repeat(64));
        // maker
        data.push_str(&"0".repeat(64));
        // signer
        data.push_str(&"0".repeat(64));
        // taker
        data.push_str(&"0".repeat(64));
        // tokenId = 42
        data.push_str(&format!("{:064x}", 42u128));
        // makerAmount = 1000000 (1 USDC)
        data.push_str(&format!("{:064x}", 1_000_000u128));
        // takerAmount
        data.push_str(&"0".repeat(64));
        // side = 0 (buy)
        data.push_str(&format!("{:064x}", 0u64));

        let result = decode_ctf_calldata(&data);
        assert!(result.is_some());
        let trade = result.unwrap();
        assert_eq!(trade.token_id, "42");
        assert!((trade.amount_usdc - 1.0).abs() < 0.01);
        assert!(trade.is_buy);
    }

    #[test]
    fn test_decode_fillorder_sell() {
        let mut data = String::from("0xfe729aaf");
        data.push_str(&"0".repeat(64)); // offset
        data.push_str(&"0".repeat(64)); // salt
        data.push_str(&"0".repeat(64)); // maker
        data.push_str(&"0".repeat(64)); // signer
        data.push_str(&"0".repeat(64)); // taker
        data.push_str(&format!("{:064x}", 100u128)); // tokenId
        data.push_str(&format!("{:064x}", 5_000_000u128)); // 5 USDC
        data.push_str(&"0".repeat(64)); // takerAmount
        data.push_str(&format!("{:064x}", 1u64)); // side = 1 (sell)

        let trade = decode_ctf_calldata(&data).unwrap();
        assert_eq!(trade.token_id, "100");
        assert!((trade.amount_usdc - 5.0).abs() < 0.01);
        assert!(!trade.is_buy);
    }

    #[test]
    fn test_parse_pending_block_empty() {
        let body = r#"{"jsonrpc":"2.0","id":1,"result":{"transactions":[]}}"#;
        let trades = parse_pending_block(body, &[CTF_EXCHANGE.to_string()]);
        assert!(trades.is_empty());
    }

    #[test]
    fn test_parse_pending_block_no_result() {
        let body = r#"{"jsonrpc":"2.0","id":1}"#;
        let trades = parse_pending_block(body, &[CTF_EXCHANGE.to_string()]);
        assert!(trades.is_empty());
    }

    #[test]
    fn test_parse_pending_block_invalid_json() {
        let trades = parse_pending_block("not json", &[CTF_EXCHANGE.to_string()]);
        assert!(trades.is_empty());
    }

    #[test]
    fn test_parse_pending_block_filters_non_ctf() {
        // Transaction to a non-CTF address should be ignored
        let body = r#"{"jsonrpc":"2.0","id":1,"result":{"transactions":[{"to":"0x1234","input":"0xfe729aaf"}]}}"#;
        let trades = parse_pending_block(body, &[CTF_EXCHANGE.to_string()]);
        assert!(trades.is_empty());
    }

    #[test]
    fn test_clamp_interval() {
        assert!((clamp_interval(5.0) - 5.0).abs() < 1e-6);
        assert!((clamp_interval(0.1) - 0.5).abs() < 1e-6);
        assert!((clamp_interval(f64::NAN) - 2.0).abs() < 1e-6);
        assert!((clamp_interval(-1.0) - 2.0).abs() < 1e-6);
        assert!((clamp_interval(0.0) - 2.0).abs() < 1e-6);
    }

    #[test]
    fn test_default_addresses() {
        assert_eq!(CTF_EXCHANGE, "0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e");
        assert_eq!(NEG_RISK_CTF_EXCHANGE, "0xc5d563a36ae78145c45a50134d48a1215220f80a");
    }
}
