pub mod binance;
pub mod kalshi_book;
pub mod polymarket_book;

use dashmap::DashMap;
use pyo3::prelude::*;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use tracing::{debug, warn};

use crate::orderbook::OrderbookSnapshot;

/// Snapshot of a single feed's latest data.
#[pyclass]
#[derive(Debug, Clone)]
pub struct FeedSnapshot {
    #[pyo3(get)]
    pub price: f64,
    #[pyo3(get)]
    pub timestamp: f64,
    #[pyo3(get)]
    pub source: String,
    #[pyo3(get)]
    pub bid: f64,
    #[pyo3(get)]
    pub ask: f64,
    #[pyo3(get)]
    pub volume_24h: f64,
}

#[pymethods]
impl FeedSnapshot {
    #[new]
    #[pyo3(signature = (price=0.0, timestamp=0.0, source="", bid=0.0, ask=0.0, volume_24h=0.0))]
    fn new(price: f64, timestamp: f64, source: &str, bid: f64, ask: f64, volume_24h: f64) -> Self {
        Self {
            price,
            timestamp,
            source: source.to_string(),
            bid,
            ask,
            volume_24h,
        }
    }

    fn __repr__(&self) -> String {
        format!(
            "FeedSnapshot(price={:.4}, source='{}', bid={:.4}, ask={:.4})",
            self.price, self.source, self.bid, self.ask
        )
    }
}

impl Default for FeedSnapshot {
    fn default() -> Self {
        Self {
            price: 0.0,
            timestamp: 0.0,
            source: String::new(),
            bid: 0.0,
            ask: 0.0,
            volume_24h: 0.0,
        }
    }
}

/// Feed configuration passed from Python.
#[derive(Debug, Clone)]
pub enum FeedConfig {
    BinanceWS { symbol: String },
    PolymarketBook { market_slug: String },
    KalshiBook { ticker: String },
    REST { url: String, interval_secs: f64 },
}

/// Shared storage for orderbook snapshots (separate from price-level FeedSnapshot).
/// Uses DashMap for lock-free concurrent reads/writes (feeds writing at different rates
/// no longer block each other or the strategy reads).
pub type OrderbookStore = Arc<DashMap<String, OrderbookSnapshot>>;

/// Manages all running feeds and their latest snapshots.
/// Uses DashMap instead of RwLock<HashMap> so feed writers and strategy readers
/// don't contend on a single lock.
pub struct FeedManager {
    snapshots: Arc<DashMap<String, FeedSnapshot>>,
    orderbooks: OrderbookStore,
    runtime: tokio::runtime::Runtime,
    shutdown: Arc<tokio::sync::Notify>,
}

impl FeedManager {
    pub fn new() -> Result<Self, String> {
        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(2)
            .enable_all()
            .build()
            .map_err(|e| format!("failed to create feed runtime: {}", e))?;

        Ok(Self {
            snapshots: Arc::new(DashMap::new()),
            orderbooks: Arc::new(DashMap::new()),
            runtime,
            shutdown: Arc::new(tokio::sync::Notify::new()),
        })
    }

    /// Start a feed. Spawns a background task on the tokio runtime.
    pub fn start_feed(&self, name: String, config: FeedConfig) {
        let snapshots = self.snapshots.clone();
        let orderbooks = self.orderbooks.clone();
        let shutdown = self.shutdown.clone();
        let feed_name = name.clone();

        // Initialize with empty snapshot
        snapshots.insert(name.clone(), FeedSnapshot::default());

        debug!(feed = %name, config = ?config, "starting feed");

        match config {
            FeedConfig::BinanceWS { symbol } => {
                self.runtime.spawn(async move {
                    binance::run_binance_ws(feed_name, symbol, snapshots, shutdown).await;
                });
            }
            FeedConfig::PolymarketBook { market_slug } => {
                self.runtime.spawn(async move {
                    polymarket_book::run_polymarket_book(
                        feed_name,
                        market_slug,
                        snapshots,
                        orderbooks,
                        shutdown,
                    )
                    .await;
                });
            }
            FeedConfig::KalshiBook { ticker } => {
                self.runtime.spawn(async move {
                    kalshi_book::run_kalshi_book(
                        feed_name,
                        ticker,
                        snapshots,
                        orderbooks,
                        shutdown,
                    )
                    .await;
                });
            }
            FeedConfig::REST { url, interval_secs } => {
                self.runtime.spawn(async move {
                    run_rest_feed(feed_name, url, interval_secs, snapshots, shutdown).await;
                });
            }
        }
    }

    /// Get the latest snapshot for a feed.
    pub fn snapshot(&self, name: &str) -> Option<FeedSnapshot> {
        self.snapshots.get(name).map(|entry| entry.value().clone())
    }

    /// Get all snapshots.
    pub fn all_snapshots(&self) -> HashMap<String, FeedSnapshot> {
        self.snapshots
            .iter()
            .map(|entry| (entry.key().clone(), entry.value().clone()))
            .collect()
    }

    /// Get the latest L2 orderbook snapshot for a feed.
    pub fn orderbook(&self, name: &str) -> Option<OrderbookSnapshot> {
        self.orderbooks.get(name).map(|entry| entry.value().clone())
    }

    /// Get all orderbook snapshots.
    pub fn all_orderbooks(&self) -> HashMap<String, OrderbookSnapshot> {
        self.orderbooks
            .iter()
            .map(|entry| (entry.key().clone(), entry.value().clone()))
            .collect()
    }

    /// Stop all feeds.
    pub fn stop_all(&self) {
        self.shutdown.notify_waiters();
    }
}

impl Drop for FeedManager {
    fn drop(&mut self) {
        self.stop_all();
    }
}

/// Generic REST feed — polls a URL at an interval.
async fn run_rest_feed(
    name: String,
    url: String,
    interval_secs: f64,
    snapshots: Arc<DashMap<String, FeedSnapshot>>,
    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 interval = tokio::time::Duration::from_secs_f64(interval_secs);

    loop {
        tokio::select! {
            _ = shutdown.notified() => break,
            _ = tokio::time::sleep(interval) => {
                match client.get(&url).send().await {
                    Ok(resp) => {
                        if let Ok(body) = resp.text().await {
                            match serde_json::from_str::<serde_json::Value>(&body) {
                                Ok(json) => {
                                    if let Some(price) = json.get("price").and_then(|v| v.as_f64()) {
                                        let now = std::time::SystemTime::now()
                                            .duration_since(std::time::UNIX_EPOCH)
                                            .unwrap_or_default()
                                            .as_secs_f64();
                                        let snap = FeedSnapshot {
                                            price,
                                            timestamp: now,
                                            source: name.clone(),
                                            bid: json.get("bid").and_then(|v| v.as_f64()).unwrap_or(0.0),
                                            ask: json.get("ask").and_then(|v| v.as_f64()).unwrap_or(0.0),
                                            volume_24h: json.get("volume").and_then(|v| v.as_f64()).unwrap_or(0.0),
                                        };
                                        snapshots.insert(name.clone(), snap);
                                    } else {
                                        debug!(feed = %name, "REST feed: JSON missing 'price' field");
                                    }
                                }
                                Err(e) => {
                                    warn!(feed = %name, error = %e, "REST feed: JSON parse failure");
                                }
                            }
                        }
                    }
                    Err(e) => {
                        warn!(feed = %name, error = %e, "REST feed poll failed");
                    }
                }
            }
        }
    }
}
