import redis from 'redis';
import logger from '../../logger';
import { KeyPromiseQueue } from '../../KeyPromiseQueue';

const DEFAULT_REDIS_TTL = 30 * 1000; // 30 seconds in milliseconds
const FLUSH_DB_KEY = '__flush_db__';

function debug(...args: any) {
  const message = ['RedisCacheAdapter: ' + arguments[0]].concat(args.slice(1, args.length));
  logger.debug.apply(logger, message);
}

const isValidTTL = ttl => typeof ttl === 'number' && ttl > 0;

export class RedisCacheAdapter {
  constructor(redisCtx, ttl = DEFAULT_REDIS_TTL) {
    this.ttl = isValidTTL(ttl) ? ttl : DEFAULT_REDIS_TTL;
    this.client = redis.createClient(redisCtx);
    this.queue = new KeyPromiseQueue();
  }

  handleShutdown() {
    if (!this.client) {
      return Promise.resolve();
    }
    return new Promise(resolve => {
      this.client.quit(err => {
        if (err) {
          logger.error('RedisCacheAdapter error on shutdown', { error: err });
        }
        resolve();
      });
    });
  }

  get(key) {
    debug('get', { key });
    return this.queue.enqueue(
      key,
      () =>
        new Promise(resolve => {
          this.client.get(key, function (err, res) {
            debug('-> get', { key, res });
            if (!res) {
              return resolve(null);
            }
            resolve(JSON.parse(res));
          });
        })
    );
  }

  put(key, value, ttl = this.ttl) {
    value = JSON.stringify(value);
    debug('put', { key, value, ttl });

    if (ttl === 0) {
      // ttl of zero is a logical no-op, but redis cannot set expire time of zero
      return this.queue.enqueue(key, () => Promise.resolve());
    }

    if (ttl === Infinity) {
      return this.queue.enqueue(
        key,
        () =>
          new Promise(resolve => {
            this.client.set(key, value, function () {
              resolve();
            });
          })
      );
    }

    if (!isValidTTL(ttl)) {
      ttl = this.ttl;
    }

    return this.queue.enqueue(
      key,
      () =>
        new Promise(resolve => {
          this.client.psetex(key, ttl, value, function () {
            resolve();
          });
        })
    );
  }

  del(key) {
    debug('del', { key });
    return this.queue.enqueue(
      key,
      () =>
        new Promise(resolve => {
          this.client.del(key, function () {
            resolve();
          });
        })
    );
  }

  clear() {
    debug('clear');
    return this.queue.enqueue(
      FLUSH_DB_KEY,
      () =>
        new Promise(resolve => {
          this.client.flushdb(function () {
            resolve();
          });
        })
    );
  }

  // Used for testing
  async getAllKeys() {
    return new Promise((resolve, reject) => {
      this.client.keys('*', (err, keys) => {
        if (err) {
          reject(err);
        } else {
          resolve(keys);
        }
      });
    });
  }
}

export default RedisCacheAdapter;
