//! Treasury yield curve feed via FRED API.
//!
//! Polls the Federal Reserve Economic Data (FRED) API for yield curve data.
//! Maps the primary series to FeedSnapshot (price=yield, bid=short_term, ask=long_term,
//! volume_24h=slope_bps).

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

use super::{FeedSnapshot, MetricsStore};

/// Treasury yield curve FRED feed.
///
/// Polls `https://api.stlouisfed.org/fred/series/observations` for yield data.
pub async fn run_treasury_feed(
    name: String,
    api_key: String,
    series_ids: Vec<String>,
    primary_series: 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(15))
        .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 fetch_yields(&client, &api_key, &series_ids).await {
                    Ok(yields) => {
                        if let Some(snap) = build_snapshot(&name, &yields, &primary_series, &series_ids) {
                            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, "Treasury feed poll failed");
                        if let Some(mut m) = metrics.get_mut(&name) {
                            m.error_count += 1;
                            m.last_error = e;
                        }
                    }
                }
            }
        }
    }

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

async fn fetch_yields(
    client: &reqwest::Client,
    api_key: &str,
    series_ids: &[String],
) -> Result<Vec<(String, f64)>, String> {
    let mut yields = Vec::new();

    for series in series_ids {
        let url = format!(
            "https://api.stlouisfed.org/fred/series/observations?series_id={}&api_key={}&file_type=json&sort_order=desc&limit=1",
            series, api_key
        );
        match client.get(&url).send().await {
            Ok(resp) => {
                if !resp.status().is_success() {
                    continue;
                }
                if let Ok(body) = resp.text().await {
                    if let Some(val) = parse_fred_value(&body) {
                        yields.push((series.clone(), val));
                    }
                }
            }
            Err(_) => continue,
        }
    }

    if yields.is_empty() {
        return Err("no yield data retrieved".to_string());
    }
    Ok(yields)
}

fn parse_fred_value(body: &str) -> Option<f64> {
    let json: serde_json::Value = serde_json::from_str(body).ok()?;
    let obs = json.get("observations")?.as_array()?;
    let first = obs.first()?;
    let val_str = first.get("value")?.as_str()?;
    val_str.parse::<f64>().ok().filter(|v| v.is_finite())
}

fn build_snapshot(
    name: &str,
    yields: &[(String, f64)],
    primary_series: &str,
    series_ids: &[String],
) -> Option<FeedSnapshot> {
    let primary_yield = yields
        .iter()
        .find(|(s, _)| s == primary_series)
        .map(|(_, v)| *v)
        .or_else(|| yields.first().map(|(_, v)| *v))?;

    // Short-term = first series yield, long-term = last series yield
    let short_term = yields.first().map(|(_, v)| *v).unwrap_or(0.0);
    let long_term = yields.last().map(|(_, v)| *v).unwrap_or(0.0);
    let slope_bps = (long_term - short_term) * 100.0; // basis points

    Some(FeedSnapshot {
        price: primary_yield / 100.0, // convert percentage to decimal
        timestamp: now_secs(),
        source: format!("treasury:{}", primary_series),
        bid: short_term / 100.0,
        ask: long_term / 100.0,
        volume_24h: slope_bps,
        last_trade_size: 0.0,
        last_trade_is_buy: slope_bps > 0.0,
    })
}

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 {
        300.0 // 5 minutes default for FRED
    } else {
        interval.max(60.0) // minimum 1 minute
    }
}

/// Default FRED series IDs for the US Treasury yield curve.
pub fn default_series_ids() -> Vec<String> {
    vec![
        "DGS1MO".to_string(),
        "DGS3MO".to_string(),
        "DGS6MO".to_string(),
        "DGS1".to_string(),
        "DGS2".to_string(),
        "DGS5".to_string(),
        "DGS10".to_string(),
        "DGS20".to_string(),
        "DGS30".to_string(),
    ]
}

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

    #[test]
    fn test_parse_fred_value_valid() {
        let json = r#"{"observations": [{"date": "2024-01-15", "value": "4.25"}]}"#;
        let val = parse_fred_value(json);
        assert!((val.unwrap() - 4.25).abs() < 1e-6);
    }

    #[test]
    fn test_parse_fred_value_dot() {
        let json = r#"{"observations": [{"date": "2024-01-15", "value": "."}]}"#;
        assert!(parse_fred_value(json).is_none());
    }

    #[test]
    fn test_parse_fred_value_empty() {
        let json = r#"{"observations": []}"#;
        assert!(parse_fred_value(json).is_none());
    }

    #[test]
    fn test_parse_fred_value_invalid_json() {
        assert!(parse_fred_value("not json").is_none());
    }

    #[test]
    fn test_build_snapshot_basic() {
        let yields = vec![
            ("DGS1MO".to_string(), 5.25),
            ("DGS10".to_string(), 4.50),
            ("DGS30".to_string(), 4.75),
        ];
        let series_ids = vec![
            "DGS1MO".to_string(),
            "DGS10".to_string(),
            "DGS30".to_string(),
        ];
        let snap = build_snapshot("test", &yields, "DGS10", &series_ids).unwrap();
        assert!((snap.price - 0.045).abs() < 1e-6); // 4.50% -> 0.045
        assert!((snap.bid - 0.0525).abs() < 1e-6); // short-term
        assert!((snap.ask - 0.0475).abs() < 1e-6); // long-term
    }

    #[test]
    fn test_build_snapshot_inverted_curve() {
        let yields = vec![
            ("DGS1MO".to_string(), 5.50),
            ("DGS30".to_string(), 4.00),
        ];
        let series_ids = vec!["DGS1MO".to_string(), "DGS30".to_string()];
        let snap = build_snapshot("test", &yields, "DGS10", &series_ids).unwrap();
        assert!(snap.volume_24h < 0.0); // inverted slope
        assert!(!snap.last_trade_is_buy);
    }

    #[test]
    fn test_build_snapshot_empty_yields() {
        let yields: Vec<(String, f64)> = vec![];
        let series_ids = vec!["DGS10".to_string()];
        assert!(build_snapshot("test", &yields, "DGS10", &series_ids).is_none());
    }

    #[test]
    fn test_clamp_interval() {
        assert!((clamp_interval(600.0) - 600.0).abs() < 1e-6);
        assert!((clamp_interval(30.0) - 60.0).abs() < 1e-6); // min 60s
        assert!((clamp_interval(f64::NAN) - 300.0).abs() < 1e-6);
        assert!((clamp_interval(-1.0) - 300.0).abs() < 1e-6);
    }

    #[test]
    fn test_default_series_ids() {
        let ids = default_series_ids();
        assert_eq!(ids.len(), 9);
        assert_eq!(ids[0], "DGS1MO");
        assert_eq!(ids[8], "DGS30");
    }
}
