use crate::{
    config::{TestBaseConfiguration, TestEndpoint},
    endpoint::Endpoint,
    error::RatError,
    store::{Store, StoreMap, VariableStore},
};
use regex::{Captures, Regex};
use std::{cell::OnceCell, collections::HashMap};

pub fn endpoint<S: Store>(
    store: &S,
    test: &TestBaseConfiguration,
    endpoint: &TestEndpoint,
) -> Endpoint {
    let url = match_and_replace(store, &endpoint.url);
    let endpoint = Endpoint::new(&url, &endpoint.method, test.verbose);
    endpoint.unwrap()
}

const REGEX: OnceCell<Regex> = OnceCell::new();
const PATTERN: &str = r#"\{\{(([\.]?(\w+[-]?|\[\d+\]))+)\}\}"#;

pub fn match_and_replace<S: Store>(store: &S, hydrate: &str) -> String {
    let r = REGEX;
    let rgx: &Regex = r.get_or_init(|| Regex::new(PATTERN).expect("pattern is invalid"));
    let result = rgx.replace_all(hydrate, |cap: &Captures| {
        let key = &cap[1];
        log::debug!("{:#?} {}", cap, key);

        let value = if let Some(x) = store.fetch_value(key) {
            x
        } else {
            return format!("{{{{{}}}}}", key);
        };

        //.expect(format!("could not find '{}'", key).as_ref());
        value
            .as_str()
            .map(|f| f.to_string())
            .unwrap_or(value.to_string())
    });

    result.into_owned()
}

pub fn outputs<T: Store>(
    store: &T,
    outputs_from_config: HashMap<String, String>,
) -> Result<VariableStore, RatError> {
    // create map for outputs
    // ensure variables used in outputs are hydrated from the latest store
    // ?? evaluate the hydrated output content?
    // finally, assign it to the output key
    let mut evaluated_outputs = StoreMap::default();
    for (key, value) in outputs_from_config.into_iter() {
        let value = match_and_replace(store, &value);
        evaluated_outputs.insert(key, serde_json::to_value(&value)?);
    }

    log::info!("{:#?}", evaluated_outputs);

    Ok(evaluated_outputs.into())
}

pub fn reponse(response: reqwest::blocking::Response) -> Result<Vec<VariableStore>, RatError> {
    let store_response: VariableStore = util::store_from_response(&response)?.into();
    let store_response_body = util::store_from_response_body(response)?;
    Ok(vec![store_response, store_response_body])
}

mod util {
    use crate::{
        error::RatError,
        store::{StoreMap, VariableStore},
    };

    pub fn store_from_response(
        response: &reqwest::blocking::Response,
    ) -> Result<StoreMap, RatError> {
        let mut store = StoreMap::default();

        store.insert(
            "r.status".to_string(),
            serde_json::to_value(response.status().as_u16()).unwrap(),
        );

        for (key, value) in response.headers().iter().filter(|(_, v)| !v.is_empty()) {
            let key = format!("r.headers.{}", key);
            let value = serde_json::to_value(
                value
                    .to_str()
                    .map_err(|e| RatError::ParsingError(e.to_string()))?,
            )?;

            for (k, v) in self::parse_v2(key, value) {
                store.insert(k, v);
            }
        }

        Ok(store)
    }

    pub fn store_from_response_body(
        response: reqwest::blocking::Response,
    ) -> Result<VariableStore, RatError> {
        let value: serde_json::Value = response.json()?;
        let mut store = StoreMap::default();

        for v in self::parse_v2(String::from("r.body"), value) {
            store.insert(v.0, v.1);
        }

        Ok(store.into())
    }

    pub fn parse_v2(key: String, value: serde_json::Value) -> Vec<(String, serde_json::Value)> {
        let mut result = Vec::new();
        match value {
            serde_json::Value::Null => result.push((key, value)),
            serde_json::Value::Bool(_) => result.push((key, value)),
            serde_json::Value::Number(_) => result.push((key, value)),
            serde_json::Value::String(_) => result.push((key, value)),
            serde_json::Value::Array(x) => {
                for (i, item) in x.into_iter().enumerate() {
                    let key = format!("{}.[{}]", key, i);
                    result.append(&mut self::parse_v2(key, item));
                }
            }
            serde_json::Value::Object(x) => {
                for (name, field) in x {
                    let key = format!("{}.{}", key, name);
                    result.append(&mut self::parse_v2(key, field));
                }
            }
        };
        result
    }
}