use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::ops::{Deref, DerefMut};

use indexmap::IndexMap;
use serde::{Serialize, Serializer};
use value::{ConstValue, Name, Variables};

use crate::types::{FetchQuery, VariablesRef};
use crate::Request;

#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum PlanNode<'a> {
    Sequence(SequenceNode<'a>),
    Parallel(ParallelNode<'a>),
    Introspection(IntrospectionNode),
    Fetch(FetchNode<'a>),
    Flatten(FlattenNode<'a>),
}

impl<'a> PlanNode<'a> {
    pub(crate) fn flatten(self) -> Self {
        match self {
            PlanNode::Sequence(mut node) if node.nodes.len() == 1 => node.nodes.remove(0),
            PlanNode::Parallel(mut node) if node.nodes.len() == 1 => node.nodes.remove(0),
            _ => self,
        }
    }
}

#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct PathSegment<'a> {
    pub name: &'a str,
    pub is_list: bool,
    pub possible_type: Option<&'a str>,
}

#[derive(Clone, Default, Hash, Eq, PartialEq)]
pub struct ResponsePath<'a>(Vec<PathSegment<'a>>);

impl<'a> Debug for ResponsePath<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        for (idx, segment) in self.0.iter().enumerate() {
            if idx > 0 {
                write!(f, ".")?;
            }
            if segment.is_list {
                write!(f, "[{}]", segment.name)?;
            } else {
                write!(f, "{}", segment.name)?;
            }
            if let Some(possible_type) = segment.possible_type {
                write!(f, "({})", possible_type)?;
            }
        }
        Ok(())
    }
}

impl<'a> Display for ResponsePath<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        Debug::fmt(self, f)
    }
}

impl<'a> Serialize for ResponsePath<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'a> Deref for ResponsePath<'a> {
    type Target = Vec<PathSegment<'a>>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<'a> DerefMut for ResponsePath<'a> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

#[derive(Default, Debug, Serialize)]
pub struct SequenceNode<'a> {
    pub nodes: Vec<PlanNode<'a>>,
}

#[derive(Default, Debug, Serialize)]
pub struct ParallelNode<'a> {
    pub nodes: Vec<PlanNode<'a>>,
}

#[derive(Debug, Serialize)]
pub struct IntrospectionDirective {
    pub name: Name,

    #[serde(skip_serializing_if = "IndexMap::is_empty")]
    pub arguments: IndexMap<Name, ConstValue>,
}

#[derive(Debug, Serialize)]
pub struct IntrospectionField {
    pub name: Name,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub alias: Option<Name>,

    #[serde(skip_serializing_if = "IndexMap::is_empty")]
    pub arguments: IndexMap<Name, ConstValue>,

    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub directives: Vec<IntrospectionDirective>,

    pub selection_set: IntrospectionSelectionSet,
}

#[derive(Debug, Default, Serialize)]
#[serde(transparent)]
pub struct IntrospectionSelectionSet(pub Vec<IntrospectionField>);

#[derive(Debug, Serialize)]
#[serde(transparent)]
pub struct IntrospectionNode {
    pub selection_set: IntrospectionSelectionSet,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchNode<'a> {
    pub service: &'a str,
    #[serde(skip_serializing_if = "VariablesRef::is_empty")]
    pub variables: VariablesRef<'a>,
    pub query: FetchQuery<'a>,
}

impl<'a> FetchNode<'a> {
    pub fn to_request(&self) -> Request {
        Request::new(self.query.to_string()).variables(self.variables.to_variables())
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FlattenNode<'a> {
    pub path: ResponsePath<'a>,
    pub prefix: usize,
    pub service: &'a str,
    #[serde(skip_serializing_if = "VariablesRef::is_empty")]
    pub variables: VariablesRef<'a>,
    pub query: FetchQuery<'a>,
}

impl<'a> FlattenNode<'a> {
    pub fn to_request(&self, representations: Variables) -> Request {
        Request::new(self.query.to_string())
            .variables(representations)
            .extend_variables(self.variables.to_variables())
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SubscribeNode<'a> {
    pub subscribe_nodes: Vec<FetchNode<'a>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub flatten_node: Option<PlanNode<'a>>,
}

#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum RootNode<'a> {
    Subscribe(SubscribeNode<'a>),
    Query(PlanNode<'a>),
}
