import { ApolloClient, ApolloLink, FetchMoreOptions } from '@apollo/client';
import {
  defaultDataIdFromObject,
  InMemoryCache,
  NormalizedCacheObject,
} from '@apollo/client/cache';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
import { captureException } from '@sentry/browser';
import { createUploadLink } from 'apollo-upload-client';
import fetch from 'isomorphic-unfetch';
import getConfig from 'next/config';
import { IContext } from 'typings/context';

import { isValid, refreshAuth } from './auth';

interface IApolloOptions {
  fetchOptions?: FetchMoreOptions;
  token: () => string;
  appContext?: IContext;
  pathname?: string;
  isWhiteListed?: boolean;
}

const { publicRuntimeConfig } = getConfig();
const isBrowser = typeof window !== 'undefined';
let apolloClient: ApolloClient<NormalizedCacheObject> | null = null;

// Polyfill fetch() on the server (used by apollo-client)
if (!isBrowser) {
  global.fetch = fetch;
}

function createClient(
  initalState: NormalizedCacheObject,
  { fetchOptions, token, isWhiteListed }: IApolloOptions,
) {
  const uploadLink = createUploadLink({
    uri: publicRuntimeConfig.API_ENDPOINT,
    fetchOptions,
  });

  const authLink = setContext((_, { headers }) => {
    // Set set token from storage to auth header here
    const _token = token();
    if (!isValid(_token) && isBrowser && !isWhiteListed) {
      refreshAuth(
        _token,
        `${window.location.pathname}${window.location.search && '?'}${window.location.search}`,
      );
    }

    return {
      ...headers,
      headers: {
        ['accept-language']: 'is-IS', // Send selected locale when start using translations
        Authorization: _token ? `Bearer ${_token}` : '',
      },
    };
  });

  const errorLink = onError(({ operation, graphQLErrors, networkError }) => {
    if (graphQLErrors) {
      graphQLErrors.map(({ message, locations, path, extensions }: any) => {
        const { code } = extensions;

        const graphError = `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}, Code: ${code}`;

        console.error(graphError); // eslint-disable-line no-console

        captureException(graphError);
      });
    }

    if (networkError) {
      console.error(`[Network error]: ${networkError} ${operation.operationName}`); // eslint-disable-line no-console
    }
  });

  const link = ApolloLink.from([errorLink, authLink, uploadLink]);

  const client = new ApolloClient({
    connectToDevTools: isBrowser,
    ssrMode: !isBrowser,
    link,
    cache: new InMemoryCache({
      dataIdFromObject: (object) => {
        switch (object.__typename) {
          case 'Account':
            return object.ssn;
          case 'Profile':
            return object.subscriptionId; // use `key` as the primary key
          case 'FiberOrderAvailableAppointmentSlots':
            return object.startTime;
          default:
            return defaultDataIdFromObject(object); // fall back to default handling
        }
      },
    }).restore(initalState),
  });

  return client;
}

export default function initApollo(initialState: NormalizedCacheObject, options: IApolloOptions) {
  if (!isBrowser) {
    const fetchOptions = {};

    return createClient(initialState, {
      ...options,
      fetchOptions,
    });
  }

  if (!apolloClient) {
    apolloClient = createClient(initialState, options);
  }

  return apolloClient;
}
