import { logout } from "@/utils/auth";
import { onError } from "@apollo/client/link/error";
import { fromPromise } from "@apollo/client/core";
import { refreshAccessToken } from "./utils";
import { GraphQLError } from "graphql";
import { generateTokenHeader } from "./auth";

let isRefreshing = false;
let pendingRequests: any[] = [];

const resolvePendingRequests = () => {
  pendingRequests.map((callback) => callback());
  pendingRequests = [];
};

const isAuthError = (graphQLError: GraphQLError | undefined) => {
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  return graphQLError && [403, 401].includes(graphQLError.status_code);
};

const errorLink = onError(
  ({ graphQLErrors, networkError, forward, operation }) => {
    console.debug("We have an apollo error", [graphQLErrors, networkError]);
    if (
      graphQLErrors?.some((graphQLError) => isAuthError(graphQLError)) ||
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      networkError === 401
    ) {
      console.debug("It's a authorization error (statusCode 401)");
      let forwardOperation;

      if (!isRefreshing) {
        console.debug("Setting isRefreshing to true");
        isRefreshing = true;

        forwardOperation = fromPromise(
          refreshAccessToken()
            .then((res) => {
              if (res !== true) {
                // failed to refresh the token
                throw "Failed to refresh the token";
              }
              resolvePendingRequests();

              const context = operation.getContext();
              const oldHeaders = context.headers;

              operation.setContext({
                headers: {
                  ...oldHeaders,
                  authorization: generateTokenHeader(),
                },
              });
              return true;
            })
            .catch((e) => {
              console.debug("Something failed, let's logout", e);
              pendingRequests = [];
              // don't perform a logout since we don't have any working access/refresh tokens
              logout(false);
              return;
            })
            .finally(() => {
              isRefreshing = false;
            })
        ).filter((value) => Boolean(value));
      } else {
        forwardOperation = fromPromise(
          new Promise((resolve) => {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            pendingRequests.push(() => resolve());
          })
        );
      }

      return forwardOperation.flatMap(() => forward(operation));
    }

    if (graphQLErrors) {
      graphQLErrors.map(
        (graphQLError: GraphQLError & { status_code?: number }) => {
          if (graphQLError?.status_code !== 401) {
            console.debug(
              `[GraphQL error]: Message: ${graphQLError.message}, Location: ${graphQLError.locations}, Path: ${graphQLError.path}`
            );
          }
        }
      );
    }

    if (networkError) {
      console.error(`[Network error]: ${networkError}`);
      console.debug(JSON.stringify(networkError));
    }
  }
);

export default errorLink;
