use std::{
    any::Any,
    collections::HashMap,
    fmt::{self, Debug, Formatter},
};
use serde::{Deserialize, Deserializer, Serialize};
use crate::{
    parser::{parse_query, types::ExecutableDocument},
    schema::IntrospectionMode,
    Data, ParseRequestError, ServerError, UploadValue, Value, Variables,
};
#[non_exhaustive]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Request {
    #[serde(default)]
    pub query: String,
    #[serde(default, rename = "operationName")]
    pub operation_name: Option<String>,
    #[serde(default)]
    pub variables: Variables,
    #[serde(skip)]
    pub uploads: Vec<UploadValue>,
    #[serde(skip)]
    pub data: Data,
    #[serde(default)]
    pub extensions: HashMap<String, Value>,
    #[serde(skip)]
    pub(crate) parsed_query: Option<ExecutableDocument>,
    #[serde(skip)]
    pub introspection_mode: IntrospectionMode,
}
impl Request {
    pub fn new(query: impl Into<String>) -> Self {
        Self {
            query: query.into(),
            operation_name: None,
            variables: Variables::default(),
            uploads: Vec::default(),
            data: Data::default(),
            extensions: Default::default(),
            parsed_query: None,
            introspection_mode: IntrospectionMode::Enabled,
        }
    }
    #[must_use]
    pub fn operation_name<T: Into<String>>(self, name: T) -> Self {
        Self {
            operation_name: Some(name.into()),
            ..self
        }
    }
    #[must_use]
    pub fn variables(self, variables: Variables) -> Self {
        Self { variables, ..self }
    }
    #[must_use]
    pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
        self.data.insert(data);
        self
    }
    #[must_use]
    pub fn disable_introspection(mut self) -> Self {
        self.introspection_mode = IntrospectionMode::Disabled;
        self
    }
    #[must_use]
    pub fn only_introspection(mut self) -> Self {
        self.introspection_mode = IntrospectionMode::IntrospectionOnly;
        self
    }
    #[inline]
    pub fn parsed_query(&mut self) -> Result<&ExecutableDocument, ServerError> {
        if self.parsed_query.is_none() {
            match parse_query(&self.query) {
                Ok(parsed) => self.parsed_query = Some(parsed),
                Err(error) => return Err(error.into()),
            }
        }
        Ok(self.parsed_query.as_ref().unwrap())
    }
    pub fn set_parsed_query(&mut self, doc: ExecutableDocument) {
        self.parsed_query = Some(doc);
    }
    pub fn set_upload(&mut self, var_path: &str, upload: UploadValue) {
        fn variable_path<'a>(variables: &'a mut Variables, path: &str) -> Option<&'a mut Value> {
            let mut parts = path.strip_prefix("variables.")?.split('.');
            let initial = variables.get_mut(parts.next().unwrap())?;
            parts.try_fold(initial, |current, part| match current {
                Value::List(list) => part
                    .parse::<u32>()
                    .ok()
                    .and_then(|idx| usize::try_from(idx).ok())
                    .and_then(move |idx| list.get_mut(idx)),
                Value::Object(obj) => obj.get_mut(part),
                _ => None,
            })
        }
        let variable = match variable_path(&mut self.variables, var_path) {
            Some(variable) => variable,
            None => return,
        };
        self.uploads.push(upload);
        *variable = Value::String(format!("#__graphql_file__:{}", self.uploads.len() - 1));
    }
}
impl<T: Into<String>> From<T> for Request {
    fn from(query: T) -> Self {
        Self::new(query)
    }
}
impl Debug for Request {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.debug_struct("Request")
            .field("query", &self.query)
            .field("operation_name", &self.operation_name)
            .field("variables", &self.variables)
            .field("extensions", &self.extensions)
            .finish()
    }
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)] pub enum BatchRequest {
    Single(Request),
    #[serde(deserialize_with = "deserialize_non_empty_vec")]
    Batch(Vec<Request>),
}
impl BatchRequest {
    pub fn into_single(self) -> Result<Request, ParseRequestError> {
        match self {
            Self::Single(req) => Ok(req),
            Self::Batch(_) => Err(ParseRequestError::UnsupportedBatch),
        }
    }
    pub fn iter(&self) -> impl Iterator<Item = &Request> {
        match self {
            BatchRequest::Single(request) => {
                Box::new(std::iter::once(request)) as Box<dyn Iterator<Item = &Request>>
            }
            BatchRequest::Batch(requests) => Box::new(requests.iter()),
        }
    }
    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Request> {
        match self {
            BatchRequest::Single(request) => {
                Box::new(std::iter::once(request)) as Box<dyn Iterator<Item = &mut Request>>
            }
            BatchRequest::Batch(requests) => Box::new(requests.iter_mut()),
        }
    }
    #[must_use]
    pub fn variables(mut self, variables: Variables) -> Self {
        for request in self.iter_mut() {
            request.variables = variables.clone();
        }
        self
    }
    #[must_use]
    pub fn data<D: Any + Clone + Send + Sync>(mut self, data: D) -> Self {
        for request in self.iter_mut() {
            request.data.insert(data.clone());
        }
        self
    }
    #[must_use]
    pub fn disable_introspection(mut self) -> Self {
        for request in self.iter_mut() {
            request.introspection_mode = IntrospectionMode::Disabled;
        }
        self
    }
    #[must_use]
    pub fn introspection_only(mut self) -> Self {
        for request in self.iter_mut() {
            request.introspection_mode = IntrospectionMode::IntrospectionOnly;
        }
        self
    }
}
fn deserialize_non_empty_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
where
    D: Deserializer<'de>,
    T: Deserialize<'de>,
{
    use serde::de::Error as _;
    let v = <Vec<T>>::deserialize(deserializer)?;
    if v.is_empty() {
        Err(D::Error::invalid_length(0, &"a non-empty sequence"))
    } else {
        Ok(v)
    }
}
impl From<Request> for BatchRequest {
    fn from(r: Request) -> Self {
        BatchRequest::Single(r)
    }
}
impl From<Vec<Request>> for BatchRequest {
    fn from(r: Vec<Request>) -> Self {
        BatchRequest::Batch(r)
    }
}
#[cfg(test)]
mod tests {
    use crate::*;
    #[test]
    fn test_request() {
        let request: Request = from_value(value! ({
            "query": "{ a b c }"
        }))
        .unwrap();
        assert!(request.variables.is_empty());
        assert!(request.operation_name.is_none());
        assert_eq!(request.query, "{ a b c }");
    }
    #[test]
    fn test_request_with_operation_name() {
        let request: Request = from_value(value! ({
            "query": "{ a b c }",
            "operationName": "a"
        }))
        .unwrap();
        assert!(request.variables.is_empty());
        assert_eq!(request.operation_name.as_deref(), Some("a"));
        assert_eq!(request.query, "{ a b c }");
    }
    #[test]
    fn test_request_with_variables() {
        let request: Request = from_value(value! ({
            "query": "{ a b c }",
            "variables": {
                "v1": 100,
                "v2": [1, 2, 3],
                "v3": "str",
            }
        }))
        .unwrap();
        assert_eq!(
            request.variables.into_value(),
            value!({
                "v1": 100,
                "v2": [1, 2, 3],
                "v3": "str",
            })
        );
        assert!(request.operation_name.is_none());
        assert_eq!(request.query, "{ a b c }");
    }
    #[test]
    fn test_deserialize_request_with_empty_object_variables() {
        let request: Request = from_value(value! ({
            "query": "{ a b c }",
            "variables": {}
        }))
        .unwrap();
        assert!(request.operation_name.is_none());
        assert!(request.variables.is_empty());
    }
    #[test]
    fn test_deserialize_request_with_empty_array_variables() {
        let error: DeserializerError = from_value::<Request>(value! ({
            "query": "{ a b c }",
            "variables": []
        }))
        .unwrap_err();
        assert_eq!(error.to_string(), "invalid type: sequence, expected a map");
    }
    #[test]
    fn test_deserialize_request_with_null_variables() {
        let request: Request = from_value(value! ({
            "query": "{ a b c }",
            "variables": null
        }))
        .unwrap();
        assert!(request.operation_name.is_none());
        assert!(request.variables.is_empty());
    }
    #[test]
    fn test_batch_request_single() {
        let request: BatchRequest = from_value(value! ({
            "query": "{ a b c }"
        }))
        .unwrap();
        if let BatchRequest::Single(request) = request {
            assert!(request.variables.is_empty());
            assert!(request.operation_name.is_none());
            assert_eq!(request.query, "{ a b c }");
        } else {
            unreachable!()
        }
    }
    #[test]
    fn test_batch_request_batch() {
        let request: BatchRequest = from_value(value!([
            {
                "query": "{ a b c }"
            },
            {
                "query": "{ d e }"
            }
        ]))
        .unwrap();
        if let BatchRequest::Batch(requests) = request {
            assert!(requests[0].variables.is_empty());
            assert!(requests[0].operation_name.is_none());
            assert_eq!(requests[0].query, "{ a b c }");
            assert!(requests[1].variables.is_empty());
            assert!(requests[1].operation_name.is_none());
            assert_eq!(requests[1].query, "{ d e }");
        } else {
            unreachable!()
        }
    }
}