use std::collections::HashMap;
use std::sync::Arc;

use chrono::{TimeZone, Utc};
use futures_util::StreamExt;
use ordered_float::OrderedFloat;
use reqwest::Client;
use serde_json::Value;
use tokio::sync::{broadcast, watch, Mutex};
use tokio_tungstenite::{connect_async, tungstenite::Message};
use tracing::{debug, error, info, warn};
use uforia_ms_types::{
    Asset, BookLevel, BookStatus, CmError, FundingEvent, FundingRate, L2Snapshot, L2Update,
    Liquidation, MarkIndex, MarketEvent, Side, Venue,
};

use crate::normalizer;
use crate::reconnect::ReconnectPolicy;

#[derive(Debug, Default, Clone)]
struct BootstrapState {
    seeded: bool,
    last_update_id: Option<u64>,
    buffered: Vec<L2Update>,
    reseed_count: u64,
}

/// Binance futures WebSocket + REST bootstrap feed.
pub struct BinanceFuturesFeed {
    ws_url: String,
    rest_url: String,
    assets: Vec<Asset>,
    market_tx: broadcast::Sender<MarketEvent>,
    funding_tx: broadcast::Sender<FundingEvent>,
    reconnect_base_ms: u64,
    reconnect_max_ms: u64,
    client: Client,
    bootstrap: Arc<Mutex<HashMap<Asset, BootstrapState>>>,
}

impl BinanceFuturesFeed {
    pub fn new(
        ws_url: String,
        rest_url: String,
        assets: Vec<Asset>,
        market_tx: broadcast::Sender<MarketEvent>,
        funding_tx: broadcast::Sender<FundingEvent>,
        reconnect_base_ms: u64,
        reconnect_max_ms: u64,
    ) -> Self {
        let bootstrap = assets
            .iter()
            .copied()
            .map(|asset| (asset, BootstrapState::default()))
            .collect();
        Self {
            ws_url,
            rest_url,
            assets,
            market_tx,
            funding_tx,
            reconnect_base_ms,
            reconnect_max_ms,
            client: Client::new(),
            bootstrap: Arc::new(Mutex::new(bootstrap)),
        }
    }

    fn build_url(&self) -> String {
        let streams: Vec<String> = self
            .assets
            .iter()
            .flat_map(|asset| {
                let sym = asset.symbol(Venue::BinanceFutures);
                vec![
                    format!("{}@aggTrade", sym),
                    format!("{}@depth@100ms", sym),
                    format!("{}@markPrice@1s", sym),
                    format!("{}@forceOrder", sym),
                ]
            })
            .collect();
        format!("{}/stream?streams={}", self.ws_url, streams.join("/"))
    }

    fn resolve_asset(symbol: &str) -> Option<Asset> {
        match symbol {
            "BTCUSDT" | "btcusdt" => Some(Asset::Btc),
            "ETHUSDT" | "ethusdt" => Some(Asset::Eth),
            _ => None,
        }
    }

    pub async fn run(&self, mut shutdown: watch::Receiver<bool>) -> Result<(), CmError> {
        let mut policy = ReconnectPolicy::new(self.reconnect_base_ms, self.reconnect_max_ms);
        let url = self.build_url();
        info!(url = %url, "binance_futures feed starting");

        loop {
            self.reset_bootstrap().await;
            match connect_async(&url).await {
                Ok((ws_stream, _)) => {
                    policy.reset();
                    info!("binance_futures connected");
                    for asset in &self.assets {
                        self.spawn_snapshot_seed(*asset);
                    }
                    let (_write, mut read) = ws_stream.split();
                    loop {
                        tokio::select! {
                            msg = read.next() => {
                                match msg {
                                    Some(Ok(Message::Text(text))) => {
                                        if let Err(e) = self.handle_message(&text).await {
                                            debug!(error = %e, "binance_futures parse error");
                                        }
                                    }
                                    Some(Ok(Message::Close(_))) => {
                                        warn!("binance_futures received close frame");
                                        break;
                                    }
                                    Some(Err(e)) => {
                                        error!(error = %e, "binance_futures ws error");
                                        break;
                                    }
                                    None => {
                                        warn!("binance_futures stream ended");
                                        break;
                                    }
                                    _ => {}
                                }
                            }
                            _ = shutdown.changed() => {
                                if *shutdown.borrow() {
                                    info!("binance_futures shutting down");
                                    return Ok(());
                                }
                            }
                        }
                    }
                }
                Err(e) => {
                    error!(error = %e, "binance_futures connection failed");
                }
            }

            if *shutdown.borrow() {
                info!("binance_futures shutting down (before reconnect)");
                return Ok(());
            }
            policy.wait("binance_futures").await;
        }
    }

    async fn handle_message(&self, text: &str) -> Result<(), CmError> {
        let wrapper: Value =
            serde_json::from_str(text).map_err(|e| CmError::Parse {
                context: "binance_futures".into(),
                msg: format!("JSON parse error: {}", e),
            })?;
        let data = if wrapper.get("stream").is_some() {
            wrapper.get("data").ok_or_else(|| CmError::Parse {
                context: "binance_futures".into(),
                msg: "missing 'data' in combined stream".into(),
            })?
        } else {
            &wrapper
        };
        match data.get("e").and_then(|v| v.as_str()).unwrap_or("") {
            "aggTrade" => self.handle_trade(data),
            "depthUpdate" => self.handle_depth(data).await,
            "markPriceUpdate" => self.handle_mark_price(data),
            "forceOrder" => self.handle_force_order(data),
            other => {
                debug!(event_type = other, "binance_futures unknown event type");
                Ok(())
            }
        }
    }

    fn handle_trade(&self, data: &Value) -> Result<(), CmError> {
        let symbol = data.get("s").and_then(|v| v.as_str()).unwrap_or("");
        if let Some(asset) = Self::resolve_asset(symbol) {
            let trade = normalizer::parse_binance_futures_trade(data, asset)?;
            let _ = self.market_tx.send(MarketEvent::Trade(trade));
        }
        Ok(())
    }

    async fn handle_depth(&self, data: &Value) -> Result<(), CmError> {
        let symbol = data.get("s").and_then(|v| v.as_str()).unwrap_or("");
        if let Some(asset) = Self::resolve_asset(symbol) {
            let update = normalizer::parse_binance_depth_update(data, asset, Venue::BinanceFutures)?;
            self.process_depth_update(asset, update).await;
        }
        Ok(())
    }

    fn handle_mark_price(&self, data: &Value) -> Result<(), CmError> {
        let symbol = data.get("s").and_then(|v| v.as_str()).unwrap_or("");
        let asset = match Self::resolve_asset(symbol) {
            Some(a) => a,
            None => return Ok(()),
        };

        let ts_ms = data.get("E").and_then(|v| v.as_i64()).ok_or_else(|| CmError::Parse {
            context: "binance_futures_mark".into(),
            msg: "missing 'E'".into(),
        })?;
        let timestamp = Utc.timestamp_millis_opt(ts_ms).single().ok_or_else(|| CmError::Parse {
            context: "binance_futures_mark".into(),
            msg: "invalid timestamp".into(),
        })?;
        let mark_price = data.get("p").and_then(|v| v.as_str()).unwrap_or("0").parse().unwrap_or(0.0);
        let index_price = data.get("i").and_then(|v| v.as_str()).unwrap_or("0").parse().unwrap_or(0.0);
        let _ = self.market_tx.send(MarketEvent::MarkIndex(MarkIndex {
            timestamp,
            asset,
            venue: Venue::BinanceFutures,
            mark_price,
            index_price,
        }));
        if let Some(rate_str) = data.get("r").and_then(|v| v.as_str()) {
            if let Ok(rate) = rate_str.parse::<f64>() {
                let next_funding_time = data
                    .get("T")
                    .and_then(|v| v.as_i64())
                    .and_then(|ms| Utc.timestamp_millis_opt(ms).single());
                let _ = self.funding_tx.send(FundingEvent::Funding(FundingRate {
                    timestamp,
                    asset,
                    venue: Venue::BinanceFutures,
                    rate,
                    next_funding_time,
                }));
            }
        }
        Ok(())
    }

    fn handle_force_order(&self, data: &Value) -> Result<(), CmError> {
        let order = data.get("o").ok_or_else(|| CmError::Parse {
            context: "binance_futures_force_order".into(),
            msg: "missing 'o'".into(),
        })?;
        let symbol = order.get("s").and_then(|v| v.as_str()).unwrap_or("");
        let asset = match Self::resolve_asset(symbol) {
            Some(a) => a,
            None => return Ok(()),
        };
        let ts_ms = order.get("T").and_then(|v| v.as_i64()).unwrap_or_else(|| {
            data.get("E").and_then(|v| v.as_i64()).unwrap_or(0)
        });
        let timestamp = Utc.timestamp_millis_opt(ts_ms).single().ok_or_else(|| CmError::Parse {
            context: "binance_futures_force_order".into(),
            msg: "invalid timestamp".into(),
        })?;
        let side = match order.get("S").and_then(|v| v.as_str()).unwrap_or("") {
            "SELL" => Side::Sell,
            _ => Side::Buy,
        };
        let price = order.get("p").and_then(|v| v.as_str()).unwrap_or("0").parse().unwrap_or(0.0);
        let size = order.get("q").and_then(|v| v.as_str()).unwrap_or("0").parse().unwrap_or(0.0);
        let _ = self.funding_tx.send(FundingEvent::Liquidation(Liquidation {
            timestamp,
            asset,
            venue: Venue::BinanceFutures,
            side,
            price,
            size,
        }));
        Ok(())
    }

    async fn process_depth_update(&self, asset: Asset, update: L2Update) {
        let mut resend_snapshot = false;
        let mut emit_update = None;
        let mut emit_status = None;
        {
            let mut states = self.bootstrap.lock().await;
            let state = states.entry(asset).or_default();
            if !state.seeded {
                state.buffered.push(update);
                if state.buffered.len() > 4000 {
                    let keep_from = state.buffered.len().saturating_sub(2000);
                    state.buffered = state.buffered.split_off(keep_from);
                }
                return;
            }
            if let (Some(first), Some(last), Some(prev_last)) =
                (update.first_update_id, update.last_update_id, state.last_update_id)
            {
                if last <= prev_last {
                    return;
                }
                if first <= prev_last + 1 && last >= prev_last + 1 {
                    state.last_update_id = Some(last);
                    emit_update = Some(update);
                } else {
                    state.seeded = false;
                    state.last_update_id = None;
                    state.reseed_count += 1;
                    state.buffered.clear();
                    emit_status = Some(BookStatus {
                        timestamp: update.timestamp,
                        asset,
                        venue: Venue::BinanceFutures,
                        is_seeded: false,
                        is_sequence_valid: false,
                        is_gap_detected: true,
                        reseed_count: state.reseed_count,
                    });
                    resend_snapshot = true;
                }
            }
        }
        if let Some(status) = emit_status {
            let _ = self.market_tx.send(MarketEvent::BookStatus(status));
        }
        if let Some(valid_update) = emit_update {
            let _ = self.market_tx.send(MarketEvent::L2Update(valid_update));
        }
        if resend_snapshot {
            self.spawn_snapshot_seed(asset);
        }
    }

    fn spawn_snapshot_seed(&self, asset: Asset) {
        let rest_url = self.rest_url.clone();
        let client = self.client.clone();
        let market_tx = self.market_tx.clone();
        let bootstrap = self.bootstrap.clone();
        tokio::spawn(async move {
            match fetch_snapshot(&client, &rest_url, asset, Venue::BinanceFutures).await {
                Ok(snapshot) => {
                    let mut events = vec![MarketEvent::L2Snapshot(snapshot.clone())];
                    let status;
                    {
                        let mut states = bootstrap.lock().await;
                        let state = states.entry(asset).or_default();
                        let mut current = snapshot.last_update_id.unwrap_or_default();
                        let buffered = std::mem::take(&mut state.buffered);
                        state.seeded = true;
                        state.last_update_id = Some(current);
                        for update in buffered {
                            let first = update.first_update_id.unwrap_or_default();
                            let last = update.last_update_id.unwrap_or_default();
                            if last <= current {
                                continue;
                            }
                            if first <= current + 1 && last >= current + 1 {
                                current = last;
                                state.last_update_id = Some(current);
                                events.push(MarketEvent::L2Update(update));
                            }
                        }
                        status = Some(BookStatus {
                            timestamp: snapshot.timestamp,
                            asset,
                            venue: Venue::BinanceFutures,
                            is_seeded: true,
                            is_sequence_valid: true,
                            is_gap_detected: false,
                            reseed_count: state.reseed_count,
                        });
                    }
                    for event in events {
                        let _ = market_tx.send(event);
                    }
                    if let Some(book_status) = status {
                        let _ = market_tx.send(MarketEvent::BookStatus(book_status));
                    }
                }
                Err(error) => {
                    error!(asset = %asset, error = %error, "binance_futures snapshot bootstrap failed");
                }
            }
        });
    }

    async fn reset_bootstrap(&self) {
        let mut states = self.bootstrap.lock().await;
        for state in states.values_mut() {
            state.seeded = false;
            state.last_update_id = None;
            state.buffered.clear();
        }
    }
}

async fn fetch_snapshot(
    client: &Client,
    rest_url: &str,
    asset: Asset,
    venue: Venue,
) -> Result<L2Snapshot, CmError> {
    let symbol = asset.symbol(venue).to_uppercase();
    let response = client
        .get(format!("{rest_url}/fapi/v1/depth"))
        .query(&[("symbol", symbol.as_str()), ("limit", "1000")])
        .send()
        .await
        .map_err(|e| CmError::Feed {
            venue: venue.to_string(),
            msg: format!("binance futures snapshot request failed: {e}"),
        })?;
    let raw: Value = response
        .json::<Value>()
        .await
        .map_err(|e| CmError::Parse {
            context: "binance_futures_snapshot".into(),
            msg: format!("snapshot json parse error: {e}"),
        })?;
    let last_update_id = raw.get("lastUpdateId").and_then(Value::as_u64);
    let bids = parse_levels(raw.get("bids"))?;
    let asks = parse_levels(raw.get("asks"))?;
    Ok(L2Snapshot {
        timestamp: Utc::now(),
        asset,
        venue,
        bids,
        asks,
        last_update_id,
    })
}

fn parse_levels(raw: Option<&Value>) -> Result<Vec<BookLevel>, CmError> {
    let levels = raw.and_then(Value::as_array).ok_or_else(|| CmError::Parse {
        context: "binance_futures_snapshot".into(),
        msg: "missing levels".into(),
    })?;
    levels
        .iter()
        .map(|entry| {
            let parts = entry.as_array().ok_or_else(|| CmError::Parse {
                context: "binance_futures_snapshot".into(),
                msg: "level is not array".into(),
            })?;
            let price = parts.first().and_then(Value::as_str).ok_or_else(|| CmError::Parse {
                context: "binance_futures_snapshot".into(),
                msg: "missing level price".into(),
            })?;
            let size = parts.get(1).and_then(Value::as_str).ok_or_else(|| CmError::Parse {
                context: "binance_futures_snapshot".into(),
                msg: "missing level size".into(),
            })?;
            Ok(BookLevel {
                price: OrderedFloat(price.parse::<f64>().map_err(|e| CmError::Parse {
                    context: "binance_futures_snapshot".into(),
                    msg: format!("invalid price: {e}"),
                })?),
                size: size.parse::<f64>().map_err(|e| CmError::Parse {
                    context: "binance_futures_snapshot".into(),
                    msg: format!("invalid size: {e}"),
                })?,
            })
        })
        .collect()
}
