/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2025 Hurry Curry! Contributors

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3 of the License only.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

*/
use anyhow::{Result, anyhow};
use futures_util::{
    SinkExt, TryStreamExt,
    stream::{SplitSink, SplitStream, StreamExt},
};
use hurrycurry_protocol::{PacketC, PacketS, VERSION};
use log::{debug, info, warn};
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::{net::TcpStream, sync::RwLock};
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream, client_async_tls_with_config};
use tungstenite::{
    Message,
    client::{IntoClientRequest, uri_mode},
    http::Request,
    stream::Mode,
};

pub struct Network {
    sock_recv: RwLock<SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>>,
    sock_send: RwLock<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>,
    use_binary: AtomicBool,
}

impl Network {
    pub async fn connect(uri: &str) -> Result<Self> {
        let (parts, _) = uri.into_client_request().unwrap().into_parts();
        let mut builder = Request::builder()
            .uri(parts.uri.clone().clone())
            .method(parts.method.clone())
            .version(parts.version);
        *builder.headers_mut().unwrap() = parts.headers.clone();
        let request = builder.body(()).unwrap();

        let host = request.uri().host().unwrap();
        let host = if host.starts_with('[') {
            &host[1..host.len() - 1]
        } else {
            host
        };
        let port = request
            .uri()
            .port_u16()
            .unwrap_or(match uri_mode(request.uri())? {
                Mode::Plain => 27032,
                Mode::Tls => 443,
            });

        info!("Connecting: host={host:?} port={port}");
        let stream = TcpStream::connect((host, port)).await?;
        Self::connect_raw(stream, uri).await
    }
    pub async fn connect_raw(stream: TcpStream, uri: &str) -> Result<Self> {
        let (parts, _) = uri.into_client_request()?.into_parts();
        let mut builder = Request::builder()
            .uri(parts.uri.clone().clone())
            .method(parts.method.clone())
            .version(parts.version);
        *builder.headers_mut().ok_or(anyhow!("???"))? = parts.headers.clone();
        let request = builder.body(())?;

        stream.set_nodelay(true)?;
        let (sock, _) = client_async_tls_with_config(request, stream, None, None).await?;
        info!("Handshake complete.");
        let (sock_send, sock_recv) = sock.split();
        Ok(Self {
            sock_recv: RwLock::new(sock_recv),
            sock_send: RwLock::new(sock_send),
            use_binary: false.into(),
        })
    }

    pub async fn receive(&self) -> anyhow::Result<Option<PacketC>> {
        let mut g = self.sock_recv.write().await;
        loop {
            match g.try_next().await? {
                Some(Message::Text(packet)) => match serde_json::from_str(&packet) {
                    Ok(packet) => {
                        debug!("<- {packet:?}");
                        if let PacketC::Version {
                            minor,
                            major,
                            supports_bincode,
                        } = &packet
                            && *minor == VERSION.0
                            && *major == VERSION.1
                            && *supports_bincode
                        {
                            info!("Binary protocol format enabled.");
                            self.use_binary.store(true, Ordering::Relaxed);
                        }
                        return Ok(Some(packet));
                    }
                    Err(e) => {
                        warn!("invalid json packet: {e:?}");
                    }
                },
                Some(Message::Binary(_packet)) => {
                    // match bincode::decode_from_slice(&packet, BINCODE_CONFIG) {
                    //     Ok((packet, _)) => {
                    //         debug!("<- {packet:?}");
                    //         return Ok(Some(packet));
                    //     }
                    //     Err(e) => {
                    //         warn!("invalid bincode packet: {e:?}");
                    //     }
                    // }
                }
                _ => (),
            };
        }
    }
    pub async fn send(&self, packet: PacketS) -> anyhow::Result<()> {
        debug!("-> {packet:?}");
        let mut g = self.sock_send.write().await;
        // if self.use_binary.load(Ordering::Relaxed) {
        //     g.send(Message::Binary(
        //         bincode::encode_to_vec(&packet, BINCODE_CONFIG)
        //             .unwrap()
        //             .into(),
        //     ))
        //     .await?;
        // } else {
        g.send(Message::Text(
            serde_json::to_string(&packet).unwrap().into(),
        ))
        .await?;
        // }
        Ok(())
    }
}
