// US-011: WebSocket message types — complete struct definitions and manual Deserialize impl.
// The outer combined-stream wrapper { "stream": "...", "data": {...} } is already unwrapped
// by the Wave 5 WebSocketClient before these types are constructed.

use serde::Deserialize;

// ---------------------------------------------------------------------------
// Event structs
// ---------------------------------------------------------------------------

/// Aggregate trade event — dispatched when `"e": "aggTrade"`.
#[derive(Debug, Deserialize)]
pub struct AggTradeEvent {
    #[serde(rename = "e")]
    pub event_type: String,
    #[serde(rename = "E")]
    pub event_time: u64,
    #[serde(rename = "s")]
    pub symbol: String,
    #[serde(rename = "a")]
    pub agg_trade_id: u64,
    #[serde(rename = "p")]
    pub price: String,
    #[serde(rename = "q")]
    pub quantity: String,
    #[serde(rename = "f")]
    pub first_trade_id: u64,
    #[serde(rename = "l")]
    pub last_trade_id: u64,
    #[serde(rename = "T")]
    pub trade_time: u64,
    #[serde(rename = "m")]
    pub is_buyer_maker: bool,
}

/// Mark price event — dispatched when `"e": "markPriceUpdate"`.
#[derive(Debug, Deserialize)]
pub struct MarkPriceEvent {
    #[serde(rename = "e")]
    pub event_type: String,
    #[serde(rename = "E")]
    pub event_time: u64,
    #[serde(rename = "s")]
    pub symbol: String,
    #[serde(rename = "p")]
    pub mark_price: String,
    #[serde(rename = "i")]
    pub index_price: String,
    #[serde(rename = "P")]
    pub estimated_settle_price: String,
    #[serde(rename = "r")]
    pub last_funding_rate: String,
    #[serde(rename = "R", default)]
    pub interest_rate: String,
    #[serde(rename = "T")]
    pub next_funding_time: u64,
}

/// Kline/candlestick event — dispatched when `"e": "kline"`.
#[derive(Debug, Deserialize)]
pub struct KlineEvent {
    #[serde(rename = "e")]
    pub event_type: String,
    #[serde(rename = "E")]
    pub event_time: u64,
    #[serde(rename = "s")]
    pub symbol: String,
    #[serde(rename = "k")]
    pub kline: KlineData,
}

/// Inner kline data nested under the `k` field of a `KlineEvent`.
#[derive(Debug, Deserialize)]
pub struct KlineData {
    #[serde(rename = "t")]
    pub start_time: u64,
    #[serde(rename = "T")]
    pub close_time: u64,
    #[serde(rename = "s")]
    pub symbol: String,
    /// Interval string e.g. `"1m"`, `"1h"`.
    #[serde(rename = "i")]
    pub interval: String,
    #[serde(rename = "f")]
    pub first_trade_id: i64,
    #[serde(rename = "L")]
    pub last_trade_id: i64,
    #[serde(rename = "o")]
    pub open: String,
    #[serde(rename = "c")]
    pub close: String,
    #[serde(rename = "h")]
    pub high: String,
    #[serde(rename = "l")]
    pub low: String,
    #[serde(rename = "v")]
    pub volume: String,
    #[serde(rename = "n")]
    pub trade_count: u64,
    #[serde(rename = "x")]
    pub is_closed: bool,
    #[serde(rename = "q")]
    pub quote_volume: String,
    #[serde(rename = "V")]
    pub taker_buy_volume: String,
    #[serde(rename = "Q")]
    pub taker_buy_quote_volume: String,
}

/// 24-hour mini ticker event — dispatched when `"e": "24hrMiniTicker"`.
#[derive(Debug, Deserialize)]
pub struct MiniTickerEvent {
    #[serde(rename = "e")]
    pub event_type: String,
    #[serde(rename = "E")]
    pub event_time: u64,
    #[serde(rename = "s")]
    pub symbol: String,
    #[serde(rename = "c")]
    pub close_price: String,
    #[serde(rename = "o")]
    pub open_price: String,
    #[serde(rename = "h")]
    pub high_price: String,
    #[serde(rename = "l")]
    pub low_price: String,
    #[serde(rename = "v")]
    pub volume: String,
    #[serde(rename = "q")]
    pub quote_volume: String,
}

/// Full 24-hour ticker event — dispatched when `"e": "24hrTicker"`.
#[derive(Debug, Deserialize)]
pub struct TickerEvent {
    #[serde(rename = "e")]
    pub event_type: String,
    #[serde(rename = "E")]
    pub event_time: u64,
    #[serde(rename = "s")]
    pub symbol: String,
    #[serde(rename = "p")]
    pub price_change: String,
    #[serde(rename = "P")]
    pub price_change_percent: String,
    #[serde(rename = "w")]
    pub weighted_avg_price: String,
    #[serde(rename = "c")]
    pub last_price: String,
    #[serde(rename = "Q")]
    pub last_qty: String,
    #[serde(rename = "o")]
    pub open_price: String,
    #[serde(rename = "h")]
    pub high_price: String,
    #[serde(rename = "l")]
    pub low_price: String,
    #[serde(rename = "v")]
    pub volume: String,
    #[serde(rename = "q")]
    pub quote_volume: String,
    #[serde(rename = "O")]
    pub open_time: u64,
    #[serde(rename = "C")]
    pub close_time: u64,
    #[serde(rename = "F")]
    pub first_trade_id: i64,
    #[serde(rename = "L")]
    pub last_trade_id: i64,
    #[serde(rename = "n")]
    pub trade_count: u64,
}

/// Best-bid/ask book ticker event — no `"e"` field; detected by `"u"` key presence without `"e"`.
#[derive(Debug, Deserialize)]
pub struct BookTickerEvent {
    #[serde(rename = "u")]
    pub update_id: u64,
    #[serde(rename = "s")]
    pub symbol: String,
    #[serde(rename = "b")]
    pub best_bid_price: String,
    #[serde(rename = "B")]
    pub best_bid_qty: String,
    #[serde(rename = "a")]
    pub best_ask_price: String,
    #[serde(rename = "A")]
    pub best_ask_qty: String,
}

/// Liquidation force order event — dispatched when `"e": "forceOrder"`.
#[derive(Debug, Deserialize)]
pub struct ForceOrderEvent {
    #[serde(rename = "e")]
    pub event_type: String,
    #[serde(rename = "E")]
    pub event_time: u64,
    #[serde(rename = "o")]
    pub order: ForceOrderData,
}

/// Inner order data for a force-order event.
#[derive(Debug, Deserialize)]
pub struct ForceOrderData {
    #[serde(rename = "s")]
    pub symbol: String,
    #[serde(rename = "S")]
    pub side: String,
    #[serde(rename = "o")]
    pub order_type: String,
    #[serde(rename = "f")]
    pub time_in_force: String,
    #[serde(rename = "q")]
    pub quantity: String,
    #[serde(rename = "p")]
    pub price: String,
    /// May be `"0"` for orders not yet filled — kept as String (BR-010).
    #[serde(rename = "ap")]
    pub average_price: String,
    #[serde(rename = "X")]
    pub order_status: String,
    #[serde(rename = "l")]
    pub last_filled_qty: String,
    #[serde(rename = "z")]
    pub accumulated_qty: String,
    #[serde(rename = "T")]
    pub trade_time: u64,
}

/// Partial depth snapshot event — no `"e"` field; detected by `"lastUpdateId"` key.
/// Dispatched for `<symbol>@depth5`, `@depth10`, `@depth20` streams.
#[derive(Debug, Deserialize)]
pub struct PartialDepthEvent {
    #[serde(rename = "lastUpdateId")]
    pub last_update_id: u64,
    pub bids: Vec<[String; 2]>,
    pub asks: Vec<[String; 2]>,
}

/// Differential depth stream event — dispatched when `"e": "depthUpdate"`.
#[derive(Debug, Deserialize)]
pub struct DiffDepthEvent {
    #[serde(rename = "e")]
    pub event_type: String,
    #[serde(rename = "E")]
    pub event_time: u64,
    #[serde(rename = "s")]
    pub symbol: String,
    #[serde(rename = "U")]
    pub first_update_id: u64,
    #[serde(rename = "u")]
    pub final_update_id: u64,
    #[serde(rename = "b")]
    pub bids: Vec<[String; 2]>,
    #[serde(rename = "a")]
    pub asks: Vec<[String; 2]>,
}

// ---------------------------------------------------------------------------
// WebSocketMessage enum — manual Deserialize impl
// ---------------------------------------------------------------------------

/// Typed WebSocket message.
///
/// Deserialization dispatches on the `e` (event_type) field for object messages.
/// Array JSON (all-symbol streams such as `!markPrice@arr`) is detected by the
/// top-level value being a JSON array and dispatched on the first element's `e`.
///
/// Special cases without an `"e"` field:
/// - `PartialDepth` — detected by the presence of `"lastUpdateId"`.
/// - `BookTicker` — detected by `"u"` key with no `"e"` field.
///
/// Any unrecognised or malformed message is mapped to `Unknown(serde_json::Value)`
/// and a `tracing::warn!` is emitted — the SDK never panics on unknown events.
#[derive(Debug)]
pub enum WebSocketMessage {
    /// Single aggregate trade event (`"e": "aggTrade"`).
    AggTrade(AggTradeEvent),
    /// Single mark price update (`"e": "markPriceUpdate"`).
    MarkPrice(MarkPriceEvent),
    /// All-symbol mark price array (`!markPrice@arr`).
    AllMarkPrices(Vec<MarkPriceEvent>),
    /// Kline/candlestick event (`"e": "kline"`).
    Kline(KlineEvent),
    /// 24-hour mini ticker for a single symbol.
    MiniTicker(MiniTickerEvent),
    /// All-symbol 24-hour mini tickers (`!miniTicker@arr`).
    AllMiniTickers(Vec<MiniTickerEvent>),
    /// Full 24-hour ticker for a single symbol.
    Ticker(TickerEvent),
    /// All-symbol full 24-hour tickers.
    AllTickers(Vec<TickerEvent>),
    /// Best bid/ask book ticker (detected by `"u"` key without `"e"`).
    BookTicker(BookTickerEvent),
    /// Liquidation force order event (`"e": "forceOrder"`).
    ForceOrder(ForceOrderEvent),
    /// All liquidation force orders.
    AllForceOrders(Vec<ForceOrderEvent>),
    /// Partial depth snapshot (detected by `"lastUpdateId"` key).
    PartialDepth(PartialDepthEvent),
    /// Differential depth update (`"e": "depthUpdate"`).
    DiffDepth(DiffDepthEvent),
    /// Unknown or unrecognised message — captured as raw JSON for forward compatibility.
    Unknown(serde_json::Value),
}

// US-011: manual Deserialize for WebSocketMessage — dispatches on "e" field.
impl<'de> serde::Deserialize<'de> for WebSocketMessage {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let v = serde_json::Value::deserialize(deserializer)?;

        // ---------------------------------------------------------------
        // Array = all-symbol stream (e.g. !markPrice@arr, !miniTicker@arr)
        // ---------------------------------------------------------------
        if let Some(arr) = v.as_array() {
            if arr.is_empty() {
                tracing::warn!("Received empty WebSocket array message");
                return Ok(WebSocketMessage::Unknown(v));
            }
            let first = &arr[0];
            match first.get("e").and_then(|e| e.as_str()).unwrap_or("") {
                "markPriceUpdate" => {
                    match serde_json::from_value::<Vec<MarkPriceEvent>>(v.clone()) {
                        Ok(events) => return Ok(WebSocketMessage::AllMarkPrices(events)),
                        Err(err) => {
                            tracing::warn!(error = ?err, "Failed to parse AllMarkPrices array");
                            return Ok(WebSocketMessage::Unknown(v));
                        }
                    }
                }
                "24hrMiniTicker" => {
                    match serde_json::from_value::<Vec<MiniTickerEvent>>(v.clone()) {
                        Ok(events) => return Ok(WebSocketMessage::AllMiniTickers(events)),
                        Err(err) => {
                            tracing::warn!(error = ?err, "Failed to parse AllMiniTickers array");
                            return Ok(WebSocketMessage::Unknown(v));
                        }
                    }
                }
                "24hrTicker" => {
                    match serde_json::from_value::<Vec<TickerEvent>>(v.clone()) {
                        Ok(events) => return Ok(WebSocketMessage::AllTickers(events)),
                        Err(err) => {
                            tracing::warn!(error = ?err, "Failed to parse AllTickers array");
                            return Ok(WebSocketMessage::Unknown(v));
                        }
                    }
                }
                "forceOrder" => {
                    match serde_json::from_value::<Vec<ForceOrderEvent>>(v.clone()) {
                        Ok(events) => return Ok(WebSocketMessage::AllForceOrders(events)),
                        Err(err) => {
                            tracing::warn!(error = ?err, "Failed to parse AllForceOrders array");
                            return Ok(WebSocketMessage::Unknown(v));
                        }
                    }
                }
                other => {
                    tracing::warn!(event_type = other, "Unknown WebSocket array event type");
                    return Ok(WebSocketMessage::Unknown(v));
                }
            }
        }

        // ---------------------------------------------------------------
        // Object messages — check for special no-"e" cases first
        // ---------------------------------------------------------------

        // PartialDepth: has "lastUpdateId" but no "e" field.
        if v.get("e").is_none() && v.get("lastUpdateId").is_some() {
            match serde_json::from_value::<PartialDepthEvent>(v.clone()) {
                Ok(event) => return Ok(WebSocketMessage::PartialDepth(event)),
                Err(err) => {
                    tracing::warn!(error = ?err, "Failed to parse PartialDepth event");
                    return Ok(WebSocketMessage::Unknown(v));
                }
            }
        }

        // BookTicker: has "u" (update_id) but no "e" field.
        if v.get("e").is_none() && v.get("u").is_some() {
            match serde_json::from_value::<BookTickerEvent>(v.clone()) {
                Ok(event) => return Ok(WebSocketMessage::BookTicker(event)),
                Err(err) => {
                    tracing::warn!(error = ?err, "Failed to parse BookTicker event");
                    return Ok(WebSocketMessage::Unknown(v));
                }
            }
        }

        // ---------------------------------------------------------------
        // Dispatch by "e" field value
        // ---------------------------------------------------------------
        let event_type = v.get("e").and_then(|e| e.as_str()).unwrap_or("");

        /// Helper: deserialize or fall back to Unknown with a warning.
        macro_rules! parse_event {
            ($variant:ident, $type:ty, $label:literal) => {
                match serde_json::from_value::<$type>(v.clone()) {
                    Ok(event) => Ok(WebSocketMessage::$variant(event)),
                    Err(err) => {
                        tracing::warn!(
                            error = ?err,
                            event_type = $label,
                            "Failed to parse known WebSocket event; falling back to Unknown"
                        );
                        Ok(WebSocketMessage::Unknown(v))
                    }
                }
            };
        }

        match event_type {
            "aggTrade" => parse_event!(AggTrade, AggTradeEvent, "aggTrade"),
            "markPriceUpdate" => parse_event!(MarkPrice, MarkPriceEvent, "markPriceUpdate"),
            "kline" => parse_event!(Kline, KlineEvent, "kline"),
            "24hrMiniTicker" => parse_event!(MiniTicker, MiniTickerEvent, "24hrMiniTicker"),
            "24hrTicker" => parse_event!(Ticker, TickerEvent, "24hrTicker"),
            "forceOrder" => parse_event!(ForceOrder, ForceOrderEvent, "forceOrder"),
            "depthUpdate" => parse_event!(DiffDepth, DiffDepthEvent, "depthUpdate"),
            other => {
                tracing::warn!(event_type = other, "Unknown WebSocket message event type");
                Ok(WebSocketMessage::Unknown(v))
            }
        }
    }
}

// ---------------------------------------------------------------------------
// Unit tests
// ---------------------------------------------------------------------------

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

    #[test]
    fn agg_trade_deserializes_correctly() {
        let json = r#"{
            "e": "aggTrade",
            "E": 1625184000000,
            "s": "BTCUSDT",
            "a": 12345,
            "p": "30000.00",
            "q": "0.001",
            "f": 100,
            "l": 105,
            "T": 1625184000000,
            "m": false
        }"#;
        let msg: WebSocketMessage = serde_json::from_str(json).unwrap();
        assert!(matches!(msg, WebSocketMessage::AggTrade(ref e) if e.symbol == "BTCUSDT"));
        if let WebSocketMessage::AggTrade(event) = msg {
            assert_eq!(event.price, "30000.00");
            assert_eq!(event.quantity, "0.001");
            assert!(!event.is_buyer_maker);
        }
    }

    #[test]
    fn unknown_event_type_maps_to_unknown_variant() {
        let json = r#"{"e":"futureData","x":"someValue"}"#;
        let msg: WebSocketMessage = serde_json::from_str(json).unwrap();
        assert!(matches!(msg, WebSocketMessage::Unknown(_)));
    }

    #[test]
    fn kline_event_deserializes_correctly() {
        let json = r#"{
            "e": "kline",
            "E": 1625184000000,
            "s": "BTCUSDT",
            "k": {
                "t": 1625184000000, "T": 1625184059999, "s": "BTCUSDT",
                "i": "1m", "f": 1, "L": 10,
                "o": "30000", "c": "30100", "h": "30200", "l": "29900",
                "v": "100", "n": 10, "x": false,
                "q": "3010000", "V": "50", "Q": "1505000"
            }
        }"#;
        let msg: WebSocketMessage = serde_json::from_str(json).unwrap();
        assert!(matches!(msg, WebSocketMessage::Kline(ref e) if e.kline.interval == "1m"));
        if let WebSocketMessage::Kline(event) = msg {
            assert!(!event.kline.is_closed);
            assert_eq!(event.kline.open, "30000");
            assert_eq!(event.kline.close, "30100");
        }
    }

    #[test]
    fn partial_depth_deserializes_correctly() {
        let json = r#"{"lastUpdateId":123456,"bids":[["30000","1.5"],["29999","2.0"]],"asks":[["30001","0.5"]]}"#;
        let msg: WebSocketMessage = serde_json::from_str(json).unwrap();
        assert!(matches!(msg, WebSocketMessage::PartialDepth(_)));
        if let WebSocketMessage::PartialDepth(event) = msg {
            assert_eq!(event.bids[0][0], "30000");
            assert_eq!(event.bids[0][1], "1.5");
        }
    }

    #[test]
    fn all_mark_prices_array_deserializes_correctly() {
        let json = r#"[{"e":"markPriceUpdate","E":1625184000000,"s":"BTCUSDT","p":"30000","i":"30001","P":"29999","r":"0.0001","T":1625198400000}]"#;
        let msg: WebSocketMessage = serde_json::from_str(json).unwrap();
        assert!(matches!(msg, WebSocketMessage::AllMarkPrices(_)));
        if let WebSocketMessage::AllMarkPrices(events) = msg {
            assert_eq!(events.len(), 1);
            assert_eq!(events[0].symbol, "BTCUSDT");
        }
    }

    #[test]
    fn mark_price_single_deserializes_correctly() {
        let json = r#"{
            "e": "markPriceUpdate",
            "E": 1625184000000,
            "s": "BTCUSDT",
            "p": "30000.00",
            "i": "30001.00",
            "P": "29999.00",
            "r": "0.0001",
            "R": "0.000050",
            "T": 1625198400000
        }"#;
        let msg: WebSocketMessage = serde_json::from_str(json).unwrap();
        assert!(matches!(msg, WebSocketMessage::MarkPrice(_)));
        if let WebSocketMessage::MarkPrice(event) = msg {
            assert_eq!(event.symbol, "BTCUSDT");
            assert_eq!(event.mark_price, "30000.00");
            assert_eq!(event.last_funding_rate, "0.0001");
            assert_eq!(event.interest_rate, "0.000050");
            assert_eq!(event.next_funding_time, 1625198400000);
        }
    }

    #[test]
    fn book_ticker_deserializes_correctly() {
        let json = r#"{"u":12345,"s":"BTCUSDT","b":"30000","B":"1.5","a":"30001","A":"0.5"}"#;
        let msg: WebSocketMessage = serde_json::from_str(json).unwrap();
        assert!(matches!(msg, WebSocketMessage::BookTicker(ref e) if e.symbol == "BTCUSDT"));
        if let WebSocketMessage::BookTicker(event) = msg {
            assert_eq!(event.best_bid_price, "30000");
            assert_eq!(event.best_ask_price, "30001");
        }
    }

    #[test]
    fn diff_depth_deserializes_correctly() {
        let json = r#"{
            "e": "depthUpdate",
            "E": 1625184000000,
            "s": "BTCUSDT",
            "U": 100,
            "u": 200,
            "b": [["29999","1.0"],["29998","2.0"]],
            "a": [["30001","0.5"]]
        }"#;
        let msg: WebSocketMessage = serde_json::from_str(json).unwrap();
        assert!(matches!(msg, WebSocketMessage::DiffDepth(ref e) if e.symbol == "BTCUSDT"));
        if let WebSocketMessage::DiffDepth(event) = msg {
            assert_eq!(event.first_update_id, 100);
            assert_eq!(event.final_update_id, 200);
            assert_eq!(event.bids[0][0], "29999");
        }
    }

    #[test]
    fn mini_ticker_deserializes_correctly() {
        let json = r#"{
            "e": "24hrMiniTicker",
            "E": 1625184000000,
            "s": "ETHUSDT",
            "c": "2000.00",
            "o": "1950.00",
            "h": "2050.00",
            "l": "1900.00",
            "v": "5000",
            "q": "9750000"
        }"#;
        let msg: WebSocketMessage = serde_json::from_str(json).unwrap();
        assert!(matches!(msg, WebSocketMessage::MiniTicker(ref e) if e.symbol == "ETHUSDT"));
        if let WebSocketMessage::MiniTicker(event) = msg {
            assert_eq!(event.close_price, "2000.00");
            assert_eq!(event.volume, "5000");
        }
    }
}
