use std::time::Duration;

use crate::config::IpString;

use super::{tools::Packet, NetResult};
use prost::Message;
use proto::PacketHead;
use tokio::sync::mpsc::{channel, Receiver, Sender};
use zeromq::{PushSocket, Socket, SocketSend};

pub struct AClient {
    tx: Sender<Packet>,
}

impl AClient {
    pub fn new(host: IpString, cur_app_id: u32) -> Self {
        let (tx, rx) = channel(32);

        tokio::spawn(async move { Self::network_func(cur_app_id, host, rx).await });
        Self { tx }
    }

    async fn network_func(cur_app_id: u32, host: IpString, mut rx: Receiver<Packet>) {
        let mut socket = None;
        loop {
            let mut packet = rx.recv().await.unwrap();
            packet.head.sender_app_id = cur_app_id;

            let sock = match socket.as_mut() {
                Some(socket) => socket,
                None => {
                    socket = Some(Self::connect(&host).await);
                    socket.as_mut().unwrap()
                }
            };

            if let Err(err) = sock.send(packet.to_vec().into()).await {
                tracing::error!(
                    "network_func: socket send failed, will try to reconnect next time. Err: {err}"
                );
                socket = None;
            }
        }
    }

    pub async fn send(&self, cmd_id: u16, head: PacketHead, proto: impl Message) -> NetResult<()> {
        let packet = Packet {
            cmd_id,
            head,
            recv_vec: proto.encode_to_vec(),
        };

        self.send_packet(packet).await
    }

    pub async fn send_packet(&self, packet: Packet) -> NetResult<()> {
        self.tx.send(packet).await.unwrap();
        Ok(())
    }

    pub fn send_packet_blocking(&self, packet: Packet) -> NetResult<()> {
        self.tx.blocking_send(packet).unwrap();
        Ok(())
    }

    pub async fn connect(host: &IpString) -> PushSocket {
        let mut new_socket = PushSocket::new();
        loop {
            if new_socket
                .connect(&format!("tcp://{}", host.0))
                .await
                .is_ok()
            {
                break new_socket;
            }

            tokio::time::sleep(Duration::from_millis(500)).await;
        }
    }
}
