// eslint-disable-next-line n/no-deprecated-api
const TRedisClient = process.binding('synode_redis').TSMRedisClient
const ubVersionNum = process.version.slice(1).split('.').map(v => parseInt(v, 10)).reduce((acum, v) => acum * 1000 + v)

/**
 * @module @unitybase/redis
 * @example
 const redis = require('@unitybase/redis')
 const redisConn = redis.getDefaultRedisConnection()
 // Set key `key1` to hold the string value `val1` with 3 second expire
 redisConn.commands('set', 'key1', 'val1', 'EX', 3)
 // get time-to-live (TTL0 of key1
 const TTL = redisConn.commands('TTL', 'key1')
 console.log('key1 will live', TTL, 'sec')
 // get value
 let val = redisConn.commands('get', 'key1')
 console.log('Value for key1 is', val)

 console.log('Waiting for 3 second...')
 sleep(3100)
 // now  value is expired
 val = redisConn.commands('get', 'key1')
 console.log('Expired value for key1 is', val) // val is null
 */

class RedisClient {
  /**
   * Creates a lazy connection to the redis server.
   * Actual TCP connection established on first command.
   *
   * Automatically reconnect and repeating a command in case of connection lost.
   * If reconnect fails in reconnectTimeout - throws.
   *
   * @param {object} connSettings
   * @param {string} [connSettings.host='127.0.0.1']
   * @param {string} [connSettings.port='6379']
   * @param {number} [connSettings.dbIndex=0] Active database index. See https://redis.io/commands/select/
   * @param {number} [connSettings.reconnectTimeout=30000]
   * @param {string} [connSettings.password=''] For UB server > 5.24.23
   * @param {string} [connSettings.username=''] For UB server > 5.24.23
   */
  constructor (connSettings) {
    this._connSettings = Object.assign({
      host: '127.0.0.1',
      port: '6379',
      dbIndex: 0,
      reconnectTimeout: 30000,
      password: '',
      username: ''
    }, connSettings)
    this._native = new TRedisClient()
    if (ubVersionNum <= 5024023) {
      this._native.initialize(this._connSettings.host, this._connSettings.port, this._connSettings.dbIndex)
    } else {
      this._native.initialize(this._connSettings.host, this._connSettings.port, this._connSettings.dbIndex,
        'ub', this._connSettings.password, this._connSettings.username)
    }
  }

  /**
   * Send a series of commands and return a redis response.
   * Depending on command response can be a string, integer, array of <string|array<string>>
   *
   * For blocking operations with timeout reached returns null.
   * Throw in case of protocol level error or connection lost and reconnect fails in connSettings.reconnectTimeout ms.
   *
   * @param {...string|number} args
   * @returns {null | string | number | Array<string | Array<string>>}
   */
  commands (...args) {
    let resp
    try {
      resp = this._native.commands(...args)
      return resp
    } catch (e) {
      console.warn(`Reconnecting to redis on ${this._connSettings.host}:${this._connSettings.port} with timeout ${this._connSettings.reconnectTimeout}...`)
      this._native.reconnect(this._connSettings.reconnectTimeout)
      console.log('Reconnected')
    }
    console.log('Repeat redis command after reconnect')
    resp = this._native.commands(...args) // repeat a command. Throws if fails again
  }

  /**
   * PRIVATE method to send a raw command to redis. Reconnect is not supported
   *
   * Raw command example: '*2\r\n$7\r\nhgetall\r\n$8\r\ntestHash\r\n`
   *
   * @private
   * @param {string} cmd
   * @returns {null | string | number | Array<string | Array<string>>}
   */
  rawCommand (cmd) {
    return this._native.rawCommand(cmd)
  }

  get ioError () {
    return this._native.ioError
  }

  get ioErrorText () {
    return this._native.ioErrorText
  }
}

let defaultClient

/**
 * Return per-thread instance of redis client connected to server using configuration from `ubConfig.application.redis`.
 * To be used inside server-side thread
 *
 * @example
 const redis = require('@unitybase/redis')
 const redisConn = redis.getDefaultRedisConnection()
 console.log(redisConn.commands('PING')) // PONG
 * @returns {RedisClient}
 */
function getDefaultRedisConnection () {
  if (!defaultClient) {
    // eslint-disable-next-line no-undef
    defaultClient = new RedisClient(App.serverConfig.redis)
  }
  return defaultClient
}

module.exports = {
  getDefaultRedisConnection,
  RedisClient
}