import http from 'http'
import fs from 'fs/promises'
import path from 'path'
import { URL } from 'url'
import defaults from '../api/defaults.mjs'
import config from '../api/config.mjs'

export default function Client(port = config.get('server:port'), opts) {
  this.options = defaults(opts, {})
  this.prefix = `http://localhost:${port}`
}

Client.prototype.customRequest = function(method = 'GET', path, body, options) {
  if (path.slice(0, 4) !== 'http') {
    path = this.prefix + path
  }
  let urlObj = new URL(path)

  return new Promise((resolve, reject) => {
    const opts = defaults(defaults(options, {
      method: method,
      timeout: 500,
      protocol: urlObj.protocol,
      username: urlObj.username,
      password: urlObj.password,
      host: urlObj.hostname,
      port: Number(urlObj.port),
      path: urlObj.pathname + urlObj.search,
    }))

    const req = http.request(opts)
    if (body) {
      req.write(body)
    }

    req.on('error', reject)
    req.on('timeout', function() { reject(new Error(`Request ${method} ${path} timed out`)) })
    req.on('response', res => {
      res.setEncoding('utf8')
      let output = ''

      res.on('data', function (chunk) {
        output += chunk.toString()
      })

      res.on('end', function () {
        if (!output) return resolve(null)
        try {
          output = JSON.parse(output)
        } catch (e) {
          return reject(new Error(`${e.message} while decoding: ${output}`))
        }
        if (output.status) {
          let err = new Error(`Request failed [${output.status}]: ${output.message}`)
          err.body = output
          err.status = output.status
          return reject(err)
        }
        resolve(output)
      })
    })
    req.end()
  })
}

Client.prototype.get = function(url = '/') {
  return this.customRequest('GET', url, null)
}

Client.prototype.post = function(url = '/', body = {}) {
  let parsed = JSON.stringify(body)
  return this.customRequest('POST', url, parsed, {
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': parsed.length,
    },
  })
}

Client.prototype.del = function(url = '/', body = {}) {
  return this.customRequest('DELETE', url, JSON.stringify(body))
}

const random = (length = 8) => {
  // Declare all characters
  let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  // Pick characers randomly
  let str = '';
  for (let i = 0; i < length; i++) {
      str += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return str;
}

Client.prototype.upload = function(url, file, method = 'POST', body = {}) {
  return fs.readFile(file).then(data => {
    const crlf = '\r\n'
      const filename = path.basename(file)
      const boundary = `---------${random(32)}`

      const multipartBody = Buffer.concat([
        Buffer.from(
          `${crlf}--${boundary}${crlf}` +
          `Content-Disposition: form-data; name="file"; filename="${filename}"` + crlf + crlf
        ),
        data,
        Buffer.concat(Object.keys(body).map(function(key) {
          return Buffer.from(''
            + `${crlf}--${boundary}${crlf}`
            + `Content-Disposition: form-data; name="${key}"` + crlf + crlf
            + JSON.stringify(body[key])
          )
        })),
        Buffer.from(`${crlf}--${boundary}--`),
      ])

      return this.customRequest(method, url, multipartBody, {
        timeout: 5000,
        headers: {
          'Content-Type': 'multipart/form-data; boundary=' + boundary,
          'Content-Length': multipartBody.length,
        },
      })
  })
}
