const check = require('check-types');
const { AuthlessLowLevelClient } = require('./authlessLowLevelClient.js');
const { AuthlessRouter } = require('@authless/router');
const { Cache } = require('./cache');
const debug = require('../debug').extend('high');
const VError = require('verror');

const convertToRouterURL = url => {
  const regex = /http:\/|https:\//gum;
  return url.replace(regex, '');
}

class AuthlessClient {
  /*
   * lowLevelClient: lowLevelClient instance
   * retries: how many times it should be retried
   * router: AuthlessRouter
   * cache: {
   *  existsFn: a function that checks if it exists (takes url as input)
   *  retrieveFn: a function that retrieves from the cache (takes url as input)
   *  removeFn: a function that removes it from the cache (takes url as input)
   * }
   */
  constructor (params) {
    try {
      check.assert.instance(params.lowLevelClient, AuthlessLowLevelClient);
      check.assert.number(params.retries);
      check.assert.instance(params.router, AuthlessRouter);
      check.assert(check.any([
        check.undefined(params.cache),
        check.instance(params.cache, Cache)
      ]), 'expected params.cache to either be undefined or be a Cache instance');
      Object.assign(this, params);
    } catch (e) {
      throw new VError(e, 'failed to initialize AuthlessClient');
    }
  }

  async $extract (data) {
    const route = this.router.find('GET', convertToRouterURL(data.main.url));
    if (route) {
      const service = route.handler();
      if (service.extract) {
        return service.extract(data);
      }
    }
    return data;
  }

  async url (params, retryCounter = 0) {
    debug.extend('url')(params.u);
    /* eslint-disable-next-line no-ternary */
    let data = this.cache
      ? await this.cache.check(params.u)
      : false
    if (data) return data
    // TODO: refactor together with low-level into retry functions
    try {
      data = await this.lowLevelClient.url(params);
      let extracted = data;
      try {
        extracted = await this.$extract(data);
      } catch (e) {
        if (this.lowLevelClient.cache) {
          await this.lowLevelClient.cache.remove(params.u);
        }
        if (retryCounter < this.retries) {
          debug.extend('url').extend('error')(`retry/invalid lowLevel data: ${e.message}`);
          return this.url(params, retryCounter + 1);
        }
        debug.extend('url').extend('error')(`retried ${this.retries} times; its not working`);
        throw new VError(e, `failed to execute url; retried ${this.retries} times`);
      }
      if (this.cache) {
        await this.cache.write(params.u, extracted);
      }
      return extracted;
    } catch (e) {
      if (retryCounter < this.retries) {
        debug.extend('url').extend('error')(`retry/${retryCounter + 1}: ${e.message}`);
        return this.url(params, retryCounter + 1);
      }
      debug.extend('url').extend('error')(`retried ${this.retries} times; its not working`);
      throw e;
    }
  }
}

module.exports = { AuthlessClient };
