import {
  GraphQLID,
  GraphQLList,
  GraphQLNonNull,
  GraphQLObjectType,
  GraphQLSchema,
  GraphQLString,
} from 'graphql';
import withAuth from 'graphql-auth';
import {
  fromGlobalId,
  nodeDefinitions,
} from 'graphql-relay';

import { getUserInputType, getUserType } from './Types/User';
import {
  getGeolocationType,
  getGeolocationInputType,
  getGeolocationsType,
  getGeolocationsDistanceType,
} from './Types/Geolocations';
import {
  getActivityType,
  getActivityInputType,
} from './Types/Activity';
import {
  getScoreType,
  getScoreInputType,
} from './Types/Score';
import pubsub from './Services/PubSub';

const {
  nodeField,
  nodeInterface,
} = nodeDefinitions(
  (globalId, { loaders }) => {
    const { id, type } = fromGlobalId(globalId);
    if (type === 'User') {
      return loaders.user.load(id);
    }
    return null;
  },
  (obj) => {
    if (obj.hasOwnProperty('email')) {
      return UserType;
    }
    return null;
  }
);

const UserType = getUserType(nodeInterface);
const UserInputType = getUserInputType();

const ScoreType = getScoreType(nodeInterface);
const ScoreInputType = getScoreInputType();

const ActivityType = getActivityType('Activities');
const ActivityInputType = getActivityInputType();

const GeolocationType = getGeolocationType(nodeInterface);
const GeolocationInputType = getGeolocationInputType();

const GeolocationsType = getGeolocationsType(nodeInterface);

const GeolocationDistanceType = getGeolocationsDistanceType(nodeInterface);

const QueryType = new GraphQLObjectType({
  name: 'Query',
  description: 'Query root',
  fields: () => ({
    users: {
      type: new GraphQLList(UserType),
      description: 'get all users',
      resolve: withAuth((root, args, { loaders }) => loaders.user.loadAll()),
    },
    node: nodeField,
    user: {
      type: UserType,
      description: 'get an user',
      args: {
        id: { type: new GraphQLNonNull(GraphQLID) },
      },
      resolve: withAuth((root, args, { loaders }) => loaders.user.load(args.id)),
    },
    activities: {
      type: new GraphQLList(ActivityType),
      description: 'get all activity',
      resolve: (root, args, { loaders }) => loaders.activity.loadAll(),
    },
    activity: {
      type: ActivityType,
      description: 'get an activity',
      args: {
        id: { type: new GraphQLNonNull(GraphQLID) },
      },
      resolve: (root, args, { loaders }) => loaders.activity.load(args.id),
    },
    scores: {
      type: new GraphQLList(ScoreType),
      description: 'get all scores',
      resolve: (root, args, { loaders }) => loaders.score.loadAll(),
    },
    score: {
      type: ScoreType,
      description: 'get a score',
      args: {
        id: { type: new GraphQLNonNull(GraphQLID) },
      },
      resolve: (root, args, { loaders }) => loaders.score.load(args.id),
    },
    geolocation: {
      type: GeolocationType,
      description: 'get user geolocation',
      args: {
        id: { type: new GraphQLNonNull(GraphQLString) },
        key: { type: new GraphQLNonNull(GraphQLString) },
      },
      resolve: (root, args, { loaders }) => loaders.geolocation.load(args),
    },
    geolocations: {
      type: new GraphQLList(GeolocationsType),
      description: 'get user geolocation',
      args: {
        id: { type: new GraphQLNonNull(GraphQLString) },
        key: { type: new GraphQLNonNull(GraphQLString) },
        distance: { type: new GraphQLNonNull(GraphQLString) },
        unit: { type: new GraphQLNonNull(GraphQLString) },
      },
      resolve: (root, args, { loaders }) => loaders.geolocation.loadCloseUsers(args),
    },
    geolocationDistance: {
      type: GeolocationDistanceType,
      description: 'get user geolocation',
      args: {
        id: { type: new GraphQLNonNull(GraphQLString) },
        key: { type: new GraphQLNonNull(GraphQLString) },
        foreignKey: { type: new GraphQLNonNull(GraphQLString) },
      },
      resolve: (root, args, { loaders }) => loaders.geolocation.loadDistanceFromUser(args),
    },
    userClearCache: {
      type: GraphQLString,
      description: 'clear user cache',
      resolve: (root, args, { loaders }) => loaders.user.clearAll(),
    },
    geolocationClearCache: {
      type: GraphQLString,
      description: 'clear geolocation cache',
      resolve: (root, args, { loaders }) => loaders.geolocation.clearAll(),
    },
    activityClearCache: {
      type: GraphQLString,
      description: 'clear activity cache',
      resolve: (root, args, { loaders }) => loaders.activity.clearAll(),
    },
    scoresClearCache: {
      type: GraphQLString,
      description: 'clear score cache',
      resolve: (root, args, { loaders }) => loaders.score.clearAll(),
    },
  }),
});

const MutationType = new GraphQLObjectType({
  name: 'Mutation',
  description: 'Mutation query root',
  fields: () => ({
    addUser: {
      type: UserType,
      description: 'add an user',
      args: {
        user: {
          type: UserInputType,
        },
      },
      resolve: (root, { user }, { loaders }) => loaders.user.addUser(user),
    },
    updateUser: {
      type: UserType,
      description: 'update an user',
      args: {
        user: {
          type: UserInputType,
        },
      },
      resolve: (root, { user }, { loaders }) => loaders.user.updateUser(user),
    },
    updateGeolocation: {
      type: new GraphQLList(GeolocationsType),
      description: 'update an geoloaction',
      args: {
        geolocation: {
          type: GeolocationInputType,
        },
      },
      resolve: async (root, { geolocation }, { loaders }) => {
        const matchs = await loaders.geolocation.updateGeolocation(geolocation);
        pubsub.publish(`MATCH_${geolocation.key}`, matchs);
        return matchs;
      },
    },
    addActivity: {
      type: ActivityType,
      description: 'add an activity',
      args: {
        activity: {
          type: ActivityInputType,
        },
      },
      resolve: (root, { activity }, { loaders }) => loaders.activity.addActivity(activity),
    },
    updateActivity: {
      type: ActivityType,
      description: 'update an activity',
      args: {
        activity: {
          type: ActivityInputType,
        },
      },
      resolve: (root, { activity }, { loaders }) => loaders.activity.updateActivity(activity),
    },
    addScore: {
      type: ScoreType,
      description: 'add a score',
      args: {
        score: {
          type: ScoreInputType,
        },
      },
      resolve: (root, { score }, { loaders }) => loaders.score.addScore(score),
    },
    updateScore: {
      type: ScoreType,
      description: 'update a score',
      args: {
        score: {
          type: ScoreInputType,
        },
      },
      resolve: (root, { score }, { loaders }) => loaders.score.updateScore(score),
    },
  }),
});

const SubscriptionType = new GraphQLObjectType({
  name: 'Subscription',
  description: 'Subs query root',
  fields: () => ({
    subMatch: {
      type: new GraphQLList(GeolocationsType),
      args: {
        id: { type: new GraphQLNonNull(GraphQLID) },
      },
      resolve: (payload, args, context, info) => payload,
      subscribe: (root, { id }, { loaders }) => pubsub.asyncIterator(`MATCH_${id}`),
    },
  }),
});

export default new GraphQLSchema({
  query: QueryType,
  mutation: MutationType,
  subscription: SubscriptionType,
});
