import router from "../router";

function stringifyPrimitive(v) {
  switch (typeof v) {
    case "string":
      return v;

    case "boolean":
      return v ? "true" : "false";

    case "number":
      return isFinite(v) ? v : "";

    default:
      return "";
  }
}

////////////////////////////////////////////
//  Try to handle an api error
// return true if handled or false if not
//
function handleError(action, error, route) {
  const errorMessage =
    "API error: " + action + " route =" + route + ", message =" + error.message;
  window.$gz.store.commit("logItem", errorMessage);

  //Handle 403 not authorized
  //popup not authorized, log, then go to HOME
  //was going to go back one page, but realized most of the time a not authorized is in
  //reaction to directly entered or opened link, not application logic driving it, so home is safest choice
  //
  if (error.message && error.message.includes("NotAuthorized")) {
    window.$gz.eventBus.$emit(
      "notify-warning",
      window.$gz.translation.get("ErrorUserNotAuthorized")
    );
    router.push(window.$gz.store.state.homePage);

    throw new Error("LT:ErrorUserNotAuthorized");
  }

  //Handle 401 not authenticated
  if (error.message && error.message.includes("NotAuthenticated")) {
    window.$gz.eventBus.$emit(
      "notify-error",
      window.$gz.translation.get("ErrorUserNotAuthenticated")
    );

    router.push("/login");

    throw new Error("LT:ErrorUserNotAuthenticated");
  }

  //is it a network error?
  //https://medium.com/@vinhlh/how-to-handle-networkerror-when-using-fetch-ff2663220435
  if (error instanceof TypeError) {
    if (
      error.message.includes("Failed to fetch") ||
      error.message.includes("NetworkError") ||
      error.message.includes("Network request failed")
    ) {
      let msg = "";

      if (window.$gz.store.state.authenticated) {
        msg = window.$gz.translation.get("ErrorServerUnresponsive");
      } else {
        msg = "Could not connect to AyaNova server ";
      }

      msg += window.$gz.api.APIUrl("") + "\r\nError: " + error.message;

      window.$gz.eventBus.$emit("notify-error", msg);
      //note: using translation key in square brackets

      throw new Error(msg);
    }
  }

  //Ideally this should never get called because any issue should be addressed above
  window.$gz.errorHandler.handleFormError(error);
}

export default {
  status(response) {
    //############ THIS IS DEPRECATED IN FAVOR OF STATUSEX BELOW ##################
    //Handle expected api errors
    if (response.status == 401) {
      throw new Error("LT:ErrorUserNotAuthenticated");
    }

    if (response.status == 403) {
      throw new Error("LT:ErrorUserNotAuthorized");
    }

    //404 not found is an expected status not worth logging allow to bubble up
    //for client code to deal with
    if (response.status == 404) {
      return Promise.resolve(response);
    }

    if (response.status == 405) {
      //Probably a development error

      throw new Error("Method Not Allowed (route issue?) " + response.url);
    }

    if (response.status >= 200 && response.status < 300) {
      return Promise.resolve(response);
    } else {
      //log unhandled api error
      window.$gz.store.commit(
        "logItem",
        "API error: status=" +
          response.status +
          ", statusText=" +
          response.statusText +
          ", url=" +
          response.url
      );
      //let it float up for dealing with by caller(s)
      return Promise.resolve(response);
    }
  },
  statusEx(response, data) {
    //Handle expected api errors
    if (response.status == 401) {
      throw new Error("LT:ErrorUserNotAuthenticated");
    }

    if (response.status == 403) {
      throw new Error("LT:ErrorUserNotAuthorized");
    }

    //404 not found is an expected status not worth logging allow to bubble up
    //for client code to deal with
    if (response.status == 404) {
      return;
    }

    if (response.status == 405) {
      //Probably a development error

      throw new Error("Method Not Allowed (route issue?) " + response.url);
    }

    //Bad request error need to see the request in the log for troubleshooting more esoteric errors remotely
    //case 4485
    if (response.status == 400) {
      window.$gz.store.commit(
        "logItem",
        "API error: status=" +
          response.status +
          ", statusText=" +
          response.statusText +
          ", url=" +
          response.url +
          ", request data=" +
          JSON.stringify(data)
      );
      return;
    }

    if (response.status >= 200 && response.status < 300) {
      return;
    } else {
      //log unhandled api error

      window.$gz.store.commit(
        "logItem",
        "API error: status=" +
          response.status +
          ", statusText=" +
          response.statusText +
          ", url=" +
          response.url
      );
    }
  },
  async extractBodyEx(response) {
    if (response.status == 204) {
      //no content, nothing to process
      return response;
    }
    const contentType = response.headers.get("content-type");

    if (!contentType) {
      return response;
    }
    if (contentType.includes("json")) {
      return await response.json();
    }
    if (contentType.includes("text/plain")) {
      return await response.text();
    }

    if (contentType.includes("application/pdf")) {
      return await response.blob();
    }
    return response;
  },
  extractBody(response) {
    if (response.status == 204) {
      //no content, nothing to process
      return response;
    }
    const contentType = response.headers.get("content-type");
    if (!contentType) {
      return response;
    }
    if (contentType.includes("json")) {
      return response.json();
    }
    if (contentType.includes("text/plain")) {
      return response.text();
    }
    return response;
  },
  apiErrorToHumanString(apiError) {
    //empty error object?
    if (!apiError) {
      return "(E18) - apiErrorToHumanString():: Empty API eror, unknown";
    }
    //convert to readable string
    return "(E18) - " + JSON.stringify(apiError);
  },
  patchAuthorizedHeaders() {
    return {
      Accept: "application/json",
      "Content-Type": "application/json-patch+json",
      Authorization: "Bearer " + window.$gz.store.state.apiToken
    };
  },
  postAuthorizedHeaders() {
    return {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: "Bearer " + window.$gz.store.state.apiToken
      //this maybe useful in future like batch ops etc so keeping as a reminder
      //,"X-AY-Import-Mode": true
    };
  },
  postUnAuthorizedHeaders() {
    return {
      Accept: "application/json",
      "Content-Type": "application/json"
    };
  },
  fetchPostNoAuthOptions(data) {
    return {
      method: "post",
      mode: "cors",
      headers: this.postUnAuthorizedHeaders(),
      body: JSON.stringify(data)
    };
  },
  fetchPostOptions(data) {
    return {
      method: "post",
      mode: "cors",
      headers: this.postAuthorizedHeaders(),
      body: JSON.stringify(data)
    };
  },
  fetchPutOptions(data) {
    return {
      method: "put",
      mode: "cors",
      headers: this.postAuthorizedHeaders(),
      body: JSON.stringify(data)
    };
  },
  fetchGetOptions() {
    /* GET WITH AUTH */
    return {
      method: "get",
      mode: "cors",
      headers: this.postAuthorizedHeaders()
    };
  },
  fetchRemoveOptions() {
    /* REMOVE WITH AUTH */
    return {
      method: "delete",
      mode: "cors",
      headers: this.postAuthorizedHeaders()
    };
  },
  APIUrl(apiPath) {
    if (
      window.$gz.dev &&
      window.location.hostname == "localhost" &&
      window.location.port == "8080"
    ) {
      return "http://localhost:7575/api/v8.0/" + apiPath;
    }

    return (
      window.location.protocol +
      "//" +
      window.location.host +
      "/api/v8.0/" +
      apiPath
    );
  },
  helpUrl() {
    if (
      window.$gz.dev &&
      window.location.hostname == "localhost" &&
      window.location.port == "8080"
    ) {
      return "http://localhost:7575/docs/";
    }
    return window.location.protocol + "//" + window.location.host + "/docs/";
  },
  helpUrlCustomer() {
    if (
      window.$gz.dev &&
      window.location.hostname == "localhost" &&
      window.location.port == "8080"
    ) {
      return "http://localhost:7575/cust/";
    }
    return window.location.protocol + "//" + window.location.host + "/cust/";
  },
  /////////////////////////////
  // Just the server itself
  // used by profiler etc
  //
  ServerBaseUrl() {
    return this.helpUrl().replace("/docs/", "/");
  },
  /////////////////////////////
  // generic routed download URL
  //
  genericDownloadUrl(route) {
    //http://localhost:7575/api/v8/backup/download/100?t=sssss
    return this.APIUrl(route + "?t=" + window.$gz.store.state.downloadToken);
  },
  /////////////////////////////
  // report file download URL
  //
  reportDownloadUrl(fileName) {
    //http://localhost:7575/api/v8/report/download/filename.pdf?t=sssss

    return this.APIUrl(
      "report/download/" +
        fileName +
        "?t=" +
        window.$gz.store.state.downloadToken
    );
  },
  /////////////////////////////
  // backup file download URL
  //
  backupDownloadUrl(fileName) {
    //http://localhost:7575/api/v8/backup/download/100?t=sssss

    return this.APIUrl(
      "backup/download/" +
        fileName +
        "?t=" +
        window.$gz.store.state.downloadToken
    );
  },
  /////////////////////////////
  // attachment download URL
  //
  attachmentDownloadUrl(fileId, ctype) {
    //http://localhost:7575/api/v8/attachment/download/100?t=sssss
    //Ctype is optional and is the MIME content type, used to detect image urls at client for drag and drop ops
    //in wiki but ignored by server

    let url =
      "attachment/download/" +
      fileId +
      "?t=" +
      window.$gz.store.state.downloadToken;

    if (ctype && ctype.includes("image")) {
      url += "&i=1";
    }

    return this.APIUrl(url);
  },
  /////////////////////////////
  // logo download URL
  // (size= 'small', 'medium', 'large')
  logoUrl(size) {
    //http://localhost:7575/api/v8/logo/small
    return this.APIUrl("logo/" + size);
  },
  /////////////////////////////
  // REPLACE END OF URL
  // (used to change ID in url)
  replaceAfterLastSlash(theUrl, theReplacement) {
    return theUrl.substr(0, theUrl.lastIndexOf("\\") + 1) + theReplacement;
  },
  /////////////////////////////
  //  ENCODE QUERY STRING
  //
  buildQuery(obj, sep, eq, name) {
    sep = sep || "&";
    eq = eq || "=";
    if (obj === null) {
      obj = undefined;
    }

    if (typeof obj === "object") {
      return Object.keys(obj)
        .map(function(k) {
          const ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
          if (Array.isArray(obj[k])) {
            return obj[k]
              .map(function(v) {
                return ks + encodeURIComponent(stringifyPrimitive(v));
              })
              .join(sep);
          } else {
            return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
          }
        })
        .filter(Boolean)
        .join(sep);
    }

    if (!name) return "";
    return (
      encodeURIComponent(stringifyPrimitive(name)) +
      eq +
      encodeURIComponent(stringifyPrimitive(obj))
    );
  },
  ///////////////////////////////////
  //  GET DATA FROM API SERVER
  //
  async get(route) {
    try {
      const that = this;

      let r = await fetch(that.APIUrl(route), that.fetchGetOptions());
      that.statusEx(r);
      r = await that.extractBodyEx(r);
      return r;
    } catch (error) {
      //fundamental error, can't proceed with this call
      handleError("GET", error, route);
    }
  },

  //////////////////////////////////////
  // Test delay for troubleshooting
  //
  doDelayAsync: () => {
    // eslint-disable-next-line
    return new Promise(resolve => {
      setTimeout(() => resolve("I did something"), 10000);
    });
  },
  ///////////////////////////////////
  //  POST / PUT DATA TO API SERVER
  //
  async upsert(route, data, isLogin = false) {
    try {
      const that = this;
      //determine if this is a new or existing record
      let fetchOptions = undefined;
      //put?
      if (data && data.concurrency) {
        fetchOptions = that.fetchPutOptions(data);
      } else {
        //post
        //ensure the route doesn't end in /0 which will happen if it's a new record
        //since the edit forms just send the url here with the ID regardless
        if (route.endsWith("/0")) {
          route = route.slice(0, -2);
        }
        if (isLogin == false) {
          fetchOptions = that.fetchPostOptions(data);
        } else {
          fetchOptions = that.fetchPostNoAuthOptions(data);
        }
      }

      let r = await fetch(that.APIUrl(route), fetchOptions);
      that.statusEx(r, data);

      let rBody = await that.extractBodyEx(r);
      //Bad request error need to see the request in the log for troubleshooting more esoteric errors remotely
      //case 4485
      if (r.status == 400) {
        window.$gz.store.commit(
          "logItem",
          "API error on upsert: bad request, response body=" +
            JSON.stringify(rBody)
        );
      }
      return rBody;
    } catch (error) {
      if (isLogin == false) {
        handleError("UPSERT", error, route);
      } else {
        //specifically this is for the login page
        console.log("upsert error is: ", error);
        throw new Error(window.$gz.errorHandler.errorToString(error));
      }
    }
  },
  ///////////////////////////////////
  //  DELETE DATA FROM API SERVER
  //
  async remove(route) {
    const that = this;
    try {
      let r = await fetch(that.APIUrl(route), that.fetchRemoveOptions());
      that.statusEx(r);
      //delete will return a body if there is an error of some kind with the request
      r = await that.extractBodyEx(r);
      return r;
    } catch (error) {
      //fundamental error, can't proceed with this call
      handleError("DELETE", error, route);
    }
  },
  ///////////////////////////////////
  //   PUT DATA TO API SERVER
  // (used for puts that can't have a concurrency token like above)
  async put(route, data) {
    try {
      const that = this;
      let r = await fetch(that.APIUrl(route), that.fetchPutOptions(data));
      that.statusEx(r, data);
      r = await that.extractBodyEx(r);
      return r;
    } catch (error) {
      handleError("PUT", error, route);
    }
  },
  ///////////////////////////////////
  //   POST DATA TO API SERVER
  // (used for post only routes not needing upserts)
  async post(route, data) {
    try {
      const that = this;
      let r = await fetch(that.APIUrl(route), that.fetchPostOptions(data));
      that.statusEx(r, data);
      r = await that.extractBodyEx(r);
      return r;
    } catch (error) {
      handleError("POST", error, route);
    }
  },
  ///////////////////////////////////
  //  POST FILE ATTACHMENTS
  // @param {ayaId:objectid, ayaType:aType, files:[array of files]}
  //
  async uploadAttachment(at) {
    const that = this;
    try {
      var files = at.files;
      var data = new FormData();
      for (var i = 0; i < files.length; i++) {
        data.append(files[i].name, files[i]);
      }

      data.append("AttachToAType", at.ayaType);
      data.append("AttachToObjectId", at.ayaId);
      data.append("Notes", at.notes);
      data.append("FileData", at.fileData);

      //-----------------

      const fetchOptions = {
        method: "post",
        mode: "cors",
        headers: {
          Authorization: "Bearer " + window.$gz.store.state.apiToken
        },
        body: data
      };

      let r = await fetch(that.APIUrl("attachment"), fetchOptions);
      that.statusEx(r);
      r = await that.extractBodyEx(r);
      return r;
    } catch (error) {
      handleError("POSTATTACHMENT", error, "uploadAttachmentRoute");
    }
  },
  //////////////////////////////////////////////
  //  POST (UPLOAD) FILE TO ARBITRARY ROUTE
  // for various things that require an upload
  // e.g. translation import etc
  //
  //
  async upload(route, at) {
    const that = this;
    try {
      var files = at.files;
      var data = new FormData();
      for (var i = 0; i < files.length; i++) {
        data.append(files[i].name, files[i]);
      }
      if (at.ayaType) {
        data.append("AType", at.ayaType);
      }
      if (at.ayaId) {
        data.append("ObjectId", at.ayaId);
      }
      if (at.notes) {
        data.append("Notes", at.notes);
      }
      data.append("FileData", at.fileData);

      //-----------------

      const fetchOptions = {
        method: "post",
        mode: "cors",
        headers: {
          Authorization: "Bearer " + window.$gz.store.state.apiToken
        },
        body: data
      };

      let r = await fetch(that.APIUrl(route), fetchOptions);
      that.statusEx(r);
      r = await that.extractBodyEx(r);
      return r;
    } catch (error) {
      handleError("POSTATTACHMENT", error, route);
    }
  },

  ///////////////////////////////////
  //  POST LOGO
  //
  //
  async uploadLogo(fileData, size) {
    const that = this;
    try {
      const data = new FormData();
      data.append(fileData.name, fileData);

      //-----------------

      const fetchOptions = {
        method: "post",
        mode: "cors",
        headers: {
          Authorization: "Bearer " + window.$gz.store.state.apiToken
        },
        body: data
      };

      let r = await fetch(that.APIUrl("logo/" + size), fetchOptions);
      that.statusEx(r);
      r = await that.extractBodyEx(r);
      return r;
    } catch (error) {
      handleError("uploadLogo", error, "postLogoRoute");
    }
  },
  ///////////////////////////////////
  //  REPORT CLIENT META DATA
  //
  //
  reportClientMetaData() {
    const nowUtc = window.$gz.locale.nowUTC8601String();
    return {
      UserName: window.$gz.store.state.userName,
      UserId: window.$gz.store.state.userId,
      Authorization: "Bearer " + window.$gz.store.state.apiToken, //api token for using api methods as current user viewing report
      DownloadToken: window.$gz.store.state.downloadToken,
      TimeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
      LanguageName: window.$gz.locale.getResolvedLanguage(),
      Hour12: window.$gz.locale.getHour12(),
      CurrencyName: window.$gz.locale.getCurrencyName(),
      DefaultLocale: window.$gz.locale.getResolvedLanguage().split("-", 1)[0], //kind of suspect, maybe it can be removed
      PDFDate: window.$gz.locale.utcDateToShortDateLocalized(nowUtc),
      PDFTime: window.$gz.locale.utcDateToShortTimeLocalized(nowUtc)
    };
  },
  ///////////////////////////////////
  //  FETCH BIZ OBJECT NAME
  //
  //
  async fetchBizObjectName(ayaType, objectId) {
    const res = await this.get(`name/${ayaType}/${objectId}`);
    //We never expect there to be no data here
    if (!Object.prototype.hasOwnProperty.call(res, "data")) {
      return Promise.reject(res);
    } else {
      return res.data;
    }
  }
  //---------------

  //new functions above here
};
