import { Subject } from 'rxjs';
import { share } from 'rxjs/operators';
import { IS_BROWSER } from '../../constants';
import { IHttpHeaders, IHttpResponse, HttpMethod, IHttpEvent } from './types';
import { time } from '../time';
import { id as idUtil } from '../id';

export * from './types';

if (IS_BROWSER) {
  require('whatwg-fetch');
}

const _events$ = new Subject<IHttpEvent>();
export const events$ = _events$.pipe(share());

const DEFAULT_HEADERS = {
  Accept: 'application/json',
  'Content-Type': 'application/json',
};

export async function request<T>(
  method: HttpMethod,
  url: string,
  data?: any,
  headers?: IHttpHeaders,
): Promise<IHttpResponse<T>> {
  // Setup initial conditions.
  const timer = time.timer();
  const payload = {
    method,
    headers: new Headers({ ...DEFAULT_HEADERS, ...(headers || {}) }),
  };
  if (data) {
    (payload as any).body = JSON.stringify(data);
  }

  // Fire pre-event.
  const event: IHttpEvent = {
    id: idUtil.shortid(),
    stage: 'START',
    method,
    url,
    data,
    headers: payload.headers,
  };
  _events$.next(event);

  // Make the request.
  let ok = true;
  let status = 200;
  let statusText = 'OK';
  let json: any;
  let res: Response | undefined;

  try {
    res = await fetch(url, payload);
    ok = res.ok;
    status = res.status;
    statusText = res.statusText;
  } catch (err) {
    ok = false;
    status = 521;
    statusText = 'CONNECTION_REFUSED';
  }

  // Parse the response JSON.
  try {
    json = res ? await res.json() : undefined;
  } catch (error) {
    // Ignore.
  }

  // Prepare the response.
  const response: IHttpResponse<T> = {
    ok,
    status,
    statusText,
    elapsed: timer.elapsed(),
    data: json as T,
  };

  // Finish up.
  _events$.next({
    ...event,
    stage: 'COMPLETE',

    response,
  });
  return response;
}

/**
 * Curry's a set of HTTP methods with the given HTTP headers.
 */
export function headers(headers: IHttpHeaders) {
  return {
    get: <T>(url: string) => {
      return request<T>('GET', url, undefined, headers);
    },
    post: <T>(url: string, data: any) => {
      return request<T>('POST', url, data, headers);
    },
  };
}

const DEFAULT_METHODS = headers({});
export const get = DEFAULT_METHODS.get;
export const post = DEFAULT_METHODS.post;
