use redis::{AsyncCommands, ErrorKind, RedisError, RedisResult, Value};
use redis::aio::MultiplexedConnection;
use serde::de::DeserializeOwned;
use serde::Serialize;

pub const REGION_IDENT_KEY: &str = "cal:region:idents";
pub const REGIONS_KEY: &str = "cal:regions";

pub const ACCOUNT_IDENT_KEY: &str = "cal:account:idents";
pub const ACCOUNTS_KEY: &str = "cal:accounts";


/// Serializes an item to a JSON string with proper error handling.
pub fn serialize_to_json<T: Serialize>(item: &T) -> Result<String, RedisError> {
    serde_json::to_string(item).map_err(|e| RedisError::from((
        ErrorKind::ClientError,
        "JSON serialization error",
        e.to_string()
    )))
}

/// Deserializes a JSON string into the specified type with proper error handling.
pub fn deserialize_from_json<T: DeserializeOwned>(json: &str) -> Result<T, RedisError> {
    serde_json::from_str(json).map_err(|e| RedisError::from((
        ErrorKind::ClientError,
        "JSON deserialization error",
        e.to_string()
    )))
}

/// Retrieves all values from a Redis hash and deserializes them into the specified type.
pub async fn get_items<T: DeserializeOwned>(
    con: MultiplexedConnection,
    key: &str
) -> Result<Vec<T>, RedisError> {
    let strings: Vec<String> = con.clone().hvals(key).await?;

    let mut items = Vec::with_capacity(strings.len());
    for json_str in strings {
        items.push(deserialize_from_json(&json_str)?);
    }

    Ok(items)
}

/// Retrieves a single value from a Redis hash and deserializes it into the specified type.
pub async fn get_hash_item<T: DeserializeOwned>(
    con: MultiplexedConnection,
    key: &str,
    field: &str
) -> Result<Option<T>, RedisError> {
    match con.clone().hget::<_, _, Option<String>>(key, field).await? {
        Some(json_str) => Ok(Some(deserialize_from_json(&json_str)?)),
        None => Ok(None),
    }
}

/// Retrieves a single value from a Redis hash and deserializes it into the specified type.
pub async fn get_item<T: DeserializeOwned>(
    con: MultiplexedConnection,
    key: &str,
) -> Result<Option<T>, RedisError> {
    match con.clone().get::<_, Option<String>>(key).await? {
        Some(json_str) => Ok(Some(deserialize_from_json(&json_str)?)),
        None => Ok(None),
    }
}



/// A trait for types that can be used as Redis hash field identifiers
pub trait RedisIdentifiable {
    /// Returns the identifier to use as a field in Redis hashes
    fn redis_id(&self) -> String;
}

/// Default implementation for types with string-like IDs
impl<T, S> RedisIdentifiable for T
where
    T: Serialize + Clone,
    T: std::ops::Deref<Target = S>,
    S: AsRef<str>,
{
    fn redis_id(&self) -> String {
        self.as_ref().to_string()
    }
}


/// Inserts multiple items into a Redis hash, serializing each item to JSON.
pub async fn insert_items<T, F>(
    mut con: MultiplexedConnection,
    key: &str,
    items: &[T],
    id_extractor: F,
) -> RedisResult<Value>
where
    T: Serialize,
    F: Fn(&T) -> String,
{
    let mut entries = Vec::with_capacity(items.len());

    for item in items {
        let field = id_extractor(item);
        let value = serialize_to_json(item)?;
        entries.push((field, value));
    }

    con.hset_multiple(key, &entries).await
}