use std::{fmt::Debug, sync::Arc};

use crate::{network::dto::VariableDto, types::variable::JsonValue};

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

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

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

#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub struct VariableImpl {
    key: Arc<str>,
    kind: Arc<str>,
    var_type: VariableType,
    value: JsonValue,
}

impl Variable for VariableImpl {
    fn new(dto: VariableDto) -> Self {
        let kind: Arc<str> = Arc::from(dto.var_type);
        let var_type = VariableType::from_str(kind.as_ref());
        let value = JsonValue::from_datafile_value(&var_type, dto.value);

        Self {
            key: Arc::from(dto.key),
            kind,
            var_type,
            value,
        }
    }

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

    fn kind(&self) -> &Arc<str> {
        &self.kind
    }

    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: serde_json::Value) {
        let dto = make_variable_dto(key, var_type, value.clone());
        let variable = VariableImpl::new(dto);
        let expected_value = JsonValue::from_datafile_value(&VariableType::from_str(var_type), value);

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