use std::collections::HashMap;

pub type StoreMap = HashMap<String, serde_json::Value>;

pub trait Store {
    fn fetch_value(&self, key: &str) -> Option<&serde_json::Value>;
}

impl<T: Store> Store for Vec<T> {
    fn fetch_value(&self, key: &str) -> Option<&serde_json::Value> {
        for s in self.iter() {
            let value = s.fetch_value(key);
            if value.is_some() {
                return value;
            }
        }

        None
    }
}

impl<T: Store> Store for [T] {
    fn fetch_value(&self, key: &str) -> Option<&serde_json::Value> {
        for s in self {
            let value = s.fetch_value(key);
            if value.is_some() {
                return value;
            }
        }

        None
    }
}

impl<T: Store> Store for &[T] {
    fn fetch_value(&self, key: &str) -> Option<&serde_json::Value> {
        for s in self.iter() {
            let value = s.fetch_value(key);
            if value.is_some() {
                return value;
            }
        }

        None
    }
}

#[derive(Debug)]
pub struct VariableStore {
    map: StoreMap,
}

impl Default for VariableStore {
    fn default() -> Self {
        Self {
            map: Default::default(),
        }
    }
}

impl Store for VariableStore {
    fn fetch_value(&self, key: &str) -> Option<&serde_json::Value> {
        self.map.get(key)
    }
}

impl VariableStore {
    pub fn from_map(map: StoreMap) -> Self {
        Self { map }
    }
}

impl From<StoreMap> for VariableStore {
    fn from(value: StoreMap) -> Self {
        VariableStore::from_map(value)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::factory;

    pub fn parse(value: &str) -> serde_json::Value {
        match serde_json::from_str(value) {
            Ok(v) => v,
            Err(_) => serde_json::to_value(value).unwrap_or(serde_json::Value::Null),
        }
    }

    pub fn equal<T: Store>(store: &T, key: &str, value: &str) -> bool {
        let variable = match store.fetch_value(key) {
            Some(v) => v,
            None => return false,
        };

        let value: serde_json::Value = serde_json::from_str(value).unwrap();
        variable == &value
    }

    /* #[test]
    fn variable_store_works() {
        let request = Endpoint::new("https://jsonplaceholder.typicode.com", "get", None)
            .unwrap()
            .build();
        let response = reqwest::blocking::Client::new().execute(request).unwrap();
        let config = Config::read().unwrap();
        let store_env: VariableStore = config.environment.into();
        let store_response: VariableStore = VariableStore::from_response(&response).into();

        let store = vec![store_env, store_response];
        //store.print();

        assert!(VariableStore::equal(&store, "r.status", "200"));
        assert!(VariableStore::equal(
            &store,
            "r.headers.content-type",
            "\"text/html; charset=UTF-8\""
        ));
        assert!(VariableStore::equal(
            &store,
            "r.headers.access-control-allow-credentials",
            "true"
        ));
    } */

    #[test]
    fn variable_replacement_works() {
        let mut map = StoreMap::new();
        map.insert("key".to_string(), self::parse("value"));
        map.insert("number".to_string(), self::parse("123"));
        map.insert("bool".to_string(), self::parse("false"));

        let store = VariableStore::from_map(map);

        let hydrated = factory::match_and_replace(&store, "{{key}}");
        assert_eq!(hydrated, "value");

        let hydrated = factory::match_and_replace(&store, "{{bool}} == {{number}}");
        assert_eq!(hydrated, "false == 123");
    }

    #[test]
    fn json_variable_replacement_works() {
        let mut map = StoreMap::new();
        map.insert("header.status".to_string(), self::parse("200"));
        map.insert(
            "r.headers.content-type".to_string(),
            self::parse("application/json"),
        );

        let store = VariableStore::from_map(map);

        let hydrated = factory::match_and_replace(&store, "{{r.headers.content-type}}");
        assert_eq!(hydrated, "application/json");

        let hydrated = factory::match_and_replace(&store, "{{header.status}} == 200");
        assert_eq!(hydrated, "200 == 200");
    }

    #[test]
    fn json_array_variable_replacement_works() {
        let mut map = StoreMap::new();

        map.insert("r.body.[0].title".to_string(), self::parse("hello world"));

        let store = VariableStore::from_map(map);

        let hydrated =
            factory::match_and_replace(&store, "\"{{r.body.[0].title}}\" == \"hello world\"");
        assert_eq!(hydrated, "\"hello world\" == \"hello world\"");
    }
}