var http = require('http')
var https = require('https')
import { readFileSync } from 'fs';

var JsonRPC = function (opts) {
  // @ts-ignore
  this.opts = opts || {}
  // @ts-ignore
  this.http = this.opts.ssl ? https : http
}

JsonRPC.prototype.call = function (method, params) {
  return new Promise((resolve, reject) => {
    var time = Date.now()
    var requestJSON

    if (Array.isArray(method)) {
      // multiple rpc batch call
      requestJSON = []
      method.forEach(function (batchCall, i) {
        requestJSON.push({
          id: time + '-' + i,
          method: batchCall.method,
          params: batchCall.params
        })
      })
    } else {
      // single rpc call
      requestJSON = {
        id: time,
        method: method,
        params: params
      }
    }

    // First we encode the request into JSON
    requestJSON = JSON.stringify(requestJSON)

    // prepare request options
    var requestOptions = {
      host: this.opts.host || 'localhost',
      port: this.opts.port || 8332,
      method: 'POST',
      path: '/',
      headers: {
        'Host': this.opts.host || 'localhost',
        'Content-Length': requestJSON.length
      },
      agent: false,
      rejectUnauthorized: this.opts.ssl && this.opts.sslStrict !== false
    }

    if (this.opts.ssl && this.opts.sslCa) {
    // @ts-ignore 
      requestOptions.ca = this.opts.sslCa
    }

    // use HTTP auth if user and password set
    if (this.opts.cookie) {
      if (!this.cachedCookie) {
        this.cachedCookie = readFileSync(this.opts.cookie).toString();
      }
      // @ts-ignore
      requestOptions.auth = this.cachedCookie;
    } else if (this.opts.user && this.opts.pass) {
      // @ts-ignore
      requestOptions.auth = this.opts.user + ':' + this.opts.pass
    }

    // Now we'll make a request to the server
    var cbCalled = false
    var request = this.http.request(requestOptions)

    // start request timeout timer
    var reqTimeout = setTimeout(function () {
      if (cbCalled) return
      cbCalled = true
      request.abort()
      var err = new Error('ETIMEDOUT')
      // @ts-ignore
      err.code = 'ETIMEDOUT'
      reject(err)
    }, this.opts.timeout || 30000)

    // set additional timeout on socket in case of remote freeze after sending headers
    request.setTimeout(this.opts.timeout || 30000, function () {
      if (cbCalled) return
      cbCalled = true
      request.abort()
      var err = new Error('ESOCKETTIMEDOUT')
      // @ts-ignore
      err.code = 'ESOCKETTIMEDOUT'
      reject(err)
    })

    request.on('error', function (err) {
      if (cbCalled) return
      cbCalled = true
      clearTimeout(reqTimeout)
      reject(err)
    })

    request.on('response', (response) => {
      clearTimeout(reqTimeout)

      // We need to buffer the response chunks in a nonblocking way.
      var buffer = ''
      response.on('data', function (chunk) {
        buffer = buffer + chunk
      })
      // When all the responses are finished, we decode the JSON and
      // depending on whether it's got a result or an error, we call
      // emitSuccess or emitError on the promise.
      response.on('end', () => {
        var err

        if (cbCalled) return
        cbCalled = true

        try {
          var decoded = JSON.parse(buffer)
        } catch (e) {
          // if we authenticated using a cookie and it failed, read the cookie file again
          if (
            response.statusCode === 401 /* Unauthorized */ &&
            this.opts.cookie
          ) {
            this.cachedCookie = undefined;
          }

          if (response.statusCode !== 200) {
            err = new Error('Invalid params, response status code: ' + response.statusCode)
            err.code = -32602
            reject(err)
          } else {
            err = new Error('Problem parsing JSON response from server')
            err.code = -32603
            reject(err)
          }
          return
        }

        if (!Array.isArray(decoded)) {
          decoded = [decoded]
        }

        // iterate over each response, normally there will be just one
        // unless a batch rpc call response is being processed
        decoded.forEach(function (decodedResponse, i) {
          if (decodedResponse.hasOwnProperty('error') && decodedResponse.error != null) {
            if (reject) {
              err = new Error(decodedResponse.error.message || '')
              if (decodedResponse.error.code) {
                err.code = decodedResponse.error.code
              }
              reject(err)
            }
          } else if (decodedResponse.hasOwnProperty('result')) {
            // @ts-ignore
            resolve(decodedResponse.result, response.headers)
          } else {
            if (reject) {
              err = new Error(decodedResponse.error.message || '')
              if (decodedResponse.error.code) {
                err.code = decodedResponse.error.code
              }
              reject(err)
            }
          }
        })
      })
    })
    request.end(requestJSON);
  });
}

module.exports.JsonRPC = JsonRPC
