const inflection = require('inflection')
const pathProxy = require('path-proxy')
const promisifyCall = require('promisify-call')

class Builder {
  constructor (baseObj, resources) {
    this.baseObj = baseObj
    this.resources = resources
  }

  build () {
    Object.keys(this.resources).forEach((key) => {
      // console.log('building ' + key);
      this.buildResource(this.resources[key])
    })
  }

  buildResource (resource) {
    resource.links.forEach(this.buildAction, this)
  }

  buildAction (action) {
    const actionName = action.title
    const properties = action.properties
    const requiredProps = action.required

    // HACKY special case for members bulk add and send MIME endpoints
    const path = action.href.replace(/\.json/gi, '').replace(/\.mime/gi, '')
    const constructor = pathProxy.pathProxy(this.baseObj, path)

    function impl (data, fn) {
      let requestPath = action.href
      const pathParams = action.href.match(/{[^}]+}/g) || []

      if (typeof data === 'function') {
        fn = data
        data = undefined
      }

      let err

      if (this.params.length !== pathParams.length) {
        err = new Error(`Invalid number of params in path (expected ${pathParams.length}, got ${this.params.length}).`)

        return fn(err)
      }

      this.params.forEach((param) => {
        requestPath = requestPath.replace(/{[^}]+}/, param)
      })

      // check required payload properties
      if (requiredProps && requiredProps.length > 0) {
        if (!data) {
          err = new Error('Missing parameters.')
        } else {
          for (let i = 0; i < requiredProps.length; i++) {
            const prop = requiredProps[i]

            if (typeof data[prop] === 'undefined') {
              err = new Error(`Missing parameter '${prop}'`)
              break
            }
          }
        }
      }

      if (err) {
        return fn(err)
      }

      // check payload property types
      for (const key in properties) {
        if (data && data[key]) {
          const type = properties[key].type

          let dataType = typeof data[key]

          if (Array.isArray(data[key])) {
            dataType = 'array'
          }

          if (Array.isArray(type)) {
            if (type.indexOf(dataType) === -1) {
              err = new Error(`Invalid parameter type. ${key} must be of type: ${type}.`)
              break
            }
          } else if (dataType !== type) {
            err = new Error(`Invalid parameter type. ${key} must be of type: ${type}.`)
            break
          }
        }
      }

      if (err) {
        return fn(err)
      }

      this.client = this.base

      return this.client.request(action.method, requestPath, data, fn)
    }

    function promisifed (data, fn) {
      return promisifyCall(this, impl, data, fn)
    }

    constructor.prototype[getName(actionName)] = promisifed
  }
}

function getName (name) {
  name = name.toLowerCase()
  name = inflection.dasherize(name).replace(/-/g, '_')
  name = inflection.camelize(name, true)

  return name
}

function build (baseObj, resources) {
  const b = new Builder(baseObj, resources)

  b.build()
}

exports.build = build