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

use super::{FeedSnapshot, MetricsStore};

/// Manifold Markets REST polling feed.
///
/// Polls `https://api.manifold.markets/v0/slug/{slug}` and maps:
///   - `probability` -> price
///   - `volume` -> volume_24h
pub async fn run_manifold_feed(
    name: String,
    slug: String,
    interval_secs: f64,
    snapshots: Arc<DashMap<String, FeedSnapshot>>,
    metrics: MetricsStore,
    global_shutdown: Arc<tokio::sync::Notify>,
    feed_shutdown: Arc<tokio::sync::Notify>,
) {
    // Validate slug contains only safe URL path characters
    if slug.is_empty() || !slug.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-') {
        warn!(feed = %name, slug = %slug, "Manifold feed: invalid slug, must match [a-z0-9-]");
        return;
    }

    let url = format!("https://api.manifold.markets/v0/slug/{}", slug);
    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(), "Manifold 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) => {
                                if let Some(snap) = parse_manifold_response(&body, &slug) {
                                    snapshots.insert(name.clone(), snap);
                                    if let Some(mut m) = metrics.get_mut(&name) {
                                        m.update_count += 1;
                                        m.last_update_time = now_secs();
                                    }
                                } else {
                                    if let Some(mut m) = metrics.get_mut(&name) {
                                        m.error_count += 1;
                                        m.last_error = "parse failed".to_string();
                                    }
                                }
                            }
                            Err(e) => {
                                warn!(feed = %name, error = %e, "Manifold 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, "Manifold 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 parse_manifold_response(body: &str, slug: &str) -> Option<FeedSnapshot> {
    let json: serde_json::Value = serde_json::from_str(body).ok()?;

    let price = json.get("probability").and_then(|v| v.as_f64())?;
    if !price.is_finite() || price < 0.0 || price > 1.0 {
        return None;
    }
    let volume = json
        .get("volume")
        .and_then(|v| v.as_f64())
        .unwrap_or(0.0);

    Some(FeedSnapshot {
        price,
        timestamp: now_secs(),
        source: format!("manifold:{}", slug),
        bid: 0.0,
        ask: 0.0,
        volume_24h: volume,
        last_trade_size: 0.0,
        last_trade_is_buy: 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 {
        5.0
    } else {
        interval.max(1.0)
    }
}

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

    #[test]
    fn test_parse_manifold_basic() {
        let json = r#"{
            "id": "abc123",
            "question": "Will BTC hit 100k?",
            "probability": 0.72,
            "volume": 12345.0
        }"#;

        let snap = parse_manifold_response(json, "will-btc-hit-100k").unwrap();
        assert!((snap.price - 0.72).abs() < 1e-6);
        assert!((snap.volume_24h - 12345.0).abs() < 1e-6);
        assert_eq!(snap.source, "manifold:will-btc-hit-100k");
    }

    #[test]
    fn test_parse_manifold_no_volume() {
        let json = r#"{"probability": 0.50}"#;

        let snap = parse_manifold_response(json, "test").unwrap();
        assert!((snap.price - 0.50).abs() < 1e-6);
        assert!((snap.volume_24h - 0.0).abs() < 1e-6);
    }

    #[test]
    fn test_parse_manifold_missing_probability() {
        let json = r#"{"volume": 100.0}"#;
        let result = parse_manifold_response(json, "test");
        assert!(result.is_none());
    }

    #[test]
    fn test_parse_manifold_invalid_json() {
        let result = parse_manifold_response("not json", "test");
        assert!(result.is_none());
    }

    #[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);
    }
}
