import { logger } from 'tachyon-logger';
import { CLIENT_ID } from 'mweb/common/config/twitchClientID';

export const GQL_URL = 'https://gql.twitch.tv/gql';

const TIMEOUT = 1500; // Implemented for node-fetch but not whatwg-fetch.

export enum GQLErrorType {
  RequestTimeout = 'request-timeout',
  BodyTimeout = 'body-timeout',
}

export interface GQLError extends Partial<GQLErrorJSON> {
  status: number;
  type?: GQLErrorType;
}

interface GQLbody {
  query: string;
  variables?: Object;
  operationName?: string;
}

interface ShouldLogErrors {
  (jsonErrors: any): boolean;
}

const alwaysLogError: ShouldLogErrors = () => true;

export async function fetchGQL<ResponseT>(
  query: string,
  variables: Object,
  operationName: string,
  shouldLogErrors: ShouldLogErrors = alwaysLogError,
): Promise<ResponseT> {
  const body: GQLbody = { query };
  if (operationName) {
    body.operationName = operationName;
  }
  if (variables) {
    body.variables = variables;
  }
  const fetchOptions = {
    timeout: TIMEOUT,
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      'Client-Id': CLIENT_ID,
      // Latch language to en-US until SD-1514 is finished.
      'Accept-Language': 'en-US',
    },
    method: 'POST',
    body: JSON.stringify(body),
  };

  const timerName = generateTimerName();
  console.time(timerName);
  const response = await fetch(GQL_URL, fetchOptions);
  console.timeEnd(timerName);
  if (response.status < 200 || 299 < response.status) {
    logAPIError(query, variables, response);
    throw { status: response.status };
  }
  const json = await response.json();
  if (json.errors) {
    if (shouldLogErrors(json.errors)) {
      logGQLError(query, variables, json);
    }
    throw { status: 500, errors: json.errors };
  }
  return json;
}

async function logAPIError(
  query: string,
  variables: Object | undefined,
  response: Response,
): Promise<void> {
  const details = await response.text();
  logger.error({
    message: 'Failed GQL request',
    status: response.status,
    query,
    variables,
    details,
  });
}

let counter = 0;
function generateTimerName(): string {
  const thisTimer = counter;
  counter = (counter + 1) % (Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1);
  return `GQL_REQUEST_${thisTimer}`;
}

interface GQLErrorJSON {
  errors: {
    message: string;
    locations: { line: number; column: number }[];
  }[];
}

function logGQLError(
  query: string,
  variables: Object | undefined,
  json: GQLErrorJSON,
): void {
  logger.error({
    message: 'Errors in GQL request',
    errors: json.errors,
    query,
    variables,
  });
}
