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

use super::{FeedSnapshot, MetricsStore};

/// Chainlink on-chain price oracle feed.
///
/// Polls a Chainlink aggregator proxy contract via `eth_call` (JSON-RPC)
/// to read `latestRoundData()`. Works with any EVM chain (Ethereum, Arbitrum,
/// Polygon, BSC) — just point `rpc_url` at the appropriate RPC endpoint.
pub async fn run_chainlink_feed(
    name: String,
    contract_address: String,
    rpc_url: String,
    decimals: u8,
    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);

    // latestRoundData() selector = 0xfeaf968c
    let call_data = "0xfeaf968c";
    // Normalize contract address to lowercase with 0x prefix
    let addr = if contract_address.starts_with("0x") || contract_address.starts_with("0X") {
        contract_address.to_lowercase()
    } else {
        format!("0x{}", contract_address.to_lowercase())
    };

    let source = format!("chainlink:{}", addr);

    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_call",
                    "params": [
                        {
                            "to": addr,
                            "data": call_data,
                        },
                        "latest"
                    ]
                });

                match client.post(&rpc_url).json(&payload).send().await {
                    Ok(resp) => {
                        if !resp.status().is_success() {
                            warn!(feed = %name, status = %resp.status(), "Chainlink 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) => {
                                match parse_rpc_response(&body, decimals) {
                                    Ok((price, updated_at)) => {
                                        let snap = FeedSnapshot {
                                            price,
                                            timestamp: updated_at,
                                            source: source.clone(),
                                            bid: 0.0,
                                            ask: 0.0,
                                            volume_24h: 0.0,
                                            last_trade_size: 0.0,
                                            last_trade_is_buy: false,
                                        };
                                        snapshots.insert(name.clone(), snap);
                                        if let Some(mut m) = metrics.get_mut(&name) {
                                            m.update_count += 1;
                                            m.last_update_time = now_secs();
                                        }
                                    }
                                    Err(e) => {
                                        warn!(feed = %name, error = %e, "Chainlink parse failed");
                                        if let Some(mut m) = metrics.get_mut(&name) {
                                            m.error_count += 1;
                                            m.last_error = e;
                                        }
                                    }
                                }
                            }
                            Err(e) => {
                                warn!(feed = %name, error = %e, "Chainlink 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, "Chainlink 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;
    }
}

/// Parse the JSON-RPC response envelope and decode ABI data.
fn parse_rpc_response(body: &str, decimals: u8) -> Result<(f64, f64), String> {
    let json: serde_json::Value =
        serde_json::from_str(body).map_err(|e| format!("invalid JSON: {}", e))?;

    // Check for RPC error
    if let Some(err) = json.get("error") {
        let msg = err
            .get("message")
            .and_then(|v| v.as_str())
            .unwrap_or("unknown RPC error");
        return Err(format!("RPC error: {}", msg));
    }

    let result = json
        .get("result")
        .and_then(|v| v.as_str())
        .ok_or_else(|| "missing 'result' field".to_string())?;

    parse_latest_round_data(result, decimals)
        .ok_or_else(|| "failed to decode latestRoundData".to_string())
}

/// Decode the ABI-encoded response from `latestRoundData()`.
///
/// Returns: `(roundId, answer, startedAt, updatedAt, answeredInRound)`
/// Each field is a 32-byte (64 hex char) word. We only need `answer` (word 1)
/// and `updatedAt` (word 3).
///
/// The `answer` is a signed int256 representing the price scaled by `10^decimals`.
fn parse_latest_round_data(hex: &str, decimals: u8) -> Option<(f64, f64)> {
    let hex = hex.strip_prefix("0x").unwrap_or(hex);

    // latestRoundData returns 5 words = 5 * 64 = 320 hex chars
    if hex.len() < 320 {
        return None;
    }

    // Word 1 (offset 64..128): answer (int256, signed)
    let answer_hex = &hex[64..128];
    let answer_raw = u128::from_str_radix(&answer_hex[32..], 16).ok()?;
    // Check sign bit (high nibble >= 8 means negative in two's complement)
    let first_nibble = u8::from_str_radix(&answer_hex[0..1], 16).unwrap_or(0);
    let is_negative = first_nibble >= 8;
    let divisor = 10_f64.powi(decimals as i32);
    let price = if is_negative {
        // Two's complement for the lower 128 bits — negative prices shouldn't happen
        // for Chainlink feeds, but handle gracefully
        -((!answer_raw).wrapping_add(1) as f64) / divisor
    } else {
        answer_raw as f64 / divisor
    };

    // Word 3 (offset 192..256): updatedAt (uint256)
    let updated_hex = &hex[192..256];
    let updated_at = u64::from_str_radix(&updated_hex[48..], 16).ok()? as f64;

    Some((price, updated_at))
}

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 {
        10.0
    } else {
        interval.max(1.0)
    }
}

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

    /// Build a fake ABI-encoded latestRoundData response.
    /// 5 words, each 32 bytes (64 hex chars):
    ///   word0 = roundId, word1 = answer, word2 = startedAt,
    ///   word3 = updatedAt, word4 = answeredInRound
    fn make_hex(answer: i128, updated_at: u64) -> String {
        let round_id: u128 = 42;
        let started_at: u128 = updated_at as u128;
        let answered_in_round: u128 = 42;

        // For positive answers, just zero-pad. For negative, use two's complement.
        let answer_word = if answer >= 0 {
            format!("{:064x}", answer as u128)
        } else {
            // Two's complement: flip bits and add 1 for 256-bit representation
            let pos = (-answer) as u128;
            let low = !pos + 1; // lower 128 bits
            // Upper 128 bits are all 0xf...f for negative
            format!("{:032x}{:032x}", u128::MAX, low)
        };

        format!(
            "0x{:064x}{}{:064x}{:064x}{:064x}",
            round_id, answer_word, started_at, updated_at as u128, answered_in_round,
        )
    }

    #[test]
    fn test_parse_eth_btc_price() {
        // ETH/USD: $3,245.12345678 with 8 decimals = 324512345678
        let hex = make_hex(324_512_345_678, 1700000000);
        let (price, ts) = parse_latest_round_data(&hex, 8).unwrap();
        assert!((price - 3245.12345678).abs() < 1e-6);
        assert!((ts - 1700000000.0).abs() < 1e-6);
    }

    #[test]
    fn test_parse_btc_usd_price() {
        // BTC/USD: $42,000.00 with 8 decimals = 4_200_000_000_000
        let hex = make_hex(4_200_000_000_000, 1700000000);
        let (price, ts) = parse_latest_round_data(&hex, 8).unwrap();
        assert!((price - 42000.0).abs() < 1e-6);
        assert!((ts - 1700000000.0).abs() < 1e-6);
    }

    #[test]
    fn test_parse_usdc_price() {
        // USDC/USD: $1.0001 with 8 decimals = 100_010_000
        let hex = make_hex(100_010_000, 1700000000);
        let (price, _) = parse_latest_round_data(&hex, 8).unwrap();
        assert!((price - 1.0001).abs() < 1e-6);
    }

    #[test]
    fn test_parse_18_decimals() {
        // Some feeds use 18 decimals: 1.5e18 = 1_500_000_000_000_000_000
        let hex = make_hex(1_500_000_000_000_000_000, 1700000000);
        let (price, _) = parse_latest_round_data(&hex, 18).unwrap();
        assert!((price - 1.5).abs() < 1e-6);
    }

    #[test]
    fn test_parse_too_short() {
        let result = parse_latest_round_data("0xdeadbeef", 8);
        assert!(result.is_none());
    }

    #[test]
    fn test_parse_no_prefix() {
        // Without 0x prefix
        let hex = make_hex(100_000_000, 1700000000);
        let no_prefix = hex.strip_prefix("0x").unwrap();
        let (price, _) = parse_latest_round_data(no_prefix, 8).unwrap();
        assert!((price - 1.0).abs() < 1e-6);
    }

    #[test]
    fn test_parse_rpc_response_ok() {
        let hex = make_hex(4_200_000_000_000, 1700000000);
        let body = format!(r#"{{"jsonrpc":"2.0","id":1,"result":"{}"}}"#, hex);
        let (price, ts) = parse_rpc_response(&body, 8).unwrap();
        assert!((price - 42000.0).abs() < 1e-6);
        assert!((ts - 1700000000.0).abs() < 1e-6);
    }

    #[test]
    fn test_parse_rpc_response_error() {
        let body = r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"execution reverted"}}"#;
        let result = parse_rpc_response(body, 8);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("execution reverted"));
    }

    #[test]
    fn test_parse_rpc_response_invalid_json() {
        let result = parse_rpc_response("not json", 8);
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_rpc_response_missing_result() {
        let body = r#"{"jsonrpc":"2.0","id":1}"#;
        let result = parse_rpc_response(body, 8);
        assert!(result.is_err());
    }

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