use crate::{MqttResult, QoS, State};
use anyhow::anyhow;
use bytes::Bytes;
use rumqttc::v5::AsyncClient;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{Mutex, watch};

pub type MqttClient = Arc<Client>;

#[derive(Debug)]
struct Topics(HashMap<String, QoS>);

impl Topics {
    pub fn new() -> Self {
        Topics(HashMap::new())
    }
    pub fn add<T: AsRef<str> + Sync + Send>(&mut self, topic: T, qos: QoS) -> MqttResult {
        let topic_ref = topic.as_ref();
        if !self.0.contains_key(topic_ref) {
            self.0.insert(topic_ref.to_string(), qos);
        }
        Ok(())
    }
    pub fn get(&self) -> HashMap<String, QoS> {
        self.0.clone()
    }
    pub fn remove<T: AsRef<str>>(&mut self, topic: T) {
        let topic_ref = topic.as_ref();
        self.0.remove(topic_ref);
    }
}
#[derive(Debug)]
pub struct Client {
    state: watch::Receiver<State>,
    mqtt: AsyncClient,
    topics: Mutex<Topics>,
}

impl Client {
    pub fn new(state: watch::Receiver<State>, mqtt: AsyncClient) -> Self {
        let topics = Mutex::new(Topics::new());
        Client {
            state,
            mqtt,
            topics,
        }
    }

    pub fn state(&self) -> String {
        match *self.state.borrow() {
            State::Pending => "pending".to_string(),
            State::Connected => "connected".to_string(),
            State::Disconnected => "disconnected".to_string(),
            State::Closed => "closed".to_string(),
            State::Error(ref s) => format!("error:{}", s),
        }
    }

    pub async fn subscribe(&self, topic: &str, qos: crate::QoS) -> MqttResult {
        if *self.state.borrow() != State::Connected {
            return Err(anyhow!("mqtt not connected"));
        }
        self.mqtt.subscribe(topic, qos).await?;
        let mut topics = self.topics.try_lock()?;
        topics.add(topic.to_string(), qos)?;
        Ok(())
    }

    pub async fn unsubscribe(&self, topic: &str) -> MqttResult {
        if *self.state.borrow() != State::Connected {
            return Err(anyhow!("mqtt not connected"));
        }
        self.mqtt.unsubscribe(topic).await?;
        let mut topics = self.topics.try_lock()?;
        topics.remove(topic.to_string());
        Ok(())
    }

    fn get_topics(&self) -> HashMap<String, QoS> {
        if let Ok(topics) = self.topics.try_lock() {
            return topics.get();
        }
        HashMap::new()
    }

    async fn re_subscribe_topic(&self) {
        let topics = self.get_topics();
        log::info!("re subscribe topics len:{}", topics.len());
        for (topic, qos) in topics.iter() {
            if let Err(e) = self.subscribe(topic, *qos).await {
                log::error!("Failed to re subscribe to topic: {}", e);
            }
        }
    }

    pub(crate) fn run(cli: MqttClient) {
        let mut state = cli.state.clone();
        tokio::spawn(async move {
            while state.changed().await.is_ok() {
                if *state.borrow() == State::Connected {
                    cli.re_subscribe_topic().await;
                }
            }
            log::error!("check reconnect subscribe stop");
        });
    }

    pub fn connected(&self) -> bool {
        *self.state.borrow() == State::Connected
    }

    pub fn state_is_error(&self) -> Option<String> {
        match *self.state.borrow() {
            State::Error(ref s) => Some(s.clone()),
            _ => None,
        }
    }

    pub async fn publish<P, S>(
        &self,
        topic: S,
        payload: P,
        qos: crate::QoS,
        retain: bool,
    ) -> MqttResult
    where
        P: Into<Bytes>,
        S: Into<String>,
    {
        if *self.state.borrow() != State::Connected {
            return Err(anyhow!("mqtt not connected"));
        }
        self.mqtt.publish(topic, qos, retain, payload).await?;
        Ok(())
    }

    pub async fn close(&self) -> MqttResult {
        if *self.state.borrow() != State::Connected {
            return Ok(());
        }
        self.mqtt.disconnect().await?;
        Ok(())
    }
}
