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

use crate::{datafile::variable::Variable, network::dto::VariationDto};

pub(crate) const OFF_VALUE: &str = "off";

pub(crate) trait Variation: Send + Sync + Debug {
    type Variable: Variable;

    fn new(dto: VariationDto) -> Self;
    fn key(&self) -> &Arc<str>;
    fn name(&self) -> &Arc<str>;
    fn variables(&self) -> &HashMap<Arc<str>, Self::Variable>;
}

#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub(crate) struct VariationImpl<V: Variable> {
    key: Arc<str>,
    name: Arc<str>,
    variables: HashMap<Arc<str>, V>,
}

impl<V: Variable> Variation for VariationImpl<V> {
    type Variable = V;

    fn new(dto: VariationDto) -> Self {
        let variables = dto.variables.into_iter().map(V::new).map(|v| (v.key().clone(), v)).collect::<HashMap<_, _>>();
        Self {
            key: Arc::from(dto.key),
            name: Arc::from(dto.name),
            variables,
        }
    }

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

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

    fn variables(&self) -> &HashMap<Arc<str>, Self::Variable> {
        &self.variables
    }
}

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

    use crate::datafile::datafile_test_utils::{make_variable_dto, make_variation_dto};
    use crate::datafile::variable::{VariableImpl, VariableType};

    #[rstest]
    #[case("flagA", "BOOLEAN", json!(true))]
    #[case("numberA", "NUMBER", json!(42))]
    fn test_variables_are_mapped(#[case] key: &str, #[case] var_type: &str, #[case] value: serde_json::Value) {
        let var_dto = make_variable_dto(key, var_type, value.clone());
        let dto = make_variation_dto("on", vec![var_dto]);

        let variation: VariationImpl<VariableImpl> = VariationImpl::new(dto);
        // Variation
        assert_eq!(variation.key().as_ref(), "on");

        // Variable
        let variables = variation.variables();
        let variable = variables.get(key).expect("variable should exist");
        let expected_value = crate::types::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);
    }
}
