use std::collections::HashMap;

use anyhow::{anyhow, Result};
use rust_database::*;
use serde::{Deserialize, Serialize};

fn str_to_bool(s: &str) -> bool {
    if s == "ja" {
        true
    } else {
        false
    }
}

fn check_none_str(s: &str) -> Option<String> {
    if s == "None" {
        None
    } else {
        Some(s.to_string())
    }
}

pub struct VariableNames<'a> {
    value_field_name: &'a str,
    rights_field_name: &'a str,
}

impl<'a> From<&'a str> for VariableNames<'a> {
    fn from(value: &'a str) -> Self {
        Self {
            value_field_name: value,
            rights_field_name: value,
        }
    }
}

impl<'a> From<(&'a str, &'a str)> for VariableNames<'a> {
    fn from((value_field_name, rights_field_name): (&'a str, &'a str)) -> Self {
        Self {
            value_field_name,
            rights_field_name,
        }
    }
}

pub trait CombineRights: Sized {
    fn combine<'a>(
        variable_map: &HashMap<String, String>,
        rights_map: &HashMap<String, Rights>,
        variable_name: impl Into<VariableNames<'a>>,
    ) -> Result<Self>;
}

#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
pub struct GuardedVariable<T: Clone> {
    pub name: String,
    pub value: T,
    pub right: Rights,
}

impl<T: Clone> GuardedVariable<T> {
    pub fn new(name: &str, value: T, right: Rights) -> Self {
        Self {
            name: name.to_string(),
            value,
            right,
        }
    }
}

impl GuardedVariable<f32> {
    pub fn as_measurement<'a>(&'a self) -> Measurement<'a> {
        Measurement {
            name: &self.name,
            value: self.value,
            rights: Some(self.right),
        }
    }
}

impl CombineRights for GuardedVariable<bool> {
    fn combine<'a>(
        variable_map: &HashMap<String, String>,
        rights_map: &HashMap<String, Rights>,
        variable_name: impl Into<VariableNames<'a>>,
    ) -> Result<Self> {
        let var_name = variable_name.into();

        Ok(GuardedVariable::new(
            var_name.value_field_name,
            str_to_bool(variable_map.get(var_name.value_field_name).ok_or(anyhow!(
                "Variable {} not found in value map",
                var_name.value_field_name
            ))?),
            *rights_map.get(var_name.rights_field_name).ok_or(anyhow!(
                "Variable {} not found in rights",
                var_name.rights_field_name
            ))?,
        ))
    }
}

impl CombineRights for GuardedVariable<Option<String>> {
    fn combine<'a>(
        variable_map: &HashMap<String, String>,
        rights_map: &HashMap<String, Rights>,
        variable_name: impl Into<VariableNames<'a>>,
    ) -> Result<Self> {
        let var_name = variable_name.into();

        Ok(GuardedVariable::new(
            var_name.value_field_name,
            check_none_str(variable_map.get(var_name.value_field_name).ok_or(anyhow!(
                "Variable {} not found in value map",
                var_name.value_field_name
            ))?),
            *rights_map.get(var_name.rights_field_name).ok_or(anyhow!(
                "Variable {} not found in rights",
                var_name.rights_field_name
            ))?,
        ))
    }
}

impl CombineRights for GuardedVariable<String> {
    fn combine<'a>(
        variable_map: &HashMap<String, String>,
        rights_map: &HashMap<String, Rights>,
        variable_name: impl Into<VariableNames<'a>>,
    ) -> Result<Self> {
        let var_name = variable_name.into();

        Ok(GuardedVariable::new(
            var_name.value_field_name,
            variable_map
                .get(var_name.value_field_name)
                .ok_or(anyhow!(
                    "Variable {} not found in value map",
                    var_name.value_field_name,
                ))?
                .clone(),
            *rights_map.get(var_name.rights_field_name).ok_or(anyhow!(
                "Variable {} not found in rights",
                var_name.rights_field_name
            ))?,
        ))
    }
}

impl CombineRights for GuardedVariable<f32> {
    fn combine<'a>(
        variable_map: &HashMap<String, String>,
        rights_map: &HashMap<String, Rights>,
        variable_name: impl Into<VariableNames<'a>>,
    ) -> Result<Self> {
        let var_name = variable_name.into();

        Ok(GuardedVariable::new(
            var_name.value_field_name,
            variable_map
                .get(var_name.value_field_name)
                .ok_or(anyhow!(
                    "Variable {} not found in value map",
                    var_name.value_field_name
                ))?
                .parse()
                .map_err(|err| anyhow!("{} for {}", err, var_name.value_field_name))?,
            *rights_map.get(var_name.rights_field_name).ok_or(anyhow!(
                "Variable {} not found in rights",
                var_name.rights_field_name
            ))?,
        ))
    }
}

#[cfg(test)]
mod test {
    use super::{GuardedVariable, Rights};
    use anyhow::Result;
    use serde_json::{from_str, to_string_pretty};

    #[test]
    fn test_vec_serialisation() -> Result<()> {
        let t = vec![
            GuardedVariable::new("var1", "test123", Rights::PremiumUser),
            GuardedVariable::new("var1", "test456", Rights::SuperUser),
            GuardedVariable::new("var1", "test789", Rights::PTSUser),
        ];

        let str = to_string_pretty(&t)?;

        println!("{str}");

        let r: Vec<GuardedVariable<&str>> = from_str(&str)?;

        assert_eq!(t, r);

        Ok(())
    }
}
