import 'whatwg-fetch'
import superagent from 'superagent'
import {api_path, DEV} from 'common/constants'
import store from 'common/store'
import {array_contains} from 'common/utilities'
import auto_bind from 'common/auto_bind'

export function make_url(endpoint) {
  return `${api_path}/${endpoint}.json?`
}

const authentication_headers = () => {
  const {token} = store.getState().authentication
  if (token) {
    return {
      'Authorization': `Bearer ${token}`
    }
  }
  return {}
}

const requestWithMethod = function(method, endpoint, data = {}) {
  var options = {method}
  let params = ""
  if (method == 'GET' && data !== null) {
    for (var key in data) {
      params += encodeURIComponent(key) + "=" + encodeURIComponent(data[key]) + "&"
    }
    data = null
  }
  options.headers = authentication_headers()
  if (data !== null) {
    options.body = JSON.stringify(data)
    options.headers = {
      ...options.headers,
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    }
  }
  var request_url = `${make_url(endpoint)}${params}`
  return fetch(request_url, options)
  .catch((error) => {
    return new Promise((resolve, reject) => {
      DEV && console.log(method, request_url, data)
      DEV && console.log(error)
      return reject(error)
    })
  })
  .then((response) => {
    return new Promise((resolve, reject) => {
      if (response == null || response.error || (array_contains([400, 404, 422, 500], response.status))) {
        reject(response)
      } else if (response.status != 204) {
        response.json()
        .then((responseObject) => {
          if (responseObject == null || responseObject.error || (array_contains([400, 404, 422, 500], responseObject.status))) {
            reject(responseObject)
          } else {
            resolve(responseObject)
          }
        })
        .catch((error) => reject(error))
      } else {
        resolve(response)
      }
    })
  })
}

const request = {
  get: function(endpoint, params, callback) {
    return requestWithMethod('GET', endpoint, params, callback)
  },

  post: function(endpoint, data, callback) {
    return requestWithMethod('POST', endpoint, data, callback)
  },

  put: function(endpoint, data, callback) {
    return requestWithMethod('PUT', endpoint, data, callback)
  },

  delete: function(endpoint, data, callback) {
    return requestWithMethod('DELETE', endpoint, data, callback)
  },

  asynchronous: function(endpoint, data) {
    return new Promise((resolve, reject) => {
      const poll_result = (asynchronous_request_id) => requestWithMethod('GET', `asynchronous_requests/${asynchronous_request_id}`)
      .then((asynchronous_request) => {
        switch (asynchronous_request.status) {
          case "pending":
            setTimeout(() => poll_result(asynchronous_request_id), 1000)
            break
          case "completed":
            resolve(JSON.parse(asynchronous_request.result))
            break
          default:
            reject(asynchronous_request)
        }
      })

      requestWithMethod('POST', endpoint, data)
      .then((response) => setTimeout(() => poll_result(response.asynchronous_request_id), 1000))
    })
  },

  get_paginated: function(endpoint, data = {}, callback) {
    return new Promise((resolve, reject) => {
      const request_page = (page) => requestWithMethod('GET', endpoint, {...data, page})
      .then((page_results) => {
        if (page_results.length == 0) {
          resolve()
        } else {
          callback(page_results)
          request_page(page + 1)
        }
      })
      .catch(reject)

      request_page(1)
    })
  },

  upload(endpoint, file, data) {
    // request won't start if `then` is not called
    const request = superagent
      .put(make_url(endpoint))
      .set(authentication_headers())
      .accept('json')
      .attach('file', file)
    return Object.keys(data).reduce((request, key) => request.field(key, data[key]), request)
  },

  delayed_put(endpoint, data) {
    return new Promise((resolve, reject) => {
      let delayed_request = delayed_requests[endpoint]
      if (delayed_request && !delayed_request.completed) {
        resolve = delayed_request.cancel(resolve)
      }
      delayed_requests[endpoint] = new DelayedRequest(() => request.put(endpoint, data), resolve, reject)
    })
  }
}

const delayed_requests = {}

class DelayedRequest {
  constructor(request, resolve, reject) {
    this.request = request
    this.resolve = resolve
    this.reject = reject
    auto_bind(this)
    setTimeout(this.run, 2 * 1000)
  }

  run() {
    this.completed = true
    if (!this.canceled) {
      this.request()
      .then(this.resolve, this.reject)
    }
  }

  cancel(resolve) {
    this.canceled = true
    return (...args) => {
      resolve(...args)
      this.resolve(...args)
    }
  }
}

export default request
