use crate::{ArgumentValue, ArgumentValueObject};
use indexmap::IndexMap;
use itertools::Itertools;
use schema::constants::filters;
use std::borrow::Cow;
pub type SelectionArgument = (String, ArgumentValue);
#[derive(Debug, Clone)]
pub struct Selection {
    name: String,
    alias: Option<String>,
    arguments: Vec<(String, ArgumentValue)>,
    nested_selections: Vec<Selection>,
}
impl PartialEq for Selection {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
            && self.alias == other.alias
            && self.arguments.len() == other.arguments.len()
            && self.nested_selections.len() == other.nested_selections.len()
            && self.arguments.iter().all(|arg| other.arguments.contains(arg))
            && self
                .nested_selections
                .iter()
                .all(|sel| other.nested_selections.contains(sel))
    }
}
impl Selection {
    pub fn with_name(name: impl Into<String>) -> Selection {
        Selection::new(name.into(), None, Vec::new(), Vec::new())
    }
    pub fn new<T, A, N>(name: T, alias: Option<String>, arguments: A, nested_selections: N) -> Self
    where
        T: Into<String>,
        A: Into<Vec<SelectionArgument>>,
        N: Into<Vec<Selection>>,
    {
        Self {
            name: name.into(),
            alias,
            arguments: arguments.into(),
            nested_selections: nested_selections.into(),
        }
    }
    pub fn dedup(mut self) -> Self {
        self.nested_selections = self
            .nested_selections
            .into_iter()
            .unique_by(|s| s.name.clone())
            .collect();
        self
    }
    pub fn is_find_unique(&self) -> bool {
        self.name.starts_with("findUnique")
    }
    pub fn arguments(&self) -> &[(String, ArgumentValue)] {
        &self.arguments
    }
    pub fn pop_argument(&mut self) -> Option<(String, ArgumentValue)> {
        self.arguments.pop()
    }
    pub fn push_argument(&mut self, key: impl Into<String>, arg: impl Into<ArgumentValue>) {
        self.arguments.push((key.into(), arg.into()));
    }
    pub fn set_nested_selections(&mut self, sels: Vec<Selection>) {
        self.nested_selections = sels;
    }
    pub fn push_nested_selection(&mut self, selection: Selection) {
        self.nested_selections.push(selection);
    }
    pub fn contains_nested_selection(&self, name: &str) -> bool {
        self.nested_selections.iter().any(|sel| sel.name() == name)
    }
    pub fn nested_selections(&self) -> &[Self] {
        &self.nested_selections
    }
    pub fn name(&self) -> &str {
        &self.name
    }
    pub fn alias(&self) -> &Option<String> {
        &self.alias
    }
    pub fn set_alias(&mut self, alias: Option<String>) {
        self.alias = alias
    }
}
#[derive(Debug, Clone, PartialEq)]
pub enum SelectionSet<'a> {
    Single(Cow<'a, str>, Vec<ArgumentValue>),
    Multi(Vec<Vec<Cow<'a, str>>>, Vec<Vec<ArgumentValue>>),
    Empty,
}
impl<'a> Default for SelectionSet<'a> {
    fn default() -> Self {
        Self::Empty
    }
}
impl<'a> SelectionSet<'a> {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn push(self, column: impl Into<Cow<'a, str>>, value: ArgumentValue) -> Self {
        let column = column.into();
        match self {
            Self::Single(key, mut vals) if key == column => {
                vals.push(value);
                Self::Single(key, vals)
            }
            Self::Single(key, mut vals) => {
                vals.push(value);
                Self::Multi(vec![vec![key, column]], vec![vals])
            }
            Self::Multi(mut keys, mut vals) => {
                match (keys.last_mut(), vals.last_mut()) {
                    (Some(keys), Some(vals)) if !keys.contains(&column) => {
                        keys.push(column);
                        vals.push(value);
                    }
                    _ => {
                        keys.push(vec![column]);
                        vals.push(vec![value]);
                    }
                }
                Self::Multi(keys, vals)
            }
            Self::Empty => Self::Single(column, vec![value]),
        }
    }
    pub fn len(&self) -> usize {
        match self {
            Self::Single(_, _) => 1,
            Self::Multi(v, _) => v.len(),
            Self::Empty => 0,
        }
    }
    pub fn is_single(&self) -> bool {
        matches!(self, Self::Single(_, _))
    }
    pub fn is_multi(&self) -> bool {
        matches!(self, Self::Multi(_, _))
    }
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
    pub fn keys(&self) -> Vec<&str> {
        match self {
            Self::Single(key, _) => vec![key.as_ref()],
            Self::Multi(keys, _) => match keys.first() {
                Some(keys) => keys.iter().map(|key| key.as_ref()).collect(),
                None => Vec::new(),
            },
            Self::Empty => Vec::new(),
        }
    }
}
pub struct In<'a> {
    selection_set: SelectionSet<'a>,
}
impl<'a> In<'a> {
    pub fn new(selection_set: SelectionSet<'a>) -> Self {
        Self { selection_set }
    }
}
impl<'a> From<In<'a>> for ArgumentValue {
    fn from(other: In<'a>) -> Self {
        match other.selection_set {
            SelectionSet::Multi(key_sets, val_sets) => {
                let key_vals = key_sets.into_iter().zip(val_sets);
                let conjuctive = key_vals.fold(Conjuctive::new(), |acc, (keys, vals)| {
                    let ands = keys.into_iter().zip(vals).fold(Conjuctive::new(), |acc, (key, val)| {
                        let mut argument = IndexMap::new();
                        argument.insert(key.into_owned(), val);
                        acc.and(argument)
                    });
                    acc.or(ands)
                });
                ArgumentValue::from(conjuctive)
            }
            SelectionSet::Single(key, vals) => ArgumentValue::object([(
                key.to_string(),
                ArgumentValue::object([(filters::IN.to_owned(), ArgumentValue::list(vals))]),
            )]),
            SelectionSet::Empty => ArgumentValue::null(),
        }
    }
}
#[derive(Debug, PartialEq)]
pub enum Conjuctive {
    Or(Vec<Conjuctive>),
    And(Vec<Conjuctive>),
    Single(ArgumentValueObject),
    None,
}
impl From<ArgumentValueObject> for Conjuctive {
    fn from(map: ArgumentValueObject) -> Self {
        Self::Single(map)
    }
}
impl Default for Conjuctive {
    fn default() -> Self {
        Self::None
    }
}
impl Conjuctive {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn or(mut self, operation: impl Into<Conjuctive>) -> Self {
        match self {
            Self::Or(ref mut operations) => {
                operations.push(operation.into());
                self
            }
            Self::None => operation.into(),
            _ => Self::Or(vec![self, operation.into()]),
        }
    }
    pub fn and(mut self, operation: impl Into<Conjuctive>) -> Self {
        match self {
            Self::And(ref mut operations) => {
                operations.push(operation.into());
                self
            }
            Self::None => operation.into(),
            _ => Self::And(vec![self, operation.into()]),
        }
    }
}
impl From<Conjuctive> for ArgumentValue {
    fn from(conjuctive: Conjuctive) -> Self {
        match conjuctive {
            Conjuctive::None => Self::null(),
            Conjuctive::Single(obj) => ArgumentValue::object(single_to_multi_filter(obj)),
            Conjuctive::Or(conjuctives) => {
                let conditions: Vec<ArgumentValue> = conjuctives.into_iter().map(ArgumentValue::from).collect();
                ArgumentValue::object([("OR".to_string(), ArgumentValue::list(conditions))])
            }
            Conjuctive::And(conjuctives) => {
                let conditions: Vec<ArgumentValue> = conjuctives.into_iter().map(ArgumentValue::from).collect();
                ArgumentValue::object([("AND".to_string(), ArgumentValue::list(conditions))])
            }
        }
    }
}
fn single_to_multi_filter(obj: ArgumentValueObject) -> ArgumentValueObject {
    let mut new_obj: ArgumentValueObject = IndexMap::new();
    for (key, value) in obj {
        new_obj.insert(key, ArgumentValue::object([(filters::EQUALS.to_owned(), value)]));
    }
    new_obj
}