/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2026 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;
use hurrycurry_protocol::{PacketC, PacketS};
use log::{debug, info, warn};
use std::{collections::VecDeque, net::TcpStream};
use tungstenite::{
    Message, WebSocket,
    client::{IntoClientRequest, uri_mode},
    client_tls_with_config,
    handshake::client::Request,
    stream::{MaybeTlsStream, Mode},
    util::NonBlockingError,
};

pub struct Network {
    sock: WebSocket<MaybeTlsStream<TcpStream>>,
    pub queue_in: VecDeque<PacketC>,
    pub queue_out: VecDeque<PacketS>,
}

impl Network {
    pub fn connect(addr: &str) -> Result<Self> {
        let (parts, _) = addr.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().unwrap() = parts.headers.clone();
        let request = builder.body(())?;

        let host = request.uri().host().unwrap_or("127.0.0.1");
        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))?;
        stream.set_nodelay(true)?;

        let (mut sock, _) = client_tls_with_config(request, stream, None, None)?;

        match sock.get_mut() {
            MaybeTlsStream::Plain(s) => s.set_nonblocking(true)?,
            MaybeTlsStream::Rustls(s) => s.sock.set_nonblocking(true)?,
            _ => unreachable!(),
        };

        info!("Handshake complete.");
        Ok(Self {
            sock,
            queue_in: VecDeque::new(),
            queue_out: VecDeque::new(),
        })
    }

    pub fn poll(&mut self) -> anyhow::Result<()> {
        loop {
            self.queue_in.extend(match self.sock.read() {
                Ok(Message::Text(packet)) => match serde_json::from_str(&packet) {
                    Ok(packet) => {
                        debug!("<- {packet:?}");
                        Some(packet)
                    }
                    Err(e) => {
                        warn!("invalid json packet: {e:?}");
                        None
                    }
                },
                Ok(Message::Binary(_packet)) => {
                    // match bincode::decode_from_slice(&packet, BINCODE_CONFIG) {
                    //     Ok((packet, _)) => {
                    //         debug!("<- {packet:?}");
                    //         Some(packet)
                    //     }
                    //     Err(e) => {
                    //         warn!("invalid bincode packet: {e:?}");
                    //         None
                    //     }
                    // }
                    None
                }
                Ok(_) => None,
                Err(e) => {
                    if let Some(e) = e.into_non_blocking() {
                        warn!("{e:?}");
                        None
                    } else {
                        break;
                    }
                }
            });
        }

        for packet in self.queue_out.drain(..) {
            debug!("-> {packet:?}");
            // if self.use_bincode {
            //     self.sock.write(Message::Binary(
            //         bincode::encode_to_vec(&packet, BINCODE_CONFIG)?.into(),
            //     ))?;
            // } else {
            self.sock
                .write(Message::Text(serde_json::to_string(&packet)?.into()))?;
            // }
        }

        self.sock.flush()?;
        Ok(())
    }
}
