use crate::{
    context::call_reply::{CallResult, VerbatimStringFormat},
    CallReply, ValkeyError, ValkeyString,
};
use std::{
    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
    hash::Hash,
};
#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub enum ValkeyValueKey {
    Integer(i64),
    String(String),
    BulkValkeyString(ValkeyString),
    BulkString(Vec<u8>),
    Bool(bool),
}
#[derive(Debug, PartialEq, Clone)]
pub enum RedisValue {
    SimpleStringStatic(&'static str),
    SimpleString(String),
    BulkString(String),
    BulkValkeyString(ValkeyString),
    StringBuffer(Vec<u8>),
    Integer(i64),
    Bool(bool),
    Float(f64),
    BigNumber(String),
    VerbatimString((VerbatimStringFormat, Vec<u8>)),
    Array(Vec<RedisValue>),
    StaticError(&'static str),
    Map(HashMap<ValkeyValueKey, RedisValue>),
    Set(HashSet<ValkeyValueKey>),
    OrderedMap(BTreeMap<ValkeyValueKey, RedisValue>),
    OrderedSet(BTreeSet<ValkeyValueKey>),
    Null,
    NoReply, }
impl TryFrom<RedisValue> for String {
    type Error = ValkeyError;
    fn try_from(val: RedisValue) -> Result<Self, ValkeyError> {
        match val {
            RedisValue::SimpleStringStatic(s) => Ok(s.to_string()),
            RedisValue::SimpleString(s) => Ok(s),
            RedisValue::BulkString(s) => Ok(s),
            RedisValue::BulkValkeyString(s) => Ok(s.try_as_str()?.to_string()),
            RedisValue::StringBuffer(s) => Ok(std::str::from_utf8(&s)?.to_string()),
            _ => Err(ValkeyError::Str("Can not convert result to String")),
        }
    }
}
impl From<String> for ValkeyValueKey {
    fn from(s: String) -> Self {
        Self::String(s)
    }
}
impl From<i64> for ValkeyValueKey {
    fn from(i: i64) -> Self {
        Self::Integer(i)
    }
}
impl From<ValkeyString> for ValkeyValueKey {
    fn from(rs: ValkeyString) -> Self {
        Self::BulkValkeyString(rs)
    }
}
impl From<Vec<u8>> for ValkeyValueKey {
    fn from(s: Vec<u8>) -> Self {
        Self::BulkString(s)
    }
}
impl From<&str> for ValkeyValueKey {
    fn from(s: &str) -> Self {
        s.to_owned().into()
    }
}
impl From<&String> for ValkeyValueKey {
    fn from(s: &String) -> Self {
        s.clone().into()
    }
}
impl From<bool> for ValkeyValueKey {
    fn from(b: bool) -> Self {
        Self::Bool(b)
    }
}
impl From<()> for RedisValue {
    fn from(_: ()) -> Self {
        Self::Null
    }
}
impl From<i64> for RedisValue {
    fn from(i: i64) -> Self {
        Self::Integer(i)
    }
}
impl From<bool> for RedisValue {
    fn from(b: bool) -> Self {
        Self::Bool(b)
    }
}
impl From<usize> for RedisValue {
    fn from(i: usize) -> Self {
        (i as i64).into()
    }
}
impl From<f64> for RedisValue {
    fn from(f: f64) -> Self {
        Self::Float(f)
    }
}
impl From<String> for RedisValue {
    fn from(s: String) -> Self {
        Self::BulkString(s)
    }
}
impl From<ValkeyString> for RedisValue {
    fn from(s: ValkeyString) -> Self {
        Self::BulkValkeyString(s)
    }
}
impl From<Vec<u8>> for RedisValue {
    fn from(s: Vec<u8>) -> Self {
        Self::StringBuffer(s)
    }
}
impl From<&ValkeyString> for RedisValue {
    fn from(s: &ValkeyString) -> Self {
        s.clone().into()
    }
}
impl From<&str> for RedisValue {
    fn from(s: &str) -> Self {
        s.to_owned().into()
    }
}
impl From<&String> for RedisValue {
    fn from(s: &String) -> Self {
        s.clone().into()
    }
}
impl<T: Into<Self>> From<Option<T>> for RedisValue {
    fn from(s: Option<T>) -> Self {
        s.map_or(Self::Null, Into::into)
    }
}
impl<T: Into<Self>> From<Vec<T>> for RedisValue {
    fn from(items: Vec<T>) -> Self {
        Self::Array(items.into_iter().map(Into::into).collect())
    }
}
impl<K: Into<ValkeyValueKey>, V: Into<RedisValue>> From<HashMap<K, V>> for RedisValue {
    fn from(items: HashMap<K, V>) -> Self {
        Self::Map(
            items
                .into_iter()
                .map(|(k, v)| (k.into(), v.into()))
                .collect(),
        )
    }
}
impl<K: Into<ValkeyValueKey>, V: Into<RedisValue>> From<BTreeMap<K, V>> for RedisValue {
    fn from(items: BTreeMap<K, V>) -> Self {
        Self::OrderedMap(
            items
                .into_iter()
                .map(|(k, v)| (k.into(), v.into()))
                .collect(),
        )
    }
}
impl<K: Into<ValkeyValueKey>> From<HashSet<K>> for RedisValue {
    fn from(items: HashSet<K>) -> Self {
        Self::Set(items.into_iter().map(Into::into).collect())
    }
}
impl<K: Into<ValkeyValueKey>> From<BTreeSet<K>> for RedisValue {
    fn from(items: BTreeSet<K>) -> Self {
        Self::OrderedSet(items.into_iter().map(Into::into).collect())
    }
}
impl<'root> TryFrom<&CallReply<'root>> for ValkeyValueKey {
    type Error = ValkeyError;
    fn try_from(reply: &CallReply<'root>) -> Result<Self, Self::Error> {
        match reply {
            CallReply::I64(reply) => Ok(ValkeyValueKey::Integer(reply.to_i64())),
            CallReply::String(reply) => Ok(reply
                .to_string()
                .map_or(ValkeyValueKey::BulkString(reply.as_bytes().to_vec()), |v| {
                    ValkeyValueKey::String(v)
                })),
            CallReply::Bool(b) => Ok(ValkeyValueKey::Bool(b.to_bool())),
            _ => Err(ValkeyError::String(format!(
                "Given CallReply can not be used as a map key or a set element, {:?}",
                reply
            ))),
        }
    }
}
impl<'root> From<&CallReply<'root>> for RedisValue {
    fn from(reply: &CallReply<'root>) -> Self {
        match reply {
            CallReply::Unknown => RedisValue::StaticError("Error on method call"),
            CallReply::Array(reply) => {
                RedisValue::Array(reply.iter().map(|v| (&v).into()).collect())
            }
            CallReply::I64(reply) => RedisValue::Integer(reply.to_i64()),
            CallReply::String(reply) => RedisValue::SimpleString(reply.to_string().unwrap()),
            CallReply::Null(_) => RedisValue::Null,
            CallReply::Map(reply) => RedisValue::Map(
                reply
                    .iter()
                    .map(|(key, val)| {
                        (
                            (&key).try_into().unwrap_or_else(|e| {
                                panic!("Got unhashable map key from Redis, {key:?}, {e}")
                            }),
                            (&val).into(),
                        )
                    })
                    .collect(),
            ),
            CallReply::Set(reply) => RedisValue::Set(
                reply
                    .iter()
                    .map(|v| {
                        (&v).try_into().unwrap_or_else(|e| {
                            panic!("Got unhashable set element from Redis, {v:?}, {e}")
                        })
                    })
                    .collect(),
            ),
            CallReply::Bool(reply) => RedisValue::Bool(reply.to_bool()),
            CallReply::Double(reply) => RedisValue::Float(reply.to_double()),
            CallReply::BigNumber(reply) => RedisValue::BigNumber(reply.to_string().unwrap()),
            CallReply::VerbatimString(reply) => {
                RedisValue::VerbatimString(reply.to_parts().unwrap())
            }
        }
    }
}
impl<'root> From<&CallResult<'root>> for RedisValue {
    fn from(reply: &CallResult<'root>) -> Self {
        reply.as_ref().map_or_else(
            |e| {
                RedisValue::StringBuffer(e.as_bytes().to_vec())
            },
            |v| (v).into(),
        )
    }
}
impl<'root> TryFrom<&CallResult<'root>> for ValkeyValueKey {
    type Error = ValkeyError;
    fn try_from(reply: &CallResult<'root>) -> Result<Self, Self::Error> {
        reply.as_ref().map_or_else(
            |e| {
                Err(ValkeyError::String(
                    format!("Got an error reply which can not be translated into a map key or set element, {:?}", e),
                ))
            },
            |v| v.try_into(),
        )
    }
}
#[cfg(test)]
mod tests {
    use super::RedisValue;
    #[test]
    fn from_vec_string() {
        assert_eq!(
            RedisValue::from(vec!["foo".to_string()]),
            RedisValue::Array(vec![RedisValue::BulkString("foo".to_owned())])
        );
    }
    #[test]
    fn from_vec_str() {
        assert_eq!(
            RedisValue::from(vec!["foo"]),
            RedisValue::Array(vec![RedisValue::BulkString("foo".to_owned())])
        );
    }
    #[test]
    fn from_vec_string_ref() {
        assert_eq!(
            RedisValue::from(vec![&"foo".to_string()]),
            RedisValue::Array(vec![RedisValue::BulkString("foo".to_owned())])
        );
    }
    #[test]
    fn from_option_str() {
        assert_eq!(
            RedisValue::from(Some("foo")),
            RedisValue::BulkString("foo".to_owned())
        );
    }
    #[test]
    fn from_vec() {
        let v: Vec<u8> = vec![0, 3, 5, 21, 255];
        assert_eq!(
            RedisValue::from(v),
            RedisValue::StringBuffer(vec![0, 3, 5, 21, 255])
        );
    }
    #[test]
    fn from_option_none() {
        assert_eq!(RedisValue::from(None::<()>), RedisValue::Null,);
    }
}