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

use super::{FeedSnapshot, MetricsStore};

/// Enhanced REST feed with JSON path extraction.
///
/// Uses dot-notation paths to map any JSON field to price/bid/ask/volume:
///   `"data.markets.0.lastPrice"` -> `json["data"]["markets"][0]["lastPrice"]`
///
/// Numeric segments try array index first, then object key.
/// String values are auto-parsed to f64.
pub async fn run_rest_json_path_feed(
    name: String,
    url: String,
    price_path: Option<String>,
    bid_path: Option<String>,
    ask_path: Option<String>,
    volume_path: Option<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);

    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) => {
                match client.get(&url).send().await {
                    Ok(resp) => {
                        if !resp.status().is_success() {
                            warn!(feed = %name, status = %resp.status(), "REST+JsonPath 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 serde_json::from_str::<serde_json::Value>(&body) {
                                    Ok(json) => {
                                        let price = resolve_field(
                                            &json,
                                            price_path.as_deref(),
                                            "price",
                                        );
                                        let bid = resolve_field(
                                            &json,
                                            bid_path.as_deref(),
                                            "bid",
                                        );
                                        let ask = resolve_field(
                                            &json,
                                            ask_path.as_deref(),
                                            "ask",
                                        );
                                        let volume = resolve_field(
                                            &json,
                                            volume_path.as_deref(),
                                            "volume",
                                        );

                                        let snap = FeedSnapshot {
                                            price,
                                            timestamp: now_secs(),
                                            source: name.clone(),
                                            bid,
                                            ask,
                                            volume_24h: volume,
                                            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, "REST+JsonPath parse error");
                                        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, "REST+JsonPath 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, "REST+JsonPath 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;
    }
}

/// Resolve a field value: use the custom path if provided, else fall back to the default key.
fn resolve_field(json: &serde_json::Value, custom_path: Option<&str>, default_key: &str) -> f64 {
    if let Some(path) = custom_path {
        resolve_json_path(json, path).unwrap_or(0.0)
    } else {
        json.get(default_key)
            .and_then(value_to_f64)
            .unwrap_or(0.0)
    }
}

/// Resolve a dot-notation JSON path to an f64 value.
///
/// Path segments are separated by `.`. Numeric segments try array index first,
/// then object key. Terminal string values are auto-parsed to f64.
///
/// Examples:
///   - `"price"` -> `json["price"]`
///   - `"data.markets.0.lastPrice"` -> `json["data"]["markets"][0]["lastPrice"]`
///   - `"bitcoin.usd"` -> `json["bitcoin"]["usd"]`
pub fn resolve_json_path(json: &serde_json::Value, path: &str) -> Option<f64> {
    if path.is_empty() {
        return value_to_f64(json);
    }

    let mut current = json;

    for segment in path.split('.') {
        if segment.is_empty() {
            continue;
        }

        // Try as array index first (if segment is a number)
        if let Ok(idx) = segment.parse::<usize>() {
            if let Some(val) = current.as_array().and_then(|arr| arr.get(idx)) {
                current = val;
                continue;
            }
        }

        // Fall back to object key
        if let Some(val) = current.get(segment) {
            current = val;
        } else {
            return None;
        }
    }

    value_to_f64(current)
}

/// Convert a JSON value to f64, supporting numbers and string-encoded numbers.
fn value_to_f64(v: &serde_json::Value) -> Option<f64> {
    match v {
        serde_json::Value::Number(n) => n.as_f64(),
        serde_json::Value::String(s) => s.parse::<f64>().ok(),
        _ => None,
    }
}

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

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

    // --- resolve_json_path tests ---

    #[test]
    fn test_simple_key() {
        let data = json!({"price": 42.5});
        assert!((resolve_json_path(&data, "price").unwrap() - 42.5).abs() < 1e-6);
    }

    #[test]
    fn test_nested_key() {
        let data = json!({"data": {"market": {"price": 0.65}}});
        assert!((resolve_json_path(&data, "data.market.price").unwrap() - 0.65).abs() < 1e-6);
    }

    #[test]
    fn test_array_index() {
        let data = json!({"markets": [{"price": 10.0}, {"price": 20.0}]});
        assert!((resolve_json_path(&data, "markets.1.price").unwrap() - 20.0).abs() < 1e-6);
    }

    #[test]
    fn test_deep_nesting() {
        let data = json!({"a": {"b": {"c": {"d": 99.9}}}});
        assert!((resolve_json_path(&data, "a.b.c.d").unwrap() - 99.9).abs() < 1e-6);
    }

    #[test]
    fn test_string_to_float() {
        let data = json!({"price": "42.5"});
        assert!((resolve_json_path(&data, "price").unwrap() - 42.5).abs() < 1e-6);
    }

    #[test]
    fn test_missing_key() {
        let data = json!({"price": 42.5});
        assert!(resolve_json_path(&data, "volume").is_none());
    }

    #[test]
    fn test_missing_nested() {
        let data = json!({"data": {"market": {}}});
        assert!(resolve_json_path(&data, "data.market.price").is_none());
    }

    #[test]
    fn test_array_out_of_bounds() {
        let data = json!({"items": [1, 2, 3]});
        assert!(resolve_json_path(&data, "items.5").is_none());
    }

    #[test]
    fn test_empty_path() {
        let data = json!(42.0);
        assert!((resolve_json_path(&data, "").unwrap() - 42.0).abs() < 1e-6);
    }

    #[test]
    fn test_non_numeric_value() {
        let data = json!({"name": "test"});
        assert!(resolve_json_path(&data, "name").is_none());
    }

    #[test]
    fn test_boolean_value() {
        let data = json!({"active": true});
        assert!(resolve_json_path(&data, "active").is_none());
    }

    #[test]
    fn test_null_value() {
        let data = json!({"price": null});
        assert!(resolve_json_path(&data, "price").is_none());
    }

    #[test]
    fn test_coingecko_pattern() {
        let data = json!({"bitcoin": {"usd": 65432.10}});
        assert!(
            (resolve_json_path(&data, "bitcoin.usd").unwrap() - 65432.10).abs() < 1e-6
        );
    }

    #[test]
    fn test_numeric_object_key() {
        // When key is numeric string but value is an object, not an array
        let data = json!({"100": {"price": 5.0}});
        assert!((resolve_json_path(&data, "100.price").unwrap() - 5.0).abs() < 1e-6);
    }

    // --- resolve_field tests ---

    #[test]
    fn test_resolve_field_with_custom_path() {
        let data = json!({"data": {"last": 42.0}});
        assert!((resolve_field(&data, Some("data.last"), "price") - 42.0).abs() < 1e-6);
    }

    #[test]
    fn test_resolve_field_default_key() {
        let data = json!({"price": 42.0, "bid": 41.0});
        assert!((resolve_field(&data, None, "price") - 42.0).abs() < 1e-6);
        assert!((resolve_field(&data, None, "bid") - 41.0).abs() < 1e-6);
    }

    #[test]
    fn test_resolve_field_missing() {
        let data = json!({"other": 1.0});
        assert!((resolve_field(&data, None, "price") - 0.0).abs() < 1e-6);
    }

    // --- value_to_f64 tests ---

    #[test]
    fn test_value_to_f64_number() {
        assert!((value_to_f64(&json!(42.5)).unwrap() - 42.5).abs() < 1e-6);
    }

    #[test]
    fn test_value_to_f64_integer() {
        assert!((value_to_f64(&json!(42)).unwrap() - 42.0).abs() < 1e-6);
    }

    #[test]
    fn test_value_to_f64_string() {
        assert!((value_to_f64(&json!("42.5")).unwrap() - 42.5).abs() < 1e-6);
    }

    #[test]
    fn test_value_to_f64_non_numeric_string() {
        assert!(value_to_f64(&json!("hello")).is_none());
    }

    #[test]
    fn test_value_to_f64_null() {
        assert!(value_to_f64(&serde_json::Value::Null).is_none());
    }

    #[test]
    fn test_value_to_f64_bool() {
        assert!(value_to_f64(&json!(true)).is_none());
    }

    // --- clamp_interval tests ---

    #[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) - 5.0).abs() < 1e-6);
        assert!((clamp_interval(-1.0) - 5.0).abs() < 1e-6);
    }
}
