//! Calendar/event feed for economic events and earnings dates.
//!
//! Parses bundled or API-provided event data, maps nearest event to FeedSnapshot
//! (bid=seconds_to_event, ask=event_type_code, source="calendar:event_name").

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

use super::{FeedSnapshot, MetricsStore};

/// Event type codes for the ask field.
const EVENT_FOMC: f64 = 1.0;
const EVENT_CPI: f64 = 2.0;
const EVENT_NFP: f64 = 3.0;
const EVENT_GDP: f64 = 4.0;
const EVENT_EARNINGS: f64 = 5.0;
const EVENT_PPI: f64 = 6.0;
const EVENT_PCE: f64 = 7.0;
const EVENT_OTHER: f64 = 99.0;

/// Calendar event feed.
///
/// Polls event data and maps the nearest event to FeedSnapshot fields.
pub async fn run_calendar_feed(
    name: String,
    events_json: String,
    api_url: 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);

    // Parse bundled events
    let mut events = parse_events_json(&events_json);

    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) => {
                // Optionally refresh from API
                if !api_url.is_empty() {
                    if let Ok(new_events) = fetch_events_api(&client, &api_url).await {
                        if !new_events.is_empty() {
                            events = new_events;
                        }
                    }
                }

                let now = now_secs();
                if let Some(snap) = find_nearest_event(&name, &events, now) {
                    snapshots.insert(name.clone(), snap);
                    if let Some(mut m) = metrics.get_mut(&name) {
                        m.update_count += 1;
                        m.last_update_time = now;
                    }
                }
            }
        }
    }

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

#[derive(Debug, Clone)]
struct CalendarEvent {
    name: String,
    timestamp: f64,
    event_type: String,
}

fn event_type_code(event_type: &str) -> f64 {
    match event_type.to_lowercase().as_str() {
        "fomc" => EVENT_FOMC,
        "cpi" => EVENT_CPI,
        "nfp" | "jobs" => EVENT_NFP,
        "gdp" => EVENT_GDP,
        "earnings" => EVENT_EARNINGS,
        "ppi" => EVENT_PPI,
        "pce" => EVENT_PCE,
        _ => EVENT_OTHER,
    }
}

fn parse_events_json(json_str: &str) -> Vec<CalendarEvent> {
    let json: serde_json::Value = match serde_json::from_str(json_str) {
        Ok(v) => v,
        Err(_) => return Vec::new(),
    };

    let arr = match json.as_array() {
        Some(a) => a,
        None => return Vec::new(),
    };

    arr.iter()
        .filter_map(|item| {
            let name = item.get("name")?.as_str()?.to_string();
            let timestamp = item.get("timestamp")?.as_f64()?;
            let event_type = item
                .get("type")
                .and_then(|v| v.as_str())
                .unwrap_or("other")
                .to_string();
            Some(CalendarEvent {
                name,
                timestamp,
                event_type,
            })
        })
        .collect()
}

async fn fetch_events_api(
    client: &reqwest::Client,
    url: &str,
) -> Result<Vec<CalendarEvent>, String> {
    let resp = client
        .get(url)
        .send()
        .await
        .map_err(|e| e.to_string())?;
    if !resp.status().is_success() {
        return Err(format!("HTTP {}", resp.status()));
    }
    let body = resp.text().await.map_err(|e| e.to_string())?;
    Ok(parse_events_json(&body))
}

fn find_nearest_event(
    feed_name: &str,
    events: &[CalendarEvent],
    now: f64,
) -> Option<FeedSnapshot> {
    // Find the nearest future event
    let nearest = events
        .iter()
        .filter(|e| e.timestamp > now)
        .min_by(|a, b| a.timestamp.partial_cmp(&b.timestamp).unwrap_or(std::cmp::Ordering::Equal));

    let event = nearest?;
    let seconds_to_event = event.timestamp - now;
    let type_code = event_type_code(&event.event_type);

    // Normalize seconds_to_event to days for price field (0-1 scale for 30 days)
    let days_to_event = seconds_to_event / 86400.0;
    let normalized = (1.0 - (days_to_event / 30.0).min(1.0)).max(0.0);

    Some(FeedSnapshot {
        price: normalized, // urgency: 1.0 = imminent, 0.0 = 30+ days away
        timestamp: now,
        source: format!("calendar:{}", event.name),
        bid: seconds_to_event,
        ask: type_code,
        volume_24h: days_to_event,
        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 {
        300.0
    } else {
        interval.max(60.0)
    }
}

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

    #[test]
    fn test_parse_events_json_basic() {
        let json = r#"[
            {"name": "FOMC Decision", "timestamp": 1700000000.0, "type": "fomc"},
            {"name": "CPI Release", "timestamp": 1700100000.0, "type": "cpi"}
        ]"#;
        let events = parse_events_json(json);
        assert_eq!(events.len(), 2);
        assert_eq!(events[0].name, "FOMC Decision");
        assert_eq!(events[1].event_type, "cpi");
    }

    #[test]
    fn test_parse_events_json_empty() {
        assert!(parse_events_json("[]").is_empty());
        assert!(parse_events_json("not json").is_empty());
        assert!(parse_events_json("{}").is_empty());
    }

    #[test]
    fn test_event_type_code() {
        assert!((event_type_code("fomc") - EVENT_FOMC).abs() < 1e-6);
        assert!((event_type_code("CPI") - EVENT_CPI).abs() < 1e-6);
        assert!((event_type_code("nfp") - EVENT_NFP).abs() < 1e-6);
        assert!((event_type_code("gdp") - EVENT_GDP).abs() < 1e-6);
        assert!((event_type_code("earnings") - EVENT_EARNINGS).abs() < 1e-6);
        assert!((event_type_code("unknown") - EVENT_OTHER).abs() < 1e-6);
    }

    #[test]
    fn test_find_nearest_event_basic() {
        let now = 1700000000.0;
        let events = vec![
            CalendarEvent {
                name: "Past Event".to_string(),
                timestamp: now - 86400.0,
                event_type: "fomc".to_string(),
            },
            CalendarEvent {
                name: "Soon Event".to_string(),
                timestamp: now + 3600.0,
                event_type: "cpi".to_string(),
            },
            CalendarEvent {
                name: "Later Event".to_string(),
                timestamp: now + 86400.0 * 7.0,
                event_type: "gdp".to_string(),
            },
        ];

        let snap = find_nearest_event("test", &events, now).unwrap();
        assert!(snap.source.contains("Soon Event"));
        assert!((snap.bid - 3600.0).abs() < 1.0);
        assert!((snap.ask - EVENT_CPI).abs() < 1e-6);
    }

    #[test]
    fn test_find_nearest_event_none_future() {
        let now = 1700000000.0;
        let events = vec![CalendarEvent {
            name: "Past".to_string(),
            timestamp: now - 100.0,
            event_type: "fomc".to_string(),
        }];
        assert!(find_nearest_event("test", &events, now).is_none());
    }

    #[test]
    fn test_find_nearest_event_empty() {
        assert!(find_nearest_event("test", &[], 1700000000.0).is_none());
    }

    #[test]
    fn test_find_nearest_event_urgency() {
        let now = 1700000000.0;
        let events = vec![CalendarEvent {
            name: "Imminent".to_string(),
            timestamp: now + 60.0, // 1 minute away
            event_type: "fomc".to_string(),
        }];
        let snap = find_nearest_event("test", &events, now).unwrap();
        assert!(snap.price > 0.99); // nearly 1.0 (imminent)
    }

    #[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);
        assert!((clamp_interval(f64::NAN) - 300.0).abs() < 1e-6);
    }
}
