use std::{fmt::Debug, sync::Arc};

use log::trace;
use tokio::sync::{
    Mutex,
    mpsc::{
        UnboundedReceiver, UnboundedSender,
        error::{SendError as TokioSendError, TryRecvError},
    },
};

use crate::profiling::tracy_dynamic_zone;

#[derive(Clone, Debug)]
pub struct LoggingSender<T>
where
    T: Debug + AsRef<str>,
{
    tx: UnboundedSender<T>,
    channel_name: String,
}

impl<T> LoggingSender<T>
where
    T: Debug + AsRef<str>,
{
    pub fn attach(tx: UnboundedSender<T>, channel_name: &str) -> Self {
        Self { tx, channel_name: channel_name.to_string() }
    }

    pub fn send(&self, message: T) -> Result<(), TokioSendError<T>> {
        tracy_dynamic_zone!(&format!("{}::{}", self.channel_name, message.as_ref()));
        trace!("{} {:?}", self.channel_name, &message);
        self.tx.send(message)
    }
}

#[derive(Clone, Debug)]
pub struct LoggingReceiver<T>
where
    T: Debug + AsRef<str>,
{
    rx: Arc<Mutex<UnboundedReceiver<T>>>,
    channel_name: String,
}

impl<T> LoggingReceiver<T>
where
    T: Debug + AsRef<str>,
{
    pub fn attach(rx: UnboundedReceiver<T>, channel_name: &str) -> Self {
        Self { rx: Arc::new(Mutex::new(rx)), channel_name: channel_name.to_string() }
    }

    pub async fn recv(&mut self) -> Option<T> {
        let mut rx = self.rx.lock().await;
        match rx.recv().await {
            Some(message) => {
                tracy_dynamic_zone!(&format!("{}::{}", self.channel_name, message.as_ref()));
                trace!("{} {:?}", self.channel_name, &message);
                Some(message)
            }
            None => None,
        }
    }

    pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
        let mut rx = self.rx.try_lock().expect("Could not lock receiver");
        match rx.try_recv() {
            Ok(message) => {
                tracy_dynamic_zone!(&format!("{}::{}", self.channel_name, message.as_ref()));
                trace!("{} {:?}", self.channel_name, &message);
                Ok(message)
            }
            Err(e) => Err(e),
        }
    }
}
