import { getOnCallApiPath } from 'helpers/consts';
import { FaroHelper } from 'helpers/faro';
import { formatBackendError, openErrorNotification } from 'helpers/helpers';
import { safeJSONStringify } from 'helpers/string';
import createClient from 'openapi-fetch';
import qs from 'query-string';

import { paths } from './autogenerated-api.types';

const showApiError = (status: number, errorData: string | Record<string, unknown>) => {
  if (status >= 400 && status < 500) {
    const text = formatBackendError(errorData);
    if (text) {
      openErrorNotification(text);
    }
  }
};

export const getCustomFetchFn =
  ({ withGlobalErrorHandler }: { withGlobalErrorHandler: boolean }) =>
  async (url: string, requestConfig: Parameters<typeof fetch>[1] & { headers?: Headers } = {}): Promise<Response> => {
    /**
     * In short, this header will tell the Grafana plugin proxy, a Go service which use Go's HTTP Transport,
     * to retry POST requests (and other non-idempotent requests). This doesn't necessarily make these requests
     * idempotent, but it will make them retry-able from Go's (read: net/http) perspective.
     *
     * https://stackoverflow.com/questions/42847294/how-to-catch-http-server-closed-idle-connection-error/62292758#62292758
     * https://raintank-corp.slack.com/archives/C01C4K8DETW/p1692280544382739?thread_ts=1692279329.797149&cid=C01C4K8DETW
     */
    requestConfig.headers.set('X-Idempotency-Key', `${Date.now()}-${Math.random()}`);
    requestConfig.headers.set('Content-Type', 'application/json');

    FaroHelper.pushNetworkRequestEvent({
      method: requestConfig.method,
      url,
      body: `${safeJSONStringify(requestConfig.body)}`,
    });

    const res = await fetch(url, requestConfig);
    if (res.ok) {
      FaroHelper.pushFetchNetworkResponseEvent({ name: 'Request succeeded', res, method: requestConfig.method });
      return res;
    } else {
      const responseData = await res.clone().json();
      FaroHelper.pushFetchNetworkResponseEvent({ name: 'Request failed', res, method: requestConfig.method });
      FaroHelper.pushFetchNetworkError({ res, responseData, method: requestConfig.method });
      if (withGlobalErrorHandler) {
        showApiError(res.status, responseData);
      }

      throw res;
    }
  };

const clientConfig = {
  baseUrl: getOnCallApiPath(),
  querySerializer: (params: unknown) => qs.stringify(params, { arrayFormat: 'none' }),
};

// We might want to switch to middleware instead of 2 clients once this is published: https://github.com/drwpow/openapi-typescript/pull/1521
const onCallApiWithGlobalErrorHandling: ReturnType<typeof createClient<paths>> = createClient<paths>({
  ...clientConfig,
  fetch: getCustomFetchFn({ withGlobalErrorHandler: true }),
});
const onCallApiSkipErrorHandling: ReturnType<typeof createClient<paths>> = createClient<paths>({
  ...clientConfig,
  fetch: getCustomFetchFn({ withGlobalErrorHandler: false }),
});

export function onCallApi({ skipErrorHandling = false }: { skipErrorHandling?: boolean } = {}):
  | typeof onCallApiSkipErrorHandling
  | typeof onCallApiWithGlobalErrorHandling {
  return skipErrorHandling ? onCallApiSkipErrorHandling : onCallApiWithGlobalErrorHandling;
}
