import fetch from 'dva/fetch';

function checkStatus(response) {
  if(!response) {
    throw new Error('无法访问服务器');
  }
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  if (response.status === 400) {
    return response;
  }
  if (response.status === 404) {
    throw new Error('请求未找到');
  }
  if (response.status === 405) {
    throw new Error('无法访问服务器');
  }
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */

class TimeoutError extends Error {
  constructor(message) {
    super(message);
    this.name = 'TimeoutError';
  }
}

/**
 * 提供参数校验和wrapper功能
 *
 * @param {*} url
 * @param {*} [options={ method: 'GET' }]
 * @returns {Promise} the request result
 */
export default function request(url, options, timeout) {
  let retryCount = 0;

  class Request {
    constructor(url, {
      retry,
      ...options
    }) {
      this.url = url;
      this.retry = retry || 0;
      this.timeout = timeout || 8 * 1000;
      this.options = options;
    }

    then(fn) {
      let done = false;
      setTimeout(() => {
        // 无论是请求重试还是最终超时错误，此次请求得到的结果作废
        if (retryCount < this.retry && !done) {
          done = true;
          retryCount++;
          this.then(fn);
        } else {
          let error = new TimeoutError(`请求超时`);
          this.catchError(error);
        }
      }, this.timeout);

      fetch(this.url, this.options)
        .then(checkStatus)
        .then(data => {
          // 未进入重试或者超时错误，返回结果
          if (!done) {
            fn(
              data
            );
            done = true;
          }
        })
        .catch(err => {
          this.catchError(err);
        });

      return this;
    }

    catch (fn) {
      this.catchError = fn;
    }
  }

  return new Promise((resolve, reject) =>
    new Request(url, options).then(res => resolve(res)).catch(err => reject(err))
  );
}
