use std::time::Duration;

use avam_protocol::{Packet, Packets, SimConnectPacket, SystemPacket};
use futures_util::{SinkExt, StreamExt};
use reqwest::StatusCode;
use tokio::{
    sync::broadcast::{channel, Receiver, Sender},
    task::JoinSet,
    time::{sleep, timeout},
};
use tokio_tungstenite::{
    connect_async,
    tungstenite::{
        self,
        protocol::{frame::coding::CloseCode, CloseFrame},
        ClientRequestBuilder, Message,
    },
};

use crate::{config::Config, state_machine::Event, BASE_URL};

pub async fn start(
    config: Config,
    simconnect_sender: Sender<SimConnectPacket>,
    socket_receiver: Receiver<SimConnectPacket>,
    event_sender: Sender<Event>,
    mut event_receiver: Receiver<Event>,
) -> Result<(), anyhow::Error> {
    let uri: tungstenite::http::Uri = format!("{}/ws", BASE_URL.replace("https", "wss")).parse()?;

    let mut futures = JoinSet::new();

    let (ia_sender, ia_receiver) = channel(10);
    loop {
        if let Ok(event) = &event_receiver.try_recv() {
            match event {
                Event::TokenReceived => {
                    let Some(token) = config.token() else {
                        let _ = event_sender.send(Event::Logout);
                        sleep(Duration::from_millis(100)).await;
                        continue;
                    };

                    let builder = ClientRequestBuilder::new(uri.clone())
                        .with_header("Authorization", format!("Bearer {}", token));

                    tracing::info!("Connecting");
                    let Ok(Ok((socket, response))) =
                        timeout(Duration::from_secs(10), connect_async(builder)).await
                    else {
                        tracing::error!("Unable to connect");
                        let _ = event_sender.send(Event::Disconnected);
                        sleep(Duration::from_millis(100)).await;
                        continue;
                    };

                    if response.status() != StatusCode::SWITCHING_PROTOCOLS {
                        tracing::error!("Unable to connect: {:#?}", response);
                        let _ = event_sender.send(Event::Disconnected);
                        sleep(Duration::from_millis(100)).await;
                        continue;
                    }

                    let (mut write, mut read) = socket.split();

                    let mut ia_receiver: Receiver<Packets> = ia_receiver.resubscribe();
                    futures.spawn(async move {
                        loop {
                            if let Ok(d) = ia_receiver.try_recv() {
                                let message = match d {
                                    Packets::System(SystemPacket::Close { reason }) => {
                                        Message::Close(Some(CloseFrame {
                                            code: CloseCode::Normal,
                                            reason: reason.into(),
                                        }))
                                    }
                                    Packets::System(SystemPacket::Pong) => {
                                        let Ok(encoded_message) = d.encode() else {
                                            tracing::error!(
                                                "Unable to encode message for sending: {:#?}",
                                                d
                                            );
                                            sleep(Duration::from_millis(100)).await;
                                            continue;
                                        };
                                        Message::Binary(encoded_message)
                                    }
                                    d => {
                                        // tracing::info!("sending packet: {:#?}", &d);
                                        let Ok(encoded_message) = d.encode() else {
                                            tracing::error!(
                                                "Unable to encode message for sending: {:#?}",
                                                d
                                            );
                                            sleep(Duration::from_millis(100)).await;
                                            continue;
                                        };
                                        Message::Binary(encoded_message)
                                    }
                                };

                                match write.send(message).await {
                                    Err(tungstenite::Error::AlreadyClosed) => break,
                                    Err(e) => {
                                        tracing::error!("Error writing to socket: {:?}", e);
                                        break;
                                    }
                                    Ok(()) => {}
                                };
                            }
                            sleep(Duration::from_millis(100)).await;
                        }
                    });

                    let ias = ia_sender.clone();
                    let es = event_sender.clone();
                    let scs = simconnect_sender.clone();
                    futures.spawn(async move {
                        loop {
                            let message = match read.next().await {
                                Some(data) => match data {
                                    Ok(message) => message,
                                    Err(e) => {
                                        tracing::error!("{:?}", e);
                                        let _ = es.send(Event::Disconnected);
                                        sleep(Duration::from_millis(100)).await;
                                        continue;
                                    }
                                },
                                None => break,
                            };

                            if let Ok(data) = Packets::decode(&message.into_data()) {
                                if data == Packets::System(SystemPacket::Ping) {
                                    let _ = ias.send(Packets::System(SystemPacket::Pong));
                                    continue;
                                }

                                // From Socket -> SimConnect
                                if let Packets::SimConnect(sim_connect_packet) = data {
                                    tracing::info!("packet received: {:?}", &sim_connect_packet);
                                    let _ = scs.send(sim_connect_packet);
                                }
                            }
                            sleep(Duration::from_millis(100)).await;
                        }
                    });

                    // Data from simconnect -> Socket
                    let mut socket_receiver = socket_receiver.resubscribe();
                    let ias = ia_sender.clone();
                    futures.spawn(async move {
                        loop {
                            if let Ok(message) = socket_receiver.try_recv() {
                                let _ = ias.send(Packets::SimConnect(message));
                            }
                            sleep(Duration::from_millis(100)).await;
                        }
                    });

                    tracing::info!("Connected");
                    let _ = event_sender.send(Event::Connected);
                }
                Event::Logout => {
                    let _ = ia_sender.send(Packets::System(SystemPacket::Close {
                        reason: "User Logout".to_string(),
                    }));
                    sleep(Duration::from_millis(200)).await;
                    futures.abort_all();
                }
                Event::Quit => {
                    tracing::info!("Shutting down Client");
                    let _ = ia_sender.send(Packets::System(SystemPacket::Close {
                        reason: "Quit".to_string(),
                    }));
                    sleep(Duration::from_millis(200)).await;
                    futures.abort_all();
                    break;
                }
                _ => {}
            }
        }
        sleep(Duration::from_millis(100)).await;
    }
    tracing::info!("Client Shutdown");
    Ok(())
}
