const assert = require('assert');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const crypto = require('crypto');

const redisStub = {};
const exists = sinon.stub();
const hget = sinon.stub();
const hmset = sinon.stub();
const expire = sinon.spy();
const del = sinon.stub();

redisStub.createClient = function() {
  return {
    on: sinon.spy(),
    exists: exists,
    hget: hget,
    hmset: hmset,
    expire: expire,
    del: del
  };
};

const fsStub = {};
fsStub.statSync = sinon.stub();
fsStub.createReadStream = sinon.stub();
fsStub.createWriteStream = sinon.stub();
fsStub.unlinkSync = sinon.stub();

const logStub = {};
logStub.info = sinon.stub();
logStub.error = sinon.stub();

const s3Stub = {};
s3Stub.headObject = sinon.stub();
s3Stub.getObject = sinon.stub();
s3Stub.upload = sinon.stub();
s3Stub.deleteObject = sinon.stub();

const awsStub = {
  S3: function() {
    return s3Stub;
  }
};

const storage = proxyquire('../../server/storage', {
  redis: redisStub,
  fs: fsStub,
  './log.js': function() {
    return logStub;
  },
  'aws-sdk': awsStub,
  './config.js': {
    s3_bucket: 'test'
  }
});

describe('Testing Length using aws', function() {
  it('Filesize returns properly if id exists', function() {
    s3Stub.headObject.callsArgWith(1, null, { ContentLength: 1 });
    return storage
      .length('123')
      .then(reply => assert.equal(reply, 1))
      .catch(err => assert.fail());
  });

  it('Filesize fails if the id does not exist', function() {
    s3Stub.headObject.callsArgWith(1, new Error(), null);
    return storage
      .length('123')
      .then(_reply => assert.fail())
      .catch(err => assert(1));
  });
});

describe('Testing Get using aws', function() {
  it('Should not error out when the file exists', function() {
    const spy = sinon.spy();
    s3Stub.getObject.returns({
      createReadStream: spy
    });

    storage.get('123');
    assert(spy.calledOnce);
  });

  it('Should error when the file does not exist', function() {
    const err = function() {
      throw new Error();
    };
    const spy = sinon.spy(err);
    s3Stub.getObject.returns({
      createReadStream: spy
    });

    assert.equal(storage.get('123'), null);
    assert(spy.threw());
  });
});

describe('Testing Set using aws', function() {
  beforeEach(function() {
    expire.reset();
  });

  after(function() {
    crypto.randomBytes.restore();
  });

  it('Should pass when the file is successfully uploaded', function() {
    const buf = Buffer.alloc(10);
    sinon.stub(crypto, 'randomBytes').returns(buf);
    s3Stub.upload.returns({promise: () => Promise.resolve()});
    return storage
      .set('123', {on: sinon.stub()}, 'Filename.moz', {})
      .then(() => {
        assert(expire.calledOnce);
        assert(expire.calledWith('123', 86400));
      })
      .catch(err => assert.fail());
  });

  it('Should fail if there was an error during uploading', function() {
    s3Stub.upload.returns({promise: () => Promise.reject()});
    return storage
      .set('123', {on: sinon.stub()}, 'Filename.moz', 'url.com')
      .then(_reply => assert.fail())
      .catch(err => assert(1));
  });
});

describe('Testing Delete from aws', function() {
  it('Returns successfully if the id is deleted off aws', function() {
    hget.callsArgWith(2, null, 'delete_token');
    s3Stub.deleteObject.callsArgWith(1, null, {});
    return storage
      .delete('file_id', 'delete_token')
      .then(_reply => assert(1), err => assert.fail());
  });

  it('Delete fails if id exists locally but does not in aws', function() {
    hget.callsArgWith(2, null, 'delete_token');
    s3Stub.deleteObject.callsArgWith(1, new Error(), {});
    return storage
      .delete('file_id', 'delete_token')
      .then(_reply => assert.fail(), err => assert(1));
  });

  it('Delete fails if the delete token does not match', function() {
    hget.callsArgWith(2, null, {});
    return storage
      .delete('Filename.moz', 'delete_token')
      .then(_reply => assert.fail())
      .catch(err => assert(1));
  });
});

describe('Testing Forced Delete from aws', function() {
  it('Deletes properly if id exists', function() {
    s3Stub.deleteObject.callsArgWith(1, null, {});
    return storage
      .forceDelete('file_id', 'delete_token')
      .then(_reply => assert(1), err => assert.fail());
  });

  it('Deletes fails if id does not exist', function() {
    s3Stub.deleteObject.callsArgWith(1, new Error(), {});
    return storage
      .forceDelete('file_id')
      .then(_reply => assert.fail(), err => assert(1));
  });
});