Documentation
use super::super::method::Method;
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::collections::HashMap;

#[derive(Debug)]
pub enum RequestError {
    InvalidJson(serde_json::Error),
    InvalidUtf8(std::string::FromUtf8Error),
    InvalidFormData(String),
    MissingField(String),
    ValidationError(super::form_request::ValidationErrors),
}

impl std::fmt::Display for RequestError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RequestError::InvalidJson(e) => write!(f, "JSON inválido: {}", e),
            RequestError::InvalidUtf8(e) => write!(f, "UTF-8 inválido: {}", e),
            RequestError::InvalidFormData(e) => write!(f, "Datos de formulario inválidos: {}", e),
            RequestError::MissingField(field) => write!(f, "Campo requerido: {}", field),
            RequestError::ValidationError(errors) => {
                write!(f, "Errores de validación: {:?}", errors)
            }
        }
    }
}

impl std::error::Error for RequestError {}

#[derive(Debug, Clone)]
pub struct Request {
    pub method: Method,
    pub path: String,
    pub headers: HashMap<String, String>,
    pub body: Vec<u8>,
    pub params: HashMap<String, String>,
    pub query_params: HashMap<String, String>,
    pub query_string: Option<String>,
    cached_json: std::cell::RefCell<Option<Value>>,
    cached_form_data: std::cell::RefCell<Option<HashMap<String, String>>>,
}

impl Request {
    pub fn new(method: Method, path: String) -> Self {
        Self {
            method,
            path,
            headers: HashMap::new(),
            body: Vec::new(),
            params: HashMap::new(),
            query_params: HashMap::new(),
            query_string: None,
            cached_json: std::cell::RefCell::new(None),
            cached_form_data: std::cell::RefCell::new(None),
        }
    }

    pub fn param(&self, key: &str) -> Option<&String> {
        self.params.get(key)
    }

    pub fn query(&self, key: &str) -> Option<&String> {
        self.query_params.get(key)
    }

    pub fn query_or<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
        self.query_params
            .get(key)
            .map(|s| s.as_str())
            .unwrap_or(default)
    }

    pub fn header(&self, key: &str) -> Option<&String> {
        self.headers
            .iter()
            .find(|(k, _)| k.eq_ignore_ascii_case(key))
            .map(|(_, v)| v)
    }

    pub fn bearer_token(&self) -> Option<String> {
        self.header("Authorization").and_then(|auth| {
            if auth.starts_with("Bearer ") {
                Some(auth[7..].to_string())
            } else {
                None
            }
        })
    }

    pub fn body_as_string(&self) -> Result<String, RequestError> {
        String::from_utf8(self.body.clone()).map_err(RequestError::InvalidUtf8)
    }

    pub fn is_json(&self) -> bool {
        self.header("Content-Type")
            .map(|ct| ct.contains("application/json"))
            .unwrap_or(false)
    }

    pub fn is_form(&self) -> bool {
        self.header("Content-Type")
            .map(|ct| ct.contains("application/x-www-form-urlencoded"))
            .unwrap_or(false)
    }

    pub fn json<T: DeserializeOwned>(&self) -> Result<T, RequestError> {
        serde_json::from_slice(&self.body).map_err(RequestError::InvalidJson)
    }

    pub fn json_value(&self) -> Result<Value, RequestError> {
        let mut cache = self.cached_json.borrow_mut();

        if cache.is_none() {
            let value = serde_json::from_slice(&self.body).map_err(RequestError::InvalidJson)?;
            *cache = Some(value);
        }

        Ok(cache.as_ref().unwrap().clone())
    }

    pub fn input(&self, key: &str) -> Option<String> {
        if let Some(val) = self.query(key) {
            return Some(val.clone());
        }

        if self.is_json() {
            if let Ok(json) = self.json_value() {
                if let Some(val) = json.get(key) {
                    return match val {
                        Value::String(s) => Some(s.clone()),
                        Value::Number(n) => Some(n.to_string()),
                        Value::Bool(b) => Some(b.to_string()),
                        _ => None,
                    };
                }
            }
        }

        if self.is_form() {
            if let Ok(form) = self.form_data() {
                return form.get(key).cloned();
            }
        }

        None
    }

    pub fn input_or(&self, key: &str, default: &str) -> String {
        self.input(key).unwrap_or_else(|| default.to_string())
    }

    pub fn all_inputs(&self) -> HashMap<String, Value> {
        let mut inputs = HashMap::new();

        for (key, val) in &self.query_params {
            inputs.insert(key.clone(), Value::String(val.clone()));
        }

        if self.is_json() {
            if let Ok(json) = self.json_value() {
                if let Value::Object(map) = json {
                    for (key, val) in map {
                        inputs.insert(key, val);
                    }
                }
            }
        }

        if self.is_form() {
            if let Ok(form) = self.form_data() {
                for (key, val) in form {
                    inputs.insert(key, Value::String(val));
                }
            }
        }

        inputs
    }

    pub fn only(&self, keys: &[&str]) -> HashMap<String, Value> {
        let all = self.all_inputs();
        keys.iter()
            .filter_map(|&key| all.get(key).map(|v| (key.to_string(), v.clone())))
            .collect()
    }

    pub fn except(&self, keys: &[&str]) -> HashMap<String, Value> {
        let all = self.all_inputs();
        all.into_iter()
            .filter(|(key, _)| !keys.contains(&key.as_str()))
            .collect()
    }

    pub fn form_data(&self) -> Result<HashMap<String, String>, RequestError> {
        let mut cache = self.cached_form_data.borrow_mut();

        if cache.is_none() {
            let body_str = self.body_as_string()?;
            let data: HashMap<String, String> = url::form_urlencoded::parse(body_str.as_bytes())
                .into_owned()
                .collect();
            *cache = Some(data);
        }

        Ok(cache.as_ref().unwrap().clone())
    }

    pub fn validate<F: super::form_request::FormRequest + Default>(
        &self,
        _form_request: &F,
    ) -> Result<HashMap<String, Value>, RequestError> {
        let data = self.all_inputs();
        F::validate_data(data).map_err(RequestError::ValidationError)
    }

    pub fn full_url(&self) -> String {
        if let Some(qs) = &self.query_string {
            format!("{}?{}", self.path, qs)
        } else {
            self.path.clone()
        }
    }
}