use chrono::{DateTime, TimeZone, Utc};
use ordered_float::OrderedFloat;
use serde_json::Value;
use uforia_ms_types::{Asset, CmError, Side, Trade, Venue};

/// Parse a Binance spot trade message.
///
/// Expected JSON format (from `{symbol}@trade` stream):
/// ```json
/// {
///   "e": "trade",
///   "E": 1672515782136,
///   "s": "BTCUSDT",
///   "t": 12345,
///   "p": "50000.00",
///   "q": "0.001",
///   "T": 1672515782136,
///   "m": true,
///   "M": true
/// }
/// ```
/// - `T` = trade time (ms)
/// - `p` = price (string)
/// - `q` = quantity (string)
/// - `m` = is buyer the market maker? If true, the taker side is Sell; if false, Buy.
///   NOTE: Binance docs say `m=true` means "the buyer is the maker", which means the
///   taker (aggressor) is the seller. So `m=true` => taker is Sell, `m=false` => taker is Buy.
pub fn parse_binance_spot_trade(raw: &Value, asset: Asset) -> Result<Trade, CmError> {
    let ts_ms = raw
        .get("T")
        .and_then(Value::as_i64)
        .ok_or_else(|| CmError::Parse {
            context: "binance_spot_trade".into(),
            msg: "missing or invalid 'T' field".into(),
        })?;

    let timestamp = Utc
        .timestamp_millis_opt(ts_ms)
        .single()
        .ok_or_else(|| CmError::Parse {
            context: "binance_spot_trade".into(),
            msg: format!("invalid timestamp ms: {}", ts_ms),
        })?;

    let price: f64 = raw
        .get("p")
        .and_then(Value::as_str)
        .ok_or_else(|| CmError::Parse {
            context: "binance_spot_trade".into(),
            msg: "missing or invalid 'p' field".into(),
        })?
        .parse()
        .map_err(|e| CmError::Parse {
            context: "binance_spot_trade".into(),
            msg: format!("cannot parse price: {}", e),
        })?;

    let size: f64 = raw
        .get("q")
        .and_then(Value::as_str)
        .ok_or_else(|| CmError::Parse {
            context: "binance_spot_trade".into(),
            msg: "missing or invalid 'q' field".into(),
        })?
        .parse()
        .map_err(|e| CmError::Parse {
            context: "binance_spot_trade".into(),
            msg: format!("cannot parse size: {}", e),
        })?;

    let is_buyer_maker = raw
        .get("m")
        .and_then(Value::as_bool)
        .ok_or_else(|| CmError::Parse {
            context: "binance_spot_trade".into(),
            msg: "missing or invalid 'm' field".into(),
        })?;

    // Binance docs: m=true => buyer is maker => taker is seller => Side::Sell
    // m=false => buyer is taker => taker is buyer => Side::Buy
    let side = if is_buyer_maker {
        Side::Sell
    } else {
        Side::Buy
    };

    let trade_id = raw
        .get("t")
        .and_then(Value::as_u64)
        .map(|id| id.to_string());

    Ok(Trade {
        timestamp,
        asset,
        venue: Venue::BinanceSpot,
        price,
        size,
        side,
        trade_id,
    })
}

/// Parse a Binance futures trade message.
///
/// Same format as spot but venue is BinanceFutures.
pub fn parse_binance_futures_trade(raw: &Value, asset: Asset) -> Result<Trade, CmError> {
    let ts_ms = raw
        .get("T")
        .and_then(Value::as_i64)
        .ok_or_else(|| CmError::Parse {
            context: "binance_futures_trade".into(),
            msg: "missing or invalid 'T' field".into(),
        })?;

    let timestamp = Utc
        .timestamp_millis_opt(ts_ms)
        .single()
        .ok_or_else(|| CmError::Parse {
            context: "binance_futures_trade".into(),
            msg: format!("invalid timestamp ms: {}", ts_ms),
        })?;

    let price: f64 = raw
        .get("p")
        .and_then(Value::as_str)
        .ok_or_else(|| CmError::Parse {
            context: "binance_futures_trade".into(),
            msg: "missing or invalid 'p' field".into(),
        })?
        .parse()
        .map_err(|e| CmError::Parse {
            context: "binance_futures_trade".into(),
            msg: format!("cannot parse price: {}", e),
        })?;

    let size: f64 = raw
        .get("q")
        .and_then(Value::as_str)
        .ok_or_else(|| CmError::Parse {
            context: "binance_futures_trade".into(),
            msg: "missing or invalid 'q' field".into(),
        })?
        .parse()
        .map_err(|e| CmError::Parse {
            context: "binance_futures_trade".into(),
            msg: format!("cannot parse size: {}", e),
        })?;

    let is_buyer_maker = raw
        .get("m")
        .and_then(Value::as_bool)
        .ok_or_else(|| CmError::Parse {
            context: "binance_futures_trade".into(),
            msg: "missing or invalid 'm' field".into(),
        })?;

    let side = if is_buyer_maker {
        Side::Sell
    } else {
        Side::Buy
    };

    let trade_id = raw
        .get("t")
        .and_then(Value::as_u64)
        .map(|id| id.to_string());

    Ok(Trade {
        timestamp,
        asset,
        venue: Venue::BinanceFutures,
        price,
        size,
        side,
        trade_id,
    })
}

/// Parse Binance depth update into bid/ask BookLevel vectors.
///
/// Expected JSON (from `{symbol}@depth@100ms`):
/// ```json
/// {
///   "e": "depthUpdate",
///   "E": 1672515782136,
///   "s": "BTCUSDT",
///   "U": 157,
///   "u": 160,
///   "b": [["50000.00", "1.5"], ["49999.00", "0.5"]],
///   "a": [["50001.00", "1.0"], ["50002.00", "0.3"]]
/// }
/// ```
pub fn parse_binance_depth_update(
    raw: &Value,
    asset: Asset,
    venue: Venue,
) -> Result<uforia_ms_types::L2Update, CmError> {
    let ts_ms = raw
        .get("E")
        .and_then(Value::as_i64)
        .ok_or_else(|| CmError::Parse {
            context: "binance_depth".into(),
            msg: "missing 'E' field".into(),
        })?;

    let timestamp = Utc
        .timestamp_millis_opt(ts_ms)
        .single()
        .ok_or_else(|| CmError::Parse {
            context: "binance_depth".into(),
            msg: format!("invalid timestamp: {}", ts_ms),
        })?;

    let first_update_id = raw.get("U").and_then(Value::as_u64);
    let last_update_id = raw.get("u").and_then(Value::as_u64);

    let bids = parse_price_levels(raw.get("b"), "binance_depth bids")?;
    let asks = parse_price_levels(raw.get("a"), "binance_depth asks")?;

    Ok(uforia_ms_types::L2Update {
        timestamp,
        asset,
        venue,
        bids,
        asks,
        first_update_id,
        last_update_id,
    })
}

/// Parse Bybit trade from publicTrade topic.
///
/// ```json
/// {
///   "T": 1672515782136,
///   "s": "BTCUSDT",
///   "S": "Buy",
///   "v": "0.001",
///   "p": "50000.00",
///   "i": "12345"
/// }
/// ```
pub fn parse_bybit_trade(raw: &Value, asset: Asset, venue: Venue) -> Result<Trade, CmError> {
    let ts_ms = raw
        .get("T")
        .and_then(Value::as_i64)
        .ok_or_else(|| CmError::Parse {
            context: "bybit_trade".into(),
            msg: "missing 'T' field".into(),
        })?;

    let timestamp = Utc
        .timestamp_millis_opt(ts_ms)
        .single()
        .ok_or_else(|| CmError::Parse {
            context: "bybit_trade".into(),
            msg: format!("invalid timestamp: {}", ts_ms),
        })?;

    let price: f64 = raw
        .get("p")
        .and_then(Value::as_str)
        .ok_or_else(|| CmError::Parse {
            context: "bybit_trade".into(),
            msg: "missing 'p' field".into(),
        })?
        .parse()
        .map_err(|e| CmError::Parse {
            context: "bybit_trade".into(),
            msg: format!("cannot parse price: {}", e),
        })?;

    let size: f64 = raw
        .get("v")
        .and_then(Value::as_str)
        .ok_or_else(|| CmError::Parse {
            context: "bybit_trade".into(),
            msg: "missing 'v' field".into(),
        })?
        .parse()
        .map_err(|e| CmError::Parse {
            context: "bybit_trade".into(),
            msg: format!("cannot parse size: {}", e),
        })?;

    let side_str = raw
        .get("S")
        .and_then(Value::as_str)
        .ok_or_else(|| CmError::Parse {
            context: "bybit_trade".into(),
            msg: "missing 'S' field".into(),
        })?;

    let side = match side_str {
        "Buy" => Side::Buy,
        "Sell" => Side::Sell,
        other => {
            return Err(CmError::Parse {
                context: "bybit_trade".into(),
                msg: format!("unknown side: {}", other),
            })
        }
    };

    let trade_id = raw.get("i").and_then(Value::as_str).map(String::from);

    Ok(Trade {
        timestamp,
        asset,
        venue,
        price,
        size,
        side,
        trade_id,
    })
}

/// Parse Bybit orderbook snapshot/update.
///
/// ```json
/// {
///   "s": "BTCUSDT",
///   "b": [["50000.00", "1.5"], ["49999.00", "0.5"]],
///   "a": [["50001.00", "1.0"], ["50002.00", "0.3"]],
///   "u": 12345,
///   "seq": 67890
/// }
/// ```
pub fn parse_bybit_orderbook(
    raw: &Value,
    asset: Asset,
    venue: Venue,
    ts: DateTime<Utc>,
) -> Result<uforia_ms_types::L2Snapshot, CmError> {
    let bids = parse_price_levels(raw.get("b"), "bybit_orderbook bids")?;
    let asks = parse_price_levels(raw.get("a"), "bybit_orderbook asks")?;
    let update_id = raw.get("u").and_then(Value::as_u64);

    Ok(uforia_ms_types::L2Snapshot {
        timestamp: ts,
        asset,
        venue,
        bids,
        asks,
        last_update_id: update_id,
    })
}

/// Parse price level arrays like [["price", "size"], ...].
fn parse_price_levels(
    val: Option<&Value>,
    context: &str,
) -> Result<Vec<uforia_ms_types::BookLevel>, CmError> {
    let arr = val
        .and_then(Value::as_array)
        .ok_or_else(|| CmError::Parse {
            context: context.into(),
            msg: "missing or non-array price levels".into(),
        })?;

    arr.iter()
        .map(|level| {
            let pair = level.as_array().ok_or_else(|| CmError::Parse {
                context: context.into(),
                msg: "level is not an array".into(),
            })?;
            if pair.len() < 2 {
                return Err(CmError::Parse {
                    context: context.into(),
                    msg: "level array too short".into(),
                });
            }
            let price: f64 = pair[0]
                .as_str()
                .ok_or_else(|| CmError::Parse {
                    context: context.into(),
                    msg: "price is not a string".into(),
                })?
                .parse()
                .map_err(|e| CmError::Parse {
                    context: context.into(),
                    msg: format!("cannot parse level price: {}", e),
                })?;
            let size: f64 = pair[1]
                .as_str()
                .ok_or_else(|| CmError::Parse {
                    context: context.into(),
                    msg: "size is not a string".into(),
                })?
                .parse()
                .map_err(|e| CmError::Parse {
                    context: context.into(),
                    msg: format!("cannot parse level size: {}", e),
                })?;
            Ok(uforia_ms_types::BookLevel {
                price: OrderedFloat(price),
                size,
            })
        })
        .collect()
}

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

    #[test]
    fn test_parse_binance_spot_trade_buyer_maker() {
        let raw = json!({
            "e": "trade",
            "E": 1672515782136i64,
            "s": "BTCUSDT",
            "t": 12345u64,
            "p": "50000.50",
            "q": "0.001",
            "T": 1672515782136i64,
            "m": true,
            "M": true
        });

        let trade = parse_binance_spot_trade(&raw, Asset::Btc).unwrap();
        assert_eq!(trade.asset, Asset::Btc);
        assert_eq!(trade.venue, Venue::BinanceSpot);
        assert!((trade.price - 50000.50).abs() < 1e-10);
        assert!((trade.size - 0.001).abs() < 1e-10);
        // m=true => buyer is maker => taker is seller
        assert_eq!(trade.side, Side::Sell);
        assert_eq!(trade.trade_id, Some("12345".to_string()));
    }

    #[test]
    fn test_parse_binance_spot_trade_seller_maker() {
        let raw = json!({
            "e": "trade",
            "E": 1672515782136i64,
            "s": "ETHUSDT",
            "t": 67890u64,
            "p": "3000.25",
            "q": "0.5",
            "T": 1672515782136i64,
            "m": false,
            "M": true
        });

        let trade = parse_binance_spot_trade(&raw, Asset::Eth).unwrap();
        assert_eq!(trade.asset, Asset::Eth);
        assert!((trade.price - 3000.25).abs() < 1e-10);
        assert!((trade.size - 0.5).abs() < 1e-10);
        // m=false => seller is maker => taker is buyer
        assert_eq!(trade.side, Side::Buy);
    }

    #[test]
    fn test_parse_binance_spot_trade_missing_price() {
        let raw = json!({
            "e": "trade",
            "T": 1672515782136i64,
            "q": "0.001",
            "m": true
        });

        let result = parse_binance_spot_trade(&raw, Asset::Btc);
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_binance_futures_trade() {
        let raw = json!({
            "e": "trade",
            "E": 1672515782136i64,
            "s": "BTCUSDT",
            "t": 99999u64,
            "p": "51000.00",
            "q": "0.01",
            "T": 1672515782136i64,
            "m": false,
            "M": true
        });

        let trade = parse_binance_futures_trade(&raw, Asset::Btc).unwrap();
        assert_eq!(trade.venue, Venue::BinanceFutures);
        assert!((trade.price - 51000.0).abs() < 1e-10);
        // m=false => seller is maker => taker is buyer
        assert_eq!(trade.side, Side::Buy);
    }

    #[test]
    fn test_parse_binance_depth_update() {
        let raw = json!({
            "e": "depthUpdate",
            "E": 1672515782136i64,
            "s": "BTCUSDT",
            "U": 157u64,
            "u": 160u64,
            "b": [["50000.00", "1.5"], ["49999.00", "0.5"]],
            "a": [["50001.00", "1.0"], ["50002.00", "0.3"]]
        });

        let update = parse_binance_depth_update(&raw, Asset::Btc, Venue::BinanceSpot).unwrap();
        assert_eq!(update.bids.len(), 2);
        assert_eq!(update.asks.len(), 2);
        assert!((update.bids[0].price.into_inner() - 50000.0).abs() < 1e-10);
        assert!((update.bids[0].size - 1.5).abs() < 1e-10);
        assert!((update.asks[0].price.into_inner() - 50001.0).abs() < 1e-10);
        assert_eq!(update.first_update_id, Some(157));
        assert_eq!(update.last_update_id, Some(160));
    }

    #[test]
    fn test_parse_bybit_trade() {
        let raw = json!({
            "T": 1672515782136i64,
            "s": "BTCUSDT",
            "S": "Buy",
            "v": "0.001",
            "p": "50000.00",
            "i": "trade-123"
        });

        let trade = parse_bybit_trade(&raw, Asset::Btc, Venue::BybitSpot).unwrap();
        assert_eq!(trade.asset, Asset::Btc);
        assert_eq!(trade.venue, Venue::BybitSpot);
        assert!((trade.price - 50000.0).abs() < 1e-10);
        assert!((trade.size - 0.001).abs() < 1e-10);
        assert_eq!(trade.side, Side::Buy);
        assert_eq!(trade.trade_id, Some("trade-123".to_string()));
    }

    #[test]
    fn test_parse_bybit_trade_sell() {
        let raw = json!({
            "T": 1672515782136i64,
            "s": "ETHUSDT",
            "S": "Sell",
            "v": "1.5",
            "p": "3000.00",
            "i": "trade-456"
        });

        let trade = parse_bybit_trade(&raw, Asset::Eth, Venue::BybitPerps).unwrap();
        assert_eq!(trade.side, Side::Sell);
        assert_eq!(trade.venue, Venue::BybitPerps);
    }

    #[test]
    fn test_parse_bybit_orderbook() {
        let raw = json!({
            "s": "BTCUSDT",
            "b": [["50000.00", "1.5"], ["49999.00", "0.5"]],
            "a": [["50001.00", "1.0"], ["50002.00", "0.3"]],
            "u": 12345u64,
            "seq": 67890u64
        });

        let ts = Utc::now();
        let snap = parse_bybit_orderbook(&raw, Asset::Btc, Venue::BybitSpot, ts).unwrap();
        assert_eq!(snap.bids.len(), 2);
        assert_eq!(snap.asks.len(), 2);
        assert_eq!(snap.last_update_id, Some(12345));
    }

    #[test]
    fn test_parse_price_levels_empty() {
        let raw = json!([]);
        let levels = parse_price_levels(Some(&raw), "test").unwrap();
        assert!(levels.is_empty());
    }

    #[test]
    fn test_parse_price_levels_missing() {
        let result = parse_price_levels(None, "test");
        assert!(result.is_err());
    }
}
