use std::{collections::VecDeque, rc::Rc};

use letterbox_shared::WebsocketMessage;
use log::{debug, error};
use seed::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(not(target_arch = "wasm32"))]
#[allow(dead_code)]
mod wasm_sockets {
    use std::{cell::RefCell, rc::Rc};

    use thiserror::Error;
    use web_sys::{CloseEvent, ErrorEvent};

    #[derive(Debug)]
    pub struct JsValue;
    #[derive(Debug)]
    pub enum ConnectionStatus {
        /// Connecting to a server
        Connecting,
        /// Connected to a server
        Connected,
        /// Disconnected from a server due to an error
        Error,
        /// Disconnected from a server without an error
        Disconnected,
    }
    #[derive(Debug)]
    pub struct EventClient {
        pub status: Rc<RefCell<ConnectionStatus>>,
    }
    impl EventClient {
        pub fn new(_: &str) -> Result<Self, WebSocketError> {
            todo!("this is a mock")
        }
        pub fn send_string(&self, _essage: &str) -> Result<(), JsValue> {
            todo!("this is a mock")
        }
        pub fn set_on_error(&mut self, _: Option<Box<dyn Fn(ErrorEvent)>>) {
            todo!("this is a mock")
        }
        pub fn set_on_connection(&mut self, _: Option<Box<dyn Fn(&EventClient)>>) {
            todo!("this is a mock")
        }
        pub fn set_on_close(&mut self, _: Option<Box<dyn Fn(CloseEvent)>>) {
            todo!("this is a mock")
        }
        pub fn set_on_message(&mut self, _: Option<Box<dyn Fn(&EventClient, Message)>>) {
            todo!("this is a mock")
        }
    }
    #[derive(Debug, Clone)]
    pub enum Message {
        Text(String),
        Binary(Vec<u8>),
    }
    #[derive(Debug, Clone, Error)]
    pub enum WebSocketError {}
}
#[cfg(not(target_arch = "wasm32"))]
use wasm_sockets::{ConnectionStatus, EventClient, Message, WebSocketError};
#[cfg(target_arch = "wasm32")]
use wasm_sockets::{ConnectionStatus, EventClient, Message, WebSocketError};
use web_sys::CloseEvent;

/// Message from the client to the server.
#[derive(Serialize, Deserialize)]
pub struct ClientMessage {
    pub text: String,
}

//const WS_URL: &str = "wss://9000.z.xinu.tv/api/ws";
//const WS_URL: &str = "wss://9345.z.xinu.tv/api/graphql/ws";
//const WS_URL: &str = "wss://6758.z.xinu.tv/api/ws";

// ------ ------
//     Model
// ------ ------

pub struct Model {
    ws_url: String,
    web_socket: EventClient,
    web_socket_reconnector: Option<StreamHandle>,
    pub updates: VecDeque<WebsocketMessage>,
}

// ------ ------
//     Init
// ------ ------

pub fn init(ws_url: &str, orders: &mut impl Orders<Msg>) -> Model {
    Model {
        ws_url: ws_url.to_string(),
        web_socket: create_websocket(ws_url, orders).unwrap(),
        web_socket_reconnector: None,
        updates: VecDeque::new(),
    }
}

// ------ ------
//    Update
// ------ ------

pub enum Msg {
    WebSocketOpened,
    TextMessageReceived(WebsocketMessage),
    WebSocketClosed(CloseEvent),
    WebSocketFailed,
    ReconnectWebSocket(usize),
    #[allow(dead_code)]
    SendMessage(ClientMessage),
}

pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
    match msg {
        Msg::WebSocketOpened => {
            model.web_socket_reconnector = None;
            debug!("WebSocket connection is open now");
        }
        Msg::TextMessageReceived(msg) => {
            model.updates.push_back(msg);
        }
        Msg::WebSocketClosed(close_event) => {
            debug!(
                r#"==================
WebSocket connection was closed:
Clean: {0}
Code: {1}
Reason: {2}
=================="#,
                close_event.was_clean(),
                close_event.code(),
                close_event.reason()
            );

            // Chrome doesn't invoke `on_error` when the connection is lost.
            if !close_event.was_clean() && model.web_socket_reconnector.is_none() {
                model.web_socket_reconnector = Some(
                    orders.stream_with_handle(streams::backoff(None, Msg::ReconnectWebSocket)),
                );
            }
        }
        Msg::WebSocketFailed => {
            debug!("WebSocket failed");
            if model.web_socket_reconnector.is_none() {
                model.web_socket_reconnector = Some(
                    orders.stream_with_handle(streams::backoff(None, Msg::ReconnectWebSocket)),
                );
            }
        }
        Msg::ReconnectWebSocket(retries) => {
            debug!("Reconnect attempt: {}", retries);
            model.web_socket = create_websocket(&model.ws_url, orders).unwrap();
        }
        Msg::SendMessage(msg) => {
            let txt = serde_json::to_string(&msg).unwrap();
            model.web_socket.send_string(&txt).unwrap();
        }
    }
}

fn create_websocket(url: &str, orders: &impl Orders<Msg>) -> Result<EventClient, WebSocketError> {
    let msg_sender = orders.msg_sender();

    let mut client = EventClient::new(url)?;

    client.set_on_error(Some(Box::new(|error| {
        gloo_console::error!("WS: ", error);
    })));

    let send = msg_sender.clone();
    client.set_on_connection(Some(Box::new(move |client: &EventClient| {
        debug!("{:#?}", client.status);
        let msg = match *client.status.borrow() {
            ConnectionStatus::Connecting => {
                debug!("Connecting...");
                None
            }
            ConnectionStatus::Connected => Some(Msg::WebSocketOpened),
            ConnectionStatus::Error => Some(Msg::WebSocketFailed),
            ConnectionStatus::Disconnected => {
                debug!("Disconnected");
                None
            }
        };
        send(msg);
    })));

    let send = msg_sender.clone();
    client.set_on_close(Some(Box::new(move |ev| {
        debug!("WS: Connection closed");
        send(Some(Msg::WebSocketClosed(ev)));
    })));

    let send = msg_sender.clone();
    client.set_on_message(Some(Box::new(move |_: &EventClient, msg: Message| {
        decode_message(msg, Rc::clone(&send))
    })));

    Ok(client)
}

fn decode_message(message: Message, msg_sender: Rc<dyn Fn(Option<Msg>)>) {
    match message {
        Message::Text(txt) => {
            let msg: WebsocketMessage = serde_json::from_str(&txt).unwrap_or_else(|e| {
                panic!("failed to parse json into WebsocketMessage: {e}\n'{txt}'")
            });
            msg_sender(Some(Msg::TextMessageReceived(msg)));
        }
        m => error!("unexpected message type received of {m:?}"),
    }
}
