//! # Redis connection management

use crate::app_config::AppConfig;
use redis::Commands;
use serde::de::DeserializeOwned;
use std::cell::RefCell;
use std::time::Duration;

thread_local! {
    static REDIS_CONNECTION: RefCell<Option<redis::Client>> = const { RefCell::new(None) };
}

/// Execute a request on Redis
fn execute_request<E, I>(cb: E) -> anyhow::Result<I>
where
    E: FnOnce(&mut redis::Client) -> anyhow::Result<I>,
{
    // Establish connection if required
    if REDIS_CONNECTION.with(|i| i.borrow().is_none()) {
        let conn = redis::Client::open(AppConfig::get().redis_connection_config())?;

        REDIS_CONNECTION.with(|i| *i.borrow_mut() = Some(conn))
    }

    REDIS_CONNECTION.with(|i| cb(i.borrow_mut().as_mut().unwrap()))
}

/// Get a value stored on Redis
pub async fn get_value<E>(key: &str) -> anyhow::Result<Option<E>>
where
    E: DeserializeOwned,
{
    let value: Option<String> = execute_request(|conn| Ok(conn.get(key)?))?;

    Ok(match value {
        None => None,
        Some(v) => serde_json::from_str(&v)?,
    })
}

/// Set a new value on Redis
pub async fn set_value<E>(key: &str, value: &E, lifetime: Duration) -> anyhow::Result<()>
where
    E: serde::Serialize,
{
    let value_str = serde_json::to_string(value)?;

    execute_request(|conn| Ok(conn.set_ex::<_, _, ()>(key, value_str, lifetime.as_secs())?))?;

    Ok(())
}

/// Remove a value from Redis
pub async fn remove_value(key: &str) -> anyhow::Result<()> {
    execute_request(|conn| Ok(conn.del::<_, ()>(key)?))?;

    Ok(())
}
