/*
 This file is part of GNU Taler
 (C) 2022 Taler Systems S.A.
 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.
 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see 
 */
/**
 * Imports.
 */
import { RequestThrottler, TalerErrorCode } from "@gnu-taler/taler-util";
import {
  Headers,
  HttpRequestLibrary,
  HttpRequestOptions,
  HttpResponse,
  TalerError,
} from "@gnu-taler/taler-wallet-core";
/**
 * An implementation of the [[HttpRequestLibrary]] using the
 * browser's XMLHttpRequest.
 */
export class ServiceWorkerHttpLib implements HttpRequestLibrary {
  private throttle = new RequestThrottler();
  private throttlingEnabled = true;
  async fetch(
    requestUrl: string,
    options?: HttpRequestOptions,
  ): Promise {
    const requestMethod = options?.method ?? "GET";
    const requestBody = options?.body;
    const requestHeader = options?.headers;
    if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) {
      const parsedUrl = new URL(requestUrl);
      throw TalerError.fromDetail(
        TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
        {
          requestMethod,
          requestUrl,
          throttleStats: this.throttle.getThrottleStats(requestUrl),
        },
        `request to origin ${parsedUrl.origin} was throttled`,
      );
    }
    const response = await fetch(requestUrl, {
      headers: requestHeader,
      body: requestBody,
      method: requestMethod,
      // timeout: options?.timeout
    });
    const headerMap = new Headers();
    response.headers.forEach((value, key) => {
      headerMap.set(key, value);
    });
    return {
      headers: headerMap,
      status: response.status,
      requestMethod,
      requestUrl,
      json: makeJsonHandler(response, requestUrl),
      text: makeTextHandler(response, requestUrl),
      bytes: async () => (await response.blob()).arrayBuffer(),
    };
  }
  get(url: string, opt?: HttpRequestOptions): Promise {
    return this.fetch(url, {
      method: "GET",
      ...opt,
    });
  }
  // FIXME: "Content-Type: application/json" goes here,
  // after Sebastian suggestion.
  postJson(
    url: string,
    body: any,
    opt?: HttpRequestOptions,
  ): Promise {
    return this.fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
      ...opt,
    });
  }
  stop(): void {
    // Nothing to do
  }
}
function makeTextHandler(response: Response, requestUrl: string) {
  return async function getJsonFromResponse(): Promise {
    let respText;
    try {
      respText = await response.text();
    } catch (e) {
      throw TalerError.fromDetail(
        TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
        {
          requestUrl,
          httpStatusCode: response.status,
        },
        "Invalid JSON from HTTP response",
      );
    }
    return respText;
  };
}
function makeJsonHandler(response: Response, requestUrl: string) {
  return async function getJsonFromResponse(): Promise {
    let responseJson;
    try {
      responseJson = await response.json();
    } catch (e) {
      throw TalerError.fromDetail(
        TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
        {
          requestUrl,
          httpStatusCode: response.status,
        },
        "Invalid JSON from HTTP response",
      );
    }
    if (responseJson === null || typeof responseJson !== "object") {
      throw TalerError.fromDetail(
        TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
        {
          requestUrl,
          httpStatusCode: response.status,
        },
        "Invalid JSON from HTTP response",
      );
    }
    return responseJson;
  };
}