use juniper::{
    object, DefaultScalarValue, ExecutionError, FieldError, GraphQLEnum, Value, Variables,
};

pub type QueryResult = Result<
    (
        Value<DefaultScalarValue>,
        Vec<ExecutionError<DefaultScalarValue>>,
    ),
    String,
>;

pub struct Context {}

impl Context {
    fn new() -> Self {
        Self {}
    }
}

impl juniper::Context for Context {}

#[derive(GraphQLEnum)]
pub enum Gender {
    Male,
    Female,
    Other,
}

#[derive(GraphQLEnum)]
pub enum UserKind {
    SuperAdmin,
    Admin,
    Moderator,
    User,
    Guest,
}

pub struct User {
    pub id: i32,
    pub kind: UserKind,
    pub username: String,
    pub email: String,
    pub gender: Option<Gender>,
}

impl User {
    fn new(id: i32) -> Self {
        Self {
            id,
            kind: UserKind::Admin,
            username: "userx".to_string(),
            email: "userx@domain.com".to_string(),
            gender: Some(Gender::Female),
        }
    }
}

#[object(Context = Context)]
impl User {}

pub struct Query;

#[object(Context = Context)]
impl Query {
    fn user_sync_instant(id: i32) -> Result<User, FieldError> {
        Ok(User::new(id))
    }

    fn users_sync_instant(ids: Option<Vec<i32>>) -> Result<Vec<User>, FieldError> {
        if let Some(ids) = ids {
            let users = ids.into_iter().map(User::new).collect();
            Ok(users)
        } else {
            Ok(vec![])
        }
    }

    async fn user_async_instant(id: i32) -> Result<User, FieldError> {
        Ok(User::new(id))
    }

    async fn users_async_instant(ids: Option<Vec<i32>>) -> Result<Vec<User>, FieldError> {
        if let Some(ids) = ids {
            let users = ids.into_iter().map(User::new).collect();
            Ok(users)
        } else {
            Ok(vec![])
        }
    }
}

pub struct Mutation;

#[object(Context = Context)]
impl Mutation {}

pub fn new_schema() -> juniper::RootNode<'static, Query, Mutation> {
    juniper::RootNode::new(Query, Mutation)
}

pub fn execute(query: &str, vars: Variables) -> QueryResult {
    let root = new_schema();
    let ctx = Context::new();
    juniper::execute(query, None, &root, &vars, &ctx).map_err(|e| format!("{:?}", e))
}

pub async fn execute_async(query: &str, vars: Variables) -> QueryResult {
    let root = new_schema();
    let ctx = Context::new();
    juniper::execute_async(query, None, &root, &vars, &ctx)
        .await
        .map_err(|e| format!("{:?}", e))
}