//! Native WebSocket implementation using tokio-tungstenite.

use std::time::Duration;

use futures_util::{SinkExt, StreamExt};
use tokio::time::timeout;
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::Message;

use super::messages::{StatusUpdate, VerificationSubscribeEvent, WsMessage};
use super::transport::WebSocketTransport;
use crate::config::WebSocketConfig;
use crate::error::WebSocketError;
use crate::types::RequestId;

type WsStream =
    tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;

/// Native WebSocket transport using tokio-tungstenite.
pub struct NativeWebSocket {
    url: String,
    request_id: String,
    config: WebSocketConfig,
    stream: Option<WsStream>,
}

impl NativeWebSocket {
    /// Create a new native WebSocket transport.
    pub fn new(
        base_url: &str,
        request_id: &RequestId,
        config: &WebSocketConfig,
    ) -> Result<Self, WebSocketError> {
        // Convert HTTP URL to WebSocket URL
        let ws_url = base_url
            .replace("https://", "wss://")
            .replace("http://", "ws://");
        let url = format!("{}/ws", ws_url.trim_end_matches('/'));

        Ok(Self {
            url,
            request_id: request_id.to_string(),
            config: config.clone(),
            stream: None,
        })
    }
}

impl WebSocketTransport for NativeWebSocket {
    async fn connect(&mut self) -> Result<(), WebSocketError> {
        let connect_timeout = Duration::from_secs(self.config.connect_timeout_seconds);

        let (stream, _response) = timeout(connect_timeout, connect_async(&self.url))
            .await
            .map_err(|_| WebSocketError::ConnectionFailed("Connection timeout".into()))?
            .map_err(|e| WebSocketError::ConnectionFailed(e.to_string()))?;

        self.stream = Some(stream);

        // Send subscription message
        let subscribe_event = VerificationSubscribeEvent::new(&self.request_id);
        let msg = serde_json::to_string(&subscribe_event)
            .map_err(|e| WebSocketError::MessageParseError(e.to_string()))?;
        self.send(&msg).await?;

        Ok(())
    }

    async fn disconnect(&mut self) -> Result<(), WebSocketError> {
        if let Some(mut stream) = self.stream.take() {
            let _ = stream.close(None).await;
        }
        Ok(())
    }

    fn is_connected(&self) -> bool {
        self.stream.is_some()
    }

    async fn send(&mut self, message: &str) -> Result<(), WebSocketError> {
        let stream = self
            .stream
            .as_mut()
            .ok_or_else(|| WebSocketError::ConnectionClosed {
                code: 0,
                reason: "Not connected".into(),
            })?;

        stream
            .send(Message::Text(message.into()))
            .await
            .map_err(|e| WebSocketError::ConnectionFailed(e.to_string()))
    }

    async fn receive(&mut self) -> Result<StatusUpdate, WebSocketError> {
        let stream = self
            .stream
            .as_mut()
            .ok_or_else(|| WebSocketError::ConnectionClosed {
                code: 0,
                reason: "Not connected".into(),
            })?;

        let receive_timeout = Duration::from_secs(self.config.receive_timeout_seconds);

        loop {
            let msg = timeout(receive_timeout, stream.next())
                .await
                .map_err(|_| WebSocketError::ReceiveTimeout)?
                .ok_or_else(|| WebSocketError::ConnectionClosed {
                    code: 1000,
                    reason: "Stream ended".into(),
                })?
                .map_err(|e| WebSocketError::ConnectionClosed {
                    code: 0,
                    reason: e.to_string(),
                })?;

            match msg {
                Message::Text(text) => {
                    let ws_msg: WsMessage = serde_json::from_str(&text)
                        .map_err(|e| WebSocketError::MessageParseError(e.to_string()))?;
                    return Ok(StatusUpdate::from_message(&ws_msg));
                }
                Message::Ping(data) => {
                    let _ = stream.send(Message::Pong(data)).await;
                }
                Message::Close(frame) => {
                    return Err(WebSocketError::ConnectionClosed {
                        code: frame.as_ref().map_or(1000, |f| f.code.into()),
                        reason: frame.map_or_else(String::new, |f| f.reason.to_string()),
                    });
                }
                _ => {} // Skip ping/pong/other messages
            }
        }
    }
}
