use std::fmt::Debug;

use serde_json::Value as JsonValue;

use crate::network::dto::VariableDto;

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum VariableType {
    Boolean,
    Number,
    String,
    Json,
    Js,
    Css,
    Unknown(String),
}

impl VariableType {
    pub fn from_str(value: &str) -> Self {
        match value {
            "BOOLEAN" => Self::Boolean,
            "NUMBER" => Self::Number,
            "STRING" => Self::String,
            "JSON" => Self::Json,
            "JS" => Self::Js,
            "CSS" => Self::Css,
            _ => Self::Unknown(value.to_owned()),
        }
    }

    pub fn as_str(&self) -> &str {
        match self {
            Self::Boolean => "BOOLEAN",
            Self::Number => "NUMBER",
            Self::String => "STRING",
            Self::Json => "JSON",
            Self::Js => "JS",
            Self::Css => "CSS",
            Self::Unknown(kind) => &kind,
        }
    }
}

#[cfg_attr(test, mockall::automock)]
pub(crate) trait Variable: Send + Sync + Debug {
    fn new(dto: VariableDto) -> Self;
    fn key(&self) -> &str;
    fn var_type(&self) -> &VariableType;
    fn value(&self) -> &JsonValue;
}

#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct VariableImpl {
    key: String,
    var_type: VariableType,
    value: JsonValue,
}

impl Variable for VariableImpl {
    fn new(dto: VariableDto) -> Self {
        Self {
            key: dto.key,
            var_type: VariableType::from_str(&dto.var_type),
            value: dto.value,
        }
    }

    fn key(&self) -> &str {
        &self.key
    }

    fn var_type(&self) -> &VariableType {
        &self.var_type
    }

    fn value(&self) -> &JsonValue {
        &self.value
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::datafile::datafile_test_utils::make_variable_dto;
    use rstest::rstest;
    use serde_json::json;

    #[rstest]
    #[case("str_key", "STRING", json!("string_value"))]
    #[case("num_key", "NUMBER", json!(42))]
    #[case("bool_key", "BOOLEAN", json!(true))]
    #[case("object_key", "JSON", json!({"field": "value"}))]
    #[case("array_key", "JSON", json!([1, 2, 3]))]
    fn test_variable_impl(#[case] key: &str, #[case] var_type: &str, #[case] value: JsonValue) {
        let dto = make_variable_dto(key, var_type, value.clone());
        let variable = VariableImpl::new(dto);

        assert_eq!(variable.key(), key);
        assert_eq!(variable.var_type(), &VariableType::from_str(var_type));
        assert_eq!(variable.value(), &value);
    }
}
