//! WASM WebSocket implementation using web-sys.

use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;

use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::{CloseEvent, Event, MessageEvent, WebSocket};

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

/// WASM WebSocket transport using browser's WebSocket API.
pub struct WasmWebSocket {
    url: String,
    request_id: String,
    ws: Option<WebSocket>,
    message_queue: Rc<RefCell<VecDeque<String>>>,
    error_queue: Rc<RefCell<VecDeque<String>>>,
    closed: Rc<RefCell<bool>>,
}

impl WasmWebSocket {
    /// Create a new WASM WebSocket transport.
    pub fn new(
        base_url: &str,
        request_id: &RequestId,
        _config: &WebSocketConfig,
    ) -> Result<Self, WebSocketError> {
        let ws_url = base_url
            .replace("https://", "wss://")
            .replace("http://", "ws://");
        // Include request_id as query parameter for IDP authentication
        let url = format!(
            "{}/ws?request_id={}",
            ws_url.trim_end_matches('/'),
            urlencoding::encode(request_id.as_str())
        );

        Ok(Self {
            url,
            request_id: request_id.to_string(),
            ws: None,
            message_queue: Rc::new(RefCell::new(VecDeque::new())),
            error_queue: Rc::new(RefCell::new(VecDeque::new())),
            closed: Rc::new(RefCell::new(false)),
        })
    }
}

impl WebSocketTransport for WasmWebSocket {
    async fn connect(&mut self) -> Result<(), WebSocketError> {
        let ws = WebSocket::new(&self.url)
            .map_err(|e| WebSocketError::ConnectionFailed(format!("{:?}", e)))?;

        ws.set_binary_type(web_sys::BinaryType::Arraybuffer);

        // Set up message handler
        let msg_queue = self.message_queue.clone();
        let onmessage = Closure::<dyn FnMut(MessageEvent)>::new(move |e: MessageEvent| {
            if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
                msg_queue.borrow_mut().push_back(text.into());
            }
        });
        ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
        onmessage.forget();

        // Set up error handler
        // Note: WebSocket error events are generic Event objects, not ErrorEvent.
        // The browser doesn't expose the error reason for security reasons (same-origin
        // policy). We just know that an error occurred.
        let err_queue = self.error_queue.clone();
        let onerror = Closure::<dyn FnMut(Event)>::new(move |_e: Event| {
            err_queue
                .borrow_mut()
                .push_back("WebSocket connection error".to_string());
        });
        ws.set_onerror(Some(onerror.as_ref().unchecked_ref()));
        onerror.forget();

        // Set up close handler
        let closed = self.closed.clone();
        let onclose = Closure::<dyn FnMut(CloseEvent)>::new(move |_: CloseEvent| {
            *closed.borrow_mut() = true;
        });
        ws.set_onclose(Some(onclose.as_ref().unchecked_ref()));
        onclose.forget();

        // Wait for connection to open
        let ws_clone = ws.clone();
        let (tx, rx): (
            futures_channel::oneshot::Sender<Result<(), String>>,
            futures_channel::oneshot::Receiver<Result<(), String>>,
        ) = futures_channel::oneshot::channel();
        let tx: Rc<RefCell<Option<futures_channel::oneshot::Sender<Result<(), String>>>>> =
            Rc::new(RefCell::new(Some(tx)));

        let tx_open = tx.clone();
        let onopen = Closure::<dyn FnMut()>::new(move || {
            if let Some(sender) = tx_open.borrow_mut().take() {
                let _ = sender.send(Ok(()));
            }
        });
        ws_clone.set_onopen(Some(onopen.as_ref().unchecked_ref()));
        onopen.forget();

        rx.await
            .map_err(|_| WebSocketError::ConnectionFailed("Channel closed".into()))?
            .map_err(WebSocketError::ConnectionFailed)?;

        self.ws = Some(ws);

        // 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(ws) = self.ws.take() {
            let _ = ws.close();
        }
        *self.closed.borrow_mut() = true;
        Ok(())
    }

    fn is_connected(&self) -> bool {
        self.ws
            .as_ref()
            .map_or(false, |ws| ws.ready_state() == WebSocket::OPEN)
    }

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

        ws.send_with_str(message)
            .map_err(|e| WebSocketError::ConnectionFailed(format!("{:?}", e)))
    }

    async fn receive(&mut self) -> Result<StatusUpdate, WebSocketError> {
        loop {
            // Check for errors first
            if let Some(err) = self.error_queue.borrow_mut().pop_front() {
                return Err(WebSocketError::ConnectionFailed(err));
            }

            // Check if closed
            if *self.closed.borrow() {
                return Err(WebSocketError::ConnectionClosed {
                    code: 1000,
                    reason: "Connection closed".into(),
                });
            }

            // Check for messages
            if let Some(text) = self.message_queue.borrow_mut().pop_front() {
                let ws_msg: WsMessage = serde_json::from_str(&text)
                    .map_err(|e| WebSocketError::MessageParseError(e.to_string()))?;
                return Ok(StatusUpdate::from_message(&ws_msg));
            }

            // Yield to allow events to process
            gloo_timers::future::TimeoutFuture::new(50).await;
        }
    }
}
