pub use bb8_redis::bb8::{Pool, PooledConnection};
pub use bb8_redis::RedisConnectionManager;
use cal_core::Account;
use redis::{cmd, AsyncCommands};
use std::env;

pub type BB8Pool = Pool<RedisConnectionManager>;

#[derive(Debug)]
pub struct CacheError {
    pub msg: String,
}

impl CacheError {
    pub fn new_string(s: String) -> CacheError {
        CacheError { msg: s }
    }
}

fn get_redis_host() -> String {
    let name = "CAL_REDIS_HOST";
    match env::var(name) {
        Ok(v) => v,
        Err(e) => panic!("${} is not set ({})", name, e),
    }
}

pub async fn create_pool() -> BB8Pool {
    let host = get_redis_host();
    let manager = RedisConnectionManager::new(host).unwrap();
    Pool::builder().build(manager).await.unwrap()
}

async fn get_connection(
    pool: &BB8Pool,
) -> Result<PooledConnection<RedisConnectionManager>, CacheError> {
    let con = pool.get().await.unwrap();
    Ok(con)
}

async fn get_str(pool: &BB8Pool, key: &str) -> Result<Option<String>, CacheError> {
    let mut con = get_connection(pool).await?;

    let reply: String = cmd("PING").query_async(&mut *con).await.unwrap();
    assert_eq!("PONG", reply);
    
    let res: Result<Option<String>, CacheError> = con
        .get(key)
        .await
        .map_err(|e| CacheError::new_string(e.to_string()));

    match res {
        Ok(r) => match r {
            None => Ok(None),
            Some(value) => Ok(Some(value)),
        },
        Err(e) => Err(e),
    }
}

async fn get_hash(pool: &BB8Pool, key: &str, field: &str) -> Result<Option<String>, CacheError> {
    let mut con = get_connection(pool).await?;

    let res: Result<Option<String>, CacheError> = con
        .hget(key, field)
        .await
        .map_err(|e| CacheError::new_string(e.to_string()));

    match res {
        Ok(r) => match r {
            None => {
                println!("get_hash({}) Redis Result None", field);
                Ok(None)
            }
            Some(value) => {
                println!("get_hash({}) Redis Result {}", field, value);
                Ok(Some(value))
            }
        },
        Err(e) => {
            println!("get_hash({}) Redis Error {}", field, e.msg);
            Err(e)
        }
    }
}

pub async fn get_account_by_ident(
    pool: &BB8Pool,
    key: &str,
) -> Result<Option<Account>, CacheError> {
    let result = get_hash(pool, "cb:accounts", key).await;
    match result {
        Ok(r) => match r {
            None => Ok(None),
            Some(value) => get_account_by_id(pool, &value).await,
        },
        Err(e) => Err(e),
    }
}

pub async fn get_account_by_id(pool: &BB8Pool, key: &str) -> Result<Option<Account>, CacheError> {
    let _key = format!("cb:account:{}", key);
    let result = get_str(pool, key).await;
    match result {
        Ok(r) => match r {
            None => {
                println!("get_hash({}) Redis Result None", _key);
                Ok(None)
            }
            Some(value) => {
                println!("get_hash({}) Redis Result {}", value, _key);
                let account: Account = serde_json::from_str(&value).unwrap();
                Ok(Some(account))
            }
        },
        Err(e) => Err(e),
    }
}
