/* eslint-env es6 */
var test = require('blue-tape');
var redisSrv = require('./redis-server');
var session = require('express-session');
var RedisStore = require('../')(session);
var redis = require('redis');
var ioRedis = require('ioredis');
var sinon = require('sinon');
var P = require('bluebird');

var lifecycleTest = P.coroutine(function *(store, t) {
  P.promisifyAll(store);

  var ok = yield store.setAsync('123', { cookie: { maxAge: 2000 }, name: 'tj' });
  t.equal(ok, 'OK', '#set() ok');

  var data = yield store.getAsync('123');
  t.deepEqual({ cookie: { maxAge: 2000 }, name: 'tj' }, data, '#get() ok');

  ok = yield store.setAsync('123', { cookie: { maxAge: undefined }, name: 'tj' });
  t.equal(ok, 'OK', '#set() no maxAge ok');

  data = yield store.allAsync();
  t.deepEqual([{ id: '123', cookie: {}, name: 'tj' }], data, '#all() ok');

  data = yield store.idsAsync();
  t.deepEqual(['123'], data, '#ids() ok');

  data = yield store.lengthAsync();
  t.deepEqual(1, data, '#length() ok');

  ok = yield store.destroyAsync('123');
  t.equal(ok, 1, '#destroy() ok');

  store.client.end(false);
});

test('setup', redisSrv.connect);

test('defaults', function (t) {
  var store = new RedisStore();
  t.equal(store.prefix, 'sess:', 'defaults to sess:');
  t.notOk(store.ttl, 'ttl not set');
  t.notOk(store.disableTTL, 'disableTTL not set');
  t.ok(store.client, 'creates client');

  store.client.end(false);
  t.end();
});

test('basic', function (t) {
  t.throws(RedisStore, TypeError, 'constructor not callable as function');
  var store = new RedisStore({ port: redisSrv.port });
  return lifecycleTest(store, t);
});

test('existing client', function (t) {
  var client = redis.createClient(redisSrv.port, 'localhost');
  var store = new RedisStore({ client: client });
  return lifecycleTest(store, t);
});

test('io redis client', function (t) {
  var client = ioRedis.createClient(redisSrv.port, 'localhost');
  var store = new RedisStore({ client: client });
  return lifecycleTest(store, t).then(function () {
    t.test('#destroy()', function (p) {
      var spy = sinon.spy(ioRedis.prototype, 'sendCommand');
      var sidName = 'randomname';
      store.destroy(sidName);
      p.deepEqual(
        spy.firstCall.args[0].args,
        [store.prefix + sidName]
      );
      spy.restore();
      p.end();
    });
  });
});

test('options', function (t) {
  var store = new RedisStore({
    host: 'localhost',
    port: redisSrv.port,
    prefix: 'tobi',
    ttl: 1000,
    disableTTL: true,
    db: 1,
    scanCount: 32,
    unref: true,
    pass: 'secret'
  });

  t.equal(store.prefix, 'tobi', 'uses provided prefix');
  t.equal(store.ttl, 1000, 'ttl set');
  t.ok(store.disableTTL, 'disableTTL set');
  t.ok(store.client, 'creates client');
  t.equal(store.client.address, 'localhost:'+redisSrv.port, 'sets host and port');
  t.equal(store.scanCount, 32, 'sets scan count');

  var socketStore = new RedisStore({ socket: 'word' });
  t.equal(socketStore.client.address, 'word', 'sets socket address');
  socketStore.client.end(false);

  var urlStore = new RedisStore({ url: 'redis://127.0.0.1:8888' });
  t.equal(urlStore.client.address, '127.0.0.1:8888', 'sets url address');
  urlStore.client.end(false);

  var hostNoPort = new RedisStore({ host: 'host' });
  t.equal(hostNoPort.client.address, 'host:6379', 'sets default port');
  hostNoPort.client.end(false);

  return lifecycleTest(store, t);
});

test('ttl options', P.coroutine(function *(t) {
  var store = new RedisStore({ port: redisSrv.port });

  var sid = '123';
  var data, ok;
  sinon.stub(store.client, 'set').callsArgWith(1, null, 'OK');
  P.promisifyAll(store);

  // Basic (one day)
  data = { cookie: {}, name: 'tj' };
  ok = yield store.setAsync(sid, data);
  t.equal(ok, 'OK', '#set() ok');
  assertSetCalledWith(t, store, sid, data, ['EX', 86400]);

  // maxAge in cookie
  data = { cookie: { maxAge: 2000 }, name: 'tj' };
  ok = yield store.setAsync(sid, data);
  t.equal(ok, 'OK', '#set() ok');
  assertSetCalledWith(t, store, sid, data, ['EX', 2]);

  // Floors maxage
  data = { cookie: { maxAge: 2500 }, name: 'tj' };
  ok = yield store.setAsync(sid, data);
  t.equal(ok, 'OK', '#set() ok');
  assertSetCalledWith(t, store, sid, data, ['EX', 2]);

  // store.disableTTL
  store.disableTTL = true;
  data = { cookie: {}, name: 'tj' };
  ok = yield store.setAsync(sid, data);
  t.equal(ok, 'OK', '#set() ok');
  assertSetCalledWith(t, store, sid, data);
  store.disableTTL = false;

  // store.ttl: number
  store.ttl = 50;
  data = { cookie: {}, name: 'tj' };
  ok = yield store.setAsync(sid, data);
  t.equal(ok, 'OK', '#set() ok');
  assertSetCalledWith(t, store, sid, data, ['EX', 50]);
  store.ttl = null;

  // store.ttl: function
  store.ttl = sinon.stub().returns(200);
  data = { cookie: {}, name: 'tj' };
  ok = yield store.setAsync(sid, data);
  t.equal(ok, 'OK', '#set() ok');
  assertSetCalledWith(t, store, sid, data, ['EX', 200]);
  t.ok(store.ttl.called, 'TTL fn was called');
  t.deepEqual(store.ttl.firstCall.args, [store, data, sid]);
  store.ttl = null;

  // store.ttl: string (invalid)
  store.ttl = {};
  data = { cookie: {}, name: 'tj' };
  try {
    ok = yield store.setAsync(sid, data);
    t.ok(false, '#set() should throw with bad TTL');
  } catch (e) {
    t.ok(/must be a number or function/i.test(e.message), 'bad TTL type throws error');
  }
  store.ttl = null;

  store.client.end(false);
}));

function assertSetCalledWith(t, store, sid, data, addl) {
  var args = [store.prefix + sid, store.serializer.stringify(data)];
  if (Array.isArray(addl)) args = args.concat(addl);
  t.deepEqual(store.client.set.lastCall.args[0], args, '#.set() called with expected params');
}

test('interups', function (t) {
  var store = P.promisifyAll(new RedisStore({ port: redisSrv.port, connect_timeout: 500 }));
  return store.setAsync('123', { cookie: { maxAge: 2000 }, name: 'tj' })
    .catch(function (er) {
      t.ok(/broken/.test(er.message), 'failed connection');
      store.client.end(false);
    });
});

test('serializer', function (t) {
  var serializer = {
    stringify: function() { return 'XXX'+JSON.stringify.apply(JSON, arguments); },
    parse: function(x) {
      t.ok(x.match(/^XXX/));
      return JSON.parse(x.substring(3));
    }
  };
  t.equal(serializer.stringify('UnitTest'), 'XXX"UnitTest"');
  t.equal(serializer.parse(serializer.stringify('UnitTest')), 'UnitTest');

  var store = new RedisStore({ port: redisSrv.port, serializer: serializer });
  return lifecycleTest(store, t);
});

test('teardown', redisSrv.disconnect);
