use tokio::sync::{broadcast, watch};
use tracing::{error, info};
use uforia_ms_config::AppConfig;
use uforia_ms_types::{CmError, FundingEvent, IvEvent, MarketEvent};

use crate::binance_futures::BinanceFuturesFeed;
use crate::binance_spot::BinanceSpotFeed;
use crate::bybit_perps::BybitPerpsFeed;
use crate::bybit_spot::BybitSpotFeed;
use crate::deribit::DeribitFeed;

/// Manages all exchange feed connections, starting enabled feeds and
/// coordinating shutdown.
pub struct FeedManager {
    config: AppConfig,
    market_tx: broadcast::Sender<MarketEvent>,
    funding_tx: broadcast::Sender<FundingEvent>,
    iv_tx: broadcast::Sender<IvEvent>,
    shutdown_tx: watch::Sender<bool>,
    shutdown_rx: watch::Receiver<bool>,
}

impl FeedManager {
    pub fn new(
        config: AppConfig,
        market_tx: broadcast::Sender<MarketEvent>,
        funding_tx: broadcast::Sender<FundingEvent>,
        iv_tx: broadcast::Sender<IvEvent>,
    ) -> Self {
        let (shutdown_tx, shutdown_rx) = watch::channel(false);
        Self {
            config,
            market_tx,
            funding_tx,
            iv_tx,
            shutdown_tx,
            shutdown_rx,
        }
    }

    /// Get a clone of the shutdown receiver for external use.
    pub fn shutdown_rx(&self) -> watch::Receiver<bool> {
        self.shutdown_rx.clone()
    }

    /// Signal all feeds to shut down.
    pub fn shutdown(&self) {
        info!("FeedManager sending shutdown signal");
        let _ = self.shutdown_tx.send(true);
    }

    /// Spawn all enabled feed tasks and return their join handles.
    pub fn start(&self) -> Vec<tokio::task::JoinHandle<Result<(), CmError>>> {
        let assets = self.config.parsed_assets();
        let mut handles = Vec::new();

        if self.config.feeds.binance_spot.enabled {
            let feed = BinanceSpotFeed::new(
                self.config.feeds.binance_spot.ws_url.clone(),
                self.config.feeds.binance_spot.rest_url.clone(),
                assets.clone(),
                self.market_tx.clone(),
                self.config.feeds.binance_spot.reconnect_base_ms,
                self.config.feeds.binance_spot.reconnect_max_ms,
            );
            let rx = self.shutdown_rx.clone();
            handles.push(tokio::spawn(async move { feed.run(rx).await }));
            info!("started binance_spot feed");
        }

        if self.config.feeds.binance_futures.enabled {
            let feed = BinanceFuturesFeed::new(
                self.config.feeds.binance_futures.ws_url.clone(),
                self.config.feeds.binance_futures.rest_url.clone(),
                assets.clone(),
                self.market_tx.clone(),
                self.funding_tx.clone(),
                self.config.feeds.binance_futures.reconnect_base_ms,
                self.config.feeds.binance_futures.reconnect_max_ms,
            );
            let rx = self.shutdown_rx.clone();
            handles.push(tokio::spawn(async move { feed.run(rx).await }));
            info!("started binance_futures feed");
        }

        if self.config.feeds.bybit_spot.enabled {
            let feed = BybitSpotFeed::new(
                self.config.feeds.bybit_spot.ws_url.clone(),
                assets.clone(),
                self.market_tx.clone(),
                self.config.feeds.bybit_spot.reconnect_base_ms,
                self.config.feeds.bybit_spot.reconnect_max_ms,
            );
            let rx = self.shutdown_rx.clone();
            handles.push(tokio::spawn(async move { feed.run(rx).await }));
            info!("started bybit_spot feed");
        }

        if self.config.feeds.bybit_perps.enabled {
            let feed = BybitPerpsFeed::new(
                self.config.feeds.bybit_perps.ws_url.clone(),
                assets.clone(),
                self.market_tx.clone(),
                self.funding_tx.clone(),
                self.config.feeds.bybit_perps.reconnect_base_ms,
                self.config.feeds.bybit_perps.reconnect_max_ms,
            );
            let rx = self.shutdown_rx.clone();
            handles.push(tokio::spawn(async move { feed.run(rx).await }));
            info!("started bybit_perps feed");
        }

        if self.config.feeds.deribit.enabled {
            let feed = DeribitFeed::new(
                self.config.feeds.deribit.ws_url.clone(),
                assets.clone(),
                self.iv_tx.clone(),
                self.config.feeds.deribit.reconnect_base_ms,
                self.config.feeds.deribit.reconnect_max_ms,
            );
            let rx = self.shutdown_rx.clone();
            handles.push(tokio::spawn(async move { feed.run(rx).await }));
            info!("started deribit feed");
        }

        info!(
            feed_count = handles.len(),
            "FeedManager started all enabled feeds"
        );

        handles
    }

    /// Start feeds and wait for all to complete (or shutdown).
    pub async fn run_until_shutdown(&self) {
        let handles = self.start();

        for (i, handle) in handles.into_iter().enumerate() {
            match handle.await {
                Ok(Ok(())) => info!(feed_index = i, "feed exited cleanly"),
                Ok(Err(e)) => error!(feed_index = i, error = %e, "feed exited with error"),
                Err(e) => error!(feed_index = i, error = %e, "feed task panicked"),
            }
        }

        info!("all feeds have exited");
    }
}

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

    fn sample_config() -> AppConfig {
        let toml_str = r#"
[general]
assets = ["btc", "eth"]
log_level = "info"
metrics_port = 9090

[feeds.binance_spot]
enabled = true
ws_url = "wss://stream.binance.com:9443/ws"
rest_url = "https://api.binance.com"

[feeds.binance_futures]
enabled = false
ws_url = "wss://fstream.binance.com/ws"
rest_url = "https://fapi.binance.com"

[feeds.bybit_spot]
enabled = false
ws_url = "wss://stream.bybit.com/v5/public/spot"

[feeds.bybit_perps]
enabled = false
ws_url = "wss://stream.bybit.com/v5/public/linear"

[feeds.deribit]
enabled = false
ws_url = "wss://www.deribit.com/ws/api/v2"

[bar]
interval_secs = 60
flush_delay_ms = 200
gap_threshold_secs = 30

[costs.btc]
fee_bps = 2.0
spread_bps = 1.0
slip_bps = 1.0

[costs.eth]
fee_bps = 2.0
spread_bps = 1.5
slip_bps = 1.5

[features]
book_levels = 5

[regime]
lookback_days = [7, 30, 90]

[model]
weights_dir = "models"

[risk]
max_daily_loss = 1000.0
max_position_size = 0.1
risk_budget = 100.0

[storage.postgres]
dsn = "postgresql://localhost/uforia"
schema = "uforia"
buffer_size = 1000
"#;
        toml::from_str(toml_str).unwrap()
    }

    #[test]
    fn test_feed_manager_creation() {
        let config = sample_config();
        let (market_tx, _) = broadcast::channel(1024);
        let (funding_tx, _) = broadcast::channel(1024);
        let (iv_tx, _) = broadcast::channel(1024);

        let manager = FeedManager::new(config, market_tx, funding_tx, iv_tx);
        assert!(!*manager.shutdown_rx().borrow());
    }

    #[test]
    fn test_shutdown_signal() {
        let config = sample_config();
        let (market_tx, _) = broadcast::channel(1024);
        let (funding_tx, _) = broadcast::channel(1024);
        let (iv_tx, _) = broadcast::channel(1024);

        let manager = FeedManager::new(config, market_tx, funding_tx, iv_tx);
        let rx = manager.shutdown_rx();

        assert!(!*rx.borrow());
        manager.shutdown();
        assert!(*rx.borrow());
    }

    #[tokio::test]
    async fn test_start_only_enabled_feeds() {
        let config = sample_config();
        // Only binance_spot is enabled in the sample config
        let (market_tx, _) = broadcast::channel(1024);
        let (funding_tx, _) = broadcast::channel(1024);
        let (iv_tx, _) = broadcast::channel(1024);

        let manager = FeedManager::new(config, market_tx, funding_tx, iv_tx);
        let handles = manager.start();

        // Only binance_spot is enabled, so 1 handle
        assert_eq!(handles.len(), 1);

        // Shut down immediately so the test doesn't hang
        manager.shutdown();

        for handle in handles {
            let _ = tokio::time::timeout(std::time::Duration::from_secs(5), handle).await;
        }
    }

    #[test]
    fn test_all_feeds_disabled() {
        let toml_str = r#"
[general]
assets = ["btc"]
log_level = "info"

[feeds.binance_spot]
enabled = false
[feeds.binance_futures]
enabled = false
[feeds.bybit_spot]
enabled = false
[feeds.bybit_perps]
enabled = false
[feeds.deribit]
enabled = false

[bar]
[costs.btc]
fee_bps = 2.0
spread_bps = 1.0
slip_bps = 1.0
[features]
[regime]
[model]
[risk]
max_daily_loss = 1000.0
risk_budget = 100.0
[storage.postgres]
dsn = "postgresql://localhost/uforia"
schema = "uforia"
"#;
        let config: AppConfig = toml::from_str(toml_str).unwrap();
        let (market_tx, _) = broadcast::channel(1024);
        let (funding_tx, _) = broadcast::channel(1024);
        let (iv_tx, _) = broadcast::channel(1024);

        let manager = FeedManager::new(config, market_tx, funding_tx, iv_tx);
        let handles = manager.start();
        assert_eq!(handles.len(), 0);
    }
}
