import {defaultDataIdFromObject, InMemoryCache} from 'apollo-cache-inmemory';
import {createHttpLink} from 'apollo-link-http';
import {onError} from 'apollo-link-error';
import {ApolloClient} from 'apollo-client';
import {ApolloLink, Observable} from 'apollo-link';
import fetch from 'unfetch';
import {typeDefs} from '@/graphql/typedefs';
import {resolvers} from '@/graphql/resolvers';

const writeLocalCache = cache => {
  // we use the cache as our local state
  cache.writeData({
    data: {
      scrollPosition: {
        __typename: 'ScrollPosition',
        scrollTo: '',
      },
      sidebar: {
        __typename: 'Sidebar',
        profile: false,
        navigation: false,
      },
      helloEmail: {
        __typename: 'HelloEmail',
        email: '',
      },
    },
  });
};

export default function (uri, networkErrorCallback) {
  const httpLink = createHttpLink({
    // uri: process.env.NODE_ENV !== 'production' ? 'http://localhost:8000/api/graphql/' : '/api/graphql/',
    uri,
    credentials: 'include',
    fetch: fetch,
    headers: {
      'X-CSRFToken': document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*=\s*([^;]*).*$)|^.*$/, '$1'),
    },
  });

  const consoleLink = new ApolloLink((operation, forward) => {
    // console.log(`starting request for ${operation.operationName}`);

    return forward(operation).map((data) => {
      // console.log(`ending request for ${operation.operationName}`);

      return data;
    });
  });

  // from https://github.com/apollographql/apollo-client/issues/1564#issuecomment-357492659
  const omitTypename = (key, value) => {
    return key === '__typename' ? undefined : value;
  };

  const createOmitTypenameLink = new ApolloLink((operation, forward) => {
    if (operation.variables) {
      operation.variables = JSON.parse(JSON.stringify(operation.variables), omitTypename);
    }

    return forward(operation);
  });

  const errorLink = onError(({response, operation, networkError, graphQLErrors}) => {
    if (networkError && networkErrorCallback) {
      networkErrorCallback(networkError.statusCode);
      return Observable.of();
    }

    if (graphQLErrors) {
      graphQLErrors.forEach(({message, locations, path}) =>
        console.log(
          `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
        ),
      );
    }

    /*
      The server redirects to the HTML login page, but apollo expects a JSON response. This is fine, we'll just ignore it
     */
    if (networkError && networkError.name === 'ServerParseError') {
      // workaround found here: https://github.com/apollographql/apollo-link/issues/855#issuecomment-485764697
      return Observable.of();
    }
  });

  const composedLink = ApolloLink.from([createOmitTypenameLink, consoleLink, errorLink, httpLink]);

  const cache = new InMemoryCache({
    dataIdFromObject: obj => {
      switch (obj.__typename) {
        case 'InstrumentNode':
        case 'ModuleNode':
        case 'RoomEntryNode':
          return `${obj.__typename}:${obj.slug}`;
        default:
          return defaultDataIdFromObject(obj);
      }
    },
    cacheRedirects: {
      Query: {
        contentBlock: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ContentBlockNode', id: args.id}),
        chapter: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ChapterNode', id: args.id}),
        assignment: (_, args, {getCacheKey}) => getCacheKey({__typename: 'AssignmentNode', id: args.id}),
        objective: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveNode', id: args.id}),
        objectiveGroup: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ObjectiveGroupNode', id: args.id}),
        projectEntry: (_, args, {getCacheKey}) => getCacheKey({__typename: 'ProjectEntryNode', id: args.id}),
        project: (_, args, {getCacheKey}) => {
          if (args.slug) {
            return getCacheKey({__typename: 'ProjectNode', id: args.slug});
          } else {
            return getCacheKey({__typename: 'ProjectNode', id: args.id});
          }
        },
      },
    },
  });

  // TODO: Monkey-patching in a fix for an open issue suggesting that
  // `readQuery` should return null or undefined if the query is not yet in the
  // cache: https://github.com/apollographql/apollo-feature-requests/issues/1
  cache.originalReadQuery = cache.readQuery;
  cache.readQuery = (...args) => {
    try {
      return cache.originalReadQuery(...args);
    } catch (err) {
      console.error(err);
      return undefined;
    }
  };

  writeLocalCache(cache);

  // Create the apollo client
  const client = new ApolloClient({
    link: composedLink,
    // link: httpLink,
    cache,
    connectToDevTools: true,
    typeDefs,
    resolvers,
  });
  client.onResetStore(() => {
    writeLocalCache(cache);
  });
  return client;
}
