const os = require('os');
const https = require("https");
const { fetch, FormData, Agent } = require("undici");
const JSONbig = require("json-bigint")({ storeAsString: false });
const {{spec.title | caseUcfirst}}Exception = require("./exception.js");
const { globalConfig } = require("./config.js");
const chalk = require("chalk");

class Client {
  CHUNK_SIZE = 5*1024*1024; // 5MB

  constructor() {
    this.endpoint = '{{spec.endpoint}}';
    this.headers = {
      'content-type': '',
      'x-sdk-name': '{{ sdk.name }}',
      'x-sdk-platform': '{{ sdk.platform }}',
      'x-sdk-language': '{{ language.name | caseLower }}',
      'x-sdk-version': '{{ sdk.version }}',
      'user-agent' : `{{spec.title | caseUcfirst}}CLI/{{ sdk.version }} (${os.type()} ${os.version()}; ${os.arch()})`,
{% for key,header in spec.global.defaultHeaders %}
      '{{key}}' : '{{header}}',
{% endfor %}
    };
  }

  /**
   * Set Cookie
   *
   * Your cookie
   *
   * @param {string} cookie
   *
   * @return self
   */
  setCookie(cookie) {
    this.addHeader("cookie", cookie);

    return this;
  }

{% for header in spec.global.headers %}
    /**
     * Set {{header.key | caseUcfirst}}
     *
{% if header.description %}
     * {{header.description}}
     *
{% endif %}
     * @param {string} {{header.key | caseLower}}
     *
     * @return self
     */
    set{{header.key | caseUcfirst}}({{header.key | caseLower}}) {
        this.addHeader('{{header.name}}', {{header.key | caseLower}});

        return this;
    }

{% endfor %}
  /**
   * Set self signed.
   *
   * @param {bool} status
   *
   * @return this
   */
  setSelfSigned(status) {
    this.selfSigned = status;

    return this;
  }

  /**
   * Set endpoint.
   *
   * @param {string} endpoint
   *
   * @return this
   */
  setEndpoint(endpoint) {
    this.endpoint = endpoint;

    return this;
  }

  /**
   * @param {string} key
   * @param {string} value
   */
  addHeader(key, value) {
    this.headers[key.toLowerCase()] = value;

    return this;
  }

  async call(method, path = "", headers = {}, params = {}, responseType = "json") {
    headers = {...this.headers, ...headers};
    const url = new URL(this.endpoint + path);

    let body = undefined;

    if (method.toUpperCase() === "GET") {
      url.search = new URLSearchParams(Client.flatten(params)).toString();
    } else if (headers["content-type"]?.toLowerCase().startsWith("multipart/form-data")) {
      delete headers["content-type"];
      const formData = new FormData();

      const flatParams = Client.flatten(params);

      for (const [key, value] of Object.entries(flatParams)) {
          if (value && value.type && value.type === "file") {
              formData.append(key, value.file, value.filename);
          } else {
              formData.append(key, value);
          }
      }

      body = formData;
    } else {
      body = JSONbig.stringify(params);
    }

    let response = undefined;
    try {
      response = await fetch(url.toString(), {
        method: method.toUpperCase(),
        headers,
        body,
        dispatcher: new Agent({
            connect: {
                rejectUnauthorized: !this.selfSigned,
            },
        }),
      });

      const warnings = response.headers.get('x-{{ spec.title | lower }}-warning');
      if (warnings) {
          warnings.split(';').forEach((warning) => console.warn(warning));
      }
    } catch (error) {
      throw new {{spec.title | caseUcfirst}}Exception(error.message);
    }

    if (response.status >= 400) {
      const text = await response.text();
      let json = undefined;
      try {
        json = JSON.parse(text);
      } catch (error) {
        throw new {{spec.title | caseUcfirst}}Exception(text, response.status, "", text);
      }

      if (path !== '/account' && json.code === 401 && json.type === 'user_more_factors_required') {
        console.log(`${chalk.cyan.bold("ℹ Info")} ${chalk.cyan("Unusable account found, removing...")}`);

        const current = globalConfig.getCurrentSession();
        globalConfig.setCurrentSession('');
        globalConfig.removeSession(current);
      }
      throw new {{spec.title | caseUcfirst}}Exception(json.message, json.code, json.type, json);
    }

    if (responseType === "arraybuffer") {
      const data = await response.arrayBuffer();
      return data;
    }

    let cookies = response.headers.getSetCookie();
    if (cookies && cookies.length > 0) {
      globalConfig.setCookie(cookies.join(";"));
    }

    const text = await response.text();
    let json = undefined;
    try {
      json = JSONbig.parse(text);
    } catch (error) {
      return text;
    }
    return json;
  }

  static flatten(data, prefix = '') {
    let output = {};

    for (const key in data) {
      let value = data[key];
      let finalKey = prefix ? prefix + '[' + key +']' : key;

      if (Array.isArray(value)) {
        output = Object.assign(output, Client.flatten(value, finalKey)); // @todo: handle name collision here if needed
      } else {
        output[finalKey] = value;
      }
    }

    return output;
  }
}

module.exports = Client;
