Documentation
use crate::connection::RedisConnection;
use redis::{
    Client, PushInfo, RedisResult, ToRedisArgs,
    aio::{ConnectionManager, ConnectionManagerConfig},
    cluster::ClusterClientBuilder,
};
use std::fmt::Display;

pub struct RedisPubsub {
    pub connection: RedisConnection,
    pub stream: tokio::sync::mpsc::UnboundedReceiver<PushInfo>,
    pub db_name: String,
}

impl RedisPubsub {
    pub async fn init(uri: &str, db_name: &str) -> RedisResult<RedisPubsub> {
        let nodes = uri.split(",").collect::<Vec<&str>>();
        let (tx, stream) = tokio::sync::mpsc::unbounded_channel();
        if nodes.len() == 1 {
            let client = Client::open(uri)?;
            let config = ConnectionManagerConfig::default()
                .set_number_of_retries(usize::MAX)
                .set_factor(3000)
                .set_exponent_base(1)
                .set_push_sender(tx)
                .set_automatic_resubscription();
            let connection = ConnectionManager::new_with_config(client.clone(), config).await?;
            let connection = RedisConnection::Single(connection);
            Ok(RedisPubsub {
                connection,
                stream,
                db_name: db_name.to_string(),
            })
        } else {
            let client = ClusterClientBuilder::new(nodes)
                .use_protocol(redis::ProtocolVersion::RESP3)
                .push_sender(tx)
                .retries(u32::MAX)
                .retry_wait_formula(3000, 1)
                .build()?;
            let connection = client.get_async_connection().await?;
            let connection = RedisConnection::Cluster(connection);
            Ok(RedisPubsub {
                connection,
                stream,
                db_name: db_name.to_string(),
            })
        }
    }

    pub async fn subscribe(&mut self, channel_name: impl ToRedisArgs + Display) -> RedisResult<()> {
        let channel_name = format!("{}:{}", self.db_name, channel_name);
        match &mut self.connection {
            RedisConnection::Single(c) => c.subscribe(channel_name).await,
            RedisConnection::Cluster(c) => c.subscribe(channel_name).await,
        }
    }

    pub async fn unsubscribe(
        &mut self,
        channel_name: impl ToRedisArgs + Display,
    ) -> RedisResult<()> {
        let channel_name = format!("{}:{}", self.db_name, channel_name);
        match &mut self.connection {
            RedisConnection::Single(c) => c.unsubscribe(channel_name).await,
            RedisConnection::Cluster(c) => c.unsubscribe(channel_name).await,
        }
    }
}