//! WebSocket streaming adapter for real-time market data.
//!
//! Connects to Binance WebSocket API and pushes candle updates through an mpsc channel.

use std::sync::mpsc;
use std::thread;
use termichart_core::Candle;

/// A WebSocket stream that connects to Binance and receives real-time kline updates.
pub struct WsStream {
    symbol: String,
    interval: String,
}

impl WsStream {
    /// Create a new WebSocket stream for the given symbol and interval.
    pub fn new(symbol: impl Into<String>, interval: impl Into<String>) -> Self {
        Self {
            symbol: symbol.into().to_lowercase(),
            interval: interval.into(),
        }
    }

    /// Start streaming and return a receiver for candle updates.
    ///
    /// Spawns a background thread that maintains the WebSocket connection.
    /// Returns a receiver that yields `Candle` values as they arrive.
    pub fn start(&self) -> mpsc::Receiver<Candle> {
        let (tx, rx) = mpsc::channel();
        let url = format!(
            "wss://stream.binance.com:9443/ws/{}@kline_{}",
            self.symbol, self.interval
        );

        let url_clone = url.clone();
        thread::spawn(move || {
            if let Err(e) = run_ws_loop(&url_clone, &tx) {
                eprintln!("WebSocket error: {}", e);
            }
        });

        rx
    }
}

fn run_ws_loop(
    url: &str,
    tx: &mpsc::Sender<Candle>,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
    use tungstenite::connect;

    let parsed_url = url::Url::parse(url)?;
    let (mut socket, _response) = connect(parsed_url)?;

    loop {
        let msg = socket.read()?;

        match msg {
            tungstenite::Message::Text(text) => {
                if let Some(candle) = parse_kline_message(&text) {
                    if tx.send(candle).is_err() {
                        // Receiver dropped, exit
                        break;
                    }
                }
            }
            tungstenite::Message::Close(_) => break,
            tungstenite::Message::Ping(data) => {
                let _ = socket.write(tungstenite::Message::Pong(data));
            }
            _ => {}
        }
    }

    Ok(())
}

/// Parse a Binance kline WebSocket message into a Candle.
///
/// Message format:
/// ```json
/// {
///   "e": "kline",
///   "k": {
///     "t": 1234567890000,
///     "o": "100.00",
///     "h": "105.00",
///     "l": "95.00",
///     "c": "102.00",
///     "v": "1000.00"
///   }
/// }
/// ```
fn parse_kline_message(text: &str) -> Option<Candle> {
    let v: serde_json::Value = serde_json::from_str(text).ok()?;
    let k = v.get("k")?;

    let time = k.get("t")?.as_f64()? / 1000.0;
    let open = k.get("o")?.as_str()?.parse::<f64>().ok()?;
    let high = k.get("h")?.as_str()?.parse::<f64>().ok()?;
    let low = k.get("l")?.as_str()?.parse::<f64>().ok()?;
    let close = k.get("c")?.as_str()?.parse::<f64>().ok()?;
    let volume = k.get("v")?.as_str()?.parse::<f64>().ok()?;

    Some(Candle {
        time,
        open,
        high,
        low,
        close,
        volume,
    })
}

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

    #[test]
    fn parse_valid_kline() {
        let msg = r#"{"e":"kline","E":1234567890123,"s":"BTCUSDT","k":{"t":1234567890000,"T":1234567949999,"s":"BTCUSDT","i":"1m","f":100,"L":200,"o":"50000.00","c":"50100.00","h":"50200.00","l":"49900.00","v":"10.50","n":300,"x":false,"q":"525000.00","V":"5.00","Q":"250000.00","B":"0"}}"#;
        let candle = parse_kline_message(msg).unwrap();
        assert!((candle.open - 50000.0).abs() < 0.01);
        assert!((candle.close - 50100.0).abs() < 0.01);
        assert!((candle.high - 50200.0).abs() < 0.01);
        assert!((candle.low - 49900.0).abs() < 0.01);
        assert!((candle.volume - 10.5).abs() < 0.01);
        assert!((candle.time - 1234567890.0).abs() < 0.01);
    }

    #[test]
    fn parse_invalid_json() {
        assert!(parse_kline_message("not json").is_none());
    }

    #[test]
    fn parse_missing_fields() {
        let msg = r#"{"e":"kline","k":{"t":1000}}"#;
        assert!(parse_kline_message(msg).is_none());
    }

    #[test]
    fn ws_stream_creation() {
        let ws = WsStream::new("btcusdt", "1m");
        assert_eq!(ws.symbol, "btcusdt");
        assert_eq!(ws.interval, "1m");
    }
}
