import {
  AuthenticationError,
  FeatureNotFoundError,
  NetworkError,
} from './errors';
import { RequestResult } from './types';
import { ClientError } from 'graphql-request';
import { KanaGroupClientFullConfig } from './KanaGroupClientConfig';

export async function request<T, E>(
  config: KanaGroupClientFullConfig,
  call: () => Promise<T>,
): Promise<RequestResult<T, E>> {
  for (let i = 1; i < Number.MAX_SAFE_INTEGER; i++) {
    try {
      const data = await call();
      return { data };
    } catch (error: any) {
      const knownError = formatError(error);
      if (config.retry && config.retry(knownError, i)) {
        continue;
      }

      if (config.onError) {
        await config.onError(knownError);
      }

      return { error: knownError };
    }
  }

  throw new Error('this line should not be called, internal error');
}

const knownErrors = [AuthenticationError, NetworkError, FeatureNotFoundError];
const knownErrorsMap: any = {};

for (const err of knownErrors) {
  knownErrorsMap[err.name] = err;
}

function formatError(error: Error) {
  if ((error as any).code === 'ECONNREFUSED') {
    return new NetworkError('No network connection.');
  }

  if (error instanceof ClientError) {
    const firstGraphQLError = error.response?.errors?.length
      ? error.response?.errors[0]
      : undefined;

    if (firstGraphQLError?.message?.startsWith('Unauthenticated')) {
      return new AuthenticationError(firstGraphQLError.message);
    }

    if (firstGraphQLError?.extensions?.exception) {
      const exp = firstGraphQLError.extensions.exception as any;
      const knownErrorType = knownErrorsMap[exp.name ?? ''];
      if (knownErrorType) {
        return new knownErrorType(exp.message);
      }
    }
  }

  return error;
}
