// SPDX-License-Identifier: AGPL-3.0-or-later

use std::{path::Path, time::Duration};

use anyhow::Context;
use bitcode::{Decode, Encode};
use tokio::net::UnixStream;
use tracing::{debug, info, trace, warn};

use crate::{cmd::Cmd, server::ServerMessage, transport::Transport};

#[derive(Debug, Clone, Copy, Encode, Decode)]
pub enum ClientMessage {
    Ack,
    Wait,
}

pub struct Client<T> {
    transport: T,
}
impl Client<UnixStream> {
    pub async fn try_initialize(socket: &Path) -> Result<Self, anyhow::Error> {
        debug!(
            "Initializing client on socket: {}",
            socket.to_string_lossy()
        );
        let transport = UnixStream::connect(socket)
            .await
            .context("client: failed to connect to server")?;
        trace!(
            "Established connection to socket: {}",
            socket.to_string_lossy()
        );

        Ok(Self { transport })
    }
}
impl<T> Client<T>
where
    T: Transport,
{
    pub async fn send_cmd(&mut self, cmd: Cmd) -> Result<(), anyhow::Error> {
        info!("Sending command to server: {cmd}");
        let message = ServerMessage::Command(cmd);

        let mut loop_idx = 0;
        loop {
            self.transport
                .send(&message)
                .await
                .context("client: could not send message to server")?;
            let response = self
                .transport
                .recv()
                .await
                .context("client: failed retrieving server response")?;

            match response {
                ClientMessage::Ack => {
                    trace!("Received ACK from server");
                    break;
                }
                ClientMessage::Wait => {
                    trace!("Received WAIT from server");
                    if loop_idx % 10 == 0 {
                        warn!("Server queue is full, waiting to submit command.");
                    }
                    tokio::time::sleep(Duration::from_millis(100)).await;
                }
            }
            loop_idx += 1;
        }
        Ok(())
    }

    pub async fn send_flush(&mut self, timeout: Option<Duration>) -> Result<(), anyhow::Error> {
        info!(
            "Sending flush signal to server{}",
            if let Some(timeout) = timeout {
                format!(" (timeout: {}s)", timeout.as_secs())
            } else {
                "".into()
            }
        );
        self.transport
            .send(&ServerMessage::Flush(timeout))
            .await
            .context("client")?;

        trace!("Sent flush signal to server, awaiting ack");

        self.transport
            .recv::<ClientMessage>()
            .await
            .context("client: failed receiving ack for flush operation")?;

        trace!("Server acknowledged receipt of flush signal");

        Ok(())
    }

    pub async fn send_stop(&mut self, timeout: Option<Duration>) -> Result<(), anyhow::Error> {
        info!(
            "Sending stop signal to server{}",
            if let Some(timeout) = timeout {
                format!(" (timeout: {}s)", timeout.as_secs())
            } else {
                "".into()
            }
        );
        self.transport
            .send(&ServerMessage::Stop(timeout))
            .await
            .context("client")?;

        trace!("Sent stop signal to server, awaiting ack");

        self.transport
            .recv::<ClientMessage>()
            .await
            .context("client: failed receiving ack for stop operation")?;

        trace!("Server acknowledged receipt of stop signal");

        Ok(())
    }
}
