import supertest from 'supertest';
import should from 'should/as-function';
import _ from 'lodash';
import db from '../db';
import ChangelogModel from '../models/changelog';
import IdValidate from '../routes/validate/id';


export default class TestHelper {
  public static auth = {  // test user credentials
    admin: {pass: 'Abc123!#', key: '000000000000000000001003', id: '000000000000000000000003'},
    janedoe: {pass: 'Xyz890*)', key: '000000000000000000001002', id: '000000000000000000000002'},
    user: {pass: 'Xyz890*)', key: '000000000000000000001001', id: '000000000000000000000001'},
    johnnydoe: {pass: 'Xyz890*)', key: '000000000000000000001004', id: '000000000000000000000004'}
  }

  public static res = {  // default responses
    400: {status: 'Bad request'},
    401: {status: 'Unauthorized'},
    403: {status: 'Forbidden'},
    404: {status: 'Not found'},
    500: {status: 'Internal server error'}
  }

  static before (done) {
    process.env.port = '2999';
    process.env.NODE_ENV = 'test';
    db.connect('test', done);
  }

  static beforeEach (server, done) {
    // delete cached server code except models as these are needed in the testing files as well
    Object.keys(require.cache).filter(e => /API\\dist\\(?!(models|db|test))/.test(e)).forEach(key => {
      delete require.cache[key];  // prevent loading from cache
    });
    server = require('../index');
    db.drop(err => {  // reset database
      if (err) return done(err);
      db.loadJson(require('./db.json'), done);
    });
    return server
  }

  // options in form: {method, url, contentType, auth: {key/basic: 'name' or 'key'/{name, pass}}, httpStatus, req, res,
  // default (set to false if you want to dismiss default .end handling)}
  static request (server, done, options) {
    let st = supertest(server);
    if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('key')) {  // resolve API key
      options.url += '?key=' +
        (this.auth.hasOwnProperty(options.auth.key)? this.auth[options.auth.key].key : options.auth.key);
    }
    switch (options.method) {  // http method
      case 'get':
        st = st.get(options.url)
        break;
      case 'post':
        st = st.post(options.url)
        break;
      case 'put':
        st = st.put(options.url)
        break;
      case 'delete':
        st = st.delete(options.url)
        break;
    }
    if (options.hasOwnProperty('reqType')) {  // request body
      st = st.type(options.reqType);
    }
    if (options.hasOwnProperty('req')) {  // request body
      st = st.send(options.req);
    }
    if (options.hasOwnProperty('reqContentType')) {  // request body
      st = st.set('Content-Type', options.reqContentType);
    }
    if (options.hasOwnProperty('auth') && options.auth.hasOwnProperty('basic')) {  // resolve basic auth
      if (this.auth.hasOwnProperty(options.auth.basic)) {
        st = st.auth(options.auth.basic, this.auth[options.auth.basic].pass)
      }
      else {
        st = st.auth(options.auth.basic.name, options.auth.basic.pass)
      }
    }
    if (options.hasOwnProperty('contentType')) {
      st = st.expect('Content-type', options.contentType).expect(options.httpStatus);
    }
    else {
      st = st.expect('Content-type', /json/).expect(options.httpStatus);
    }
    if (options.hasOwnProperty('res')) {  // evaluate result
      return st.end((err, res) => {
        if (err) return done (err);
        should(res.body).be.eql(options.res);
        done();
      });
    }
    else if (this.res.hasOwnProperty(options.httpStatus) && options.default !== false) {  // evaluate default results
      return st.end((err, res) => {
        if (err) return done (err);
        should(res.body).be.eql(this.res[options.httpStatus]);
        done();
      });
    }
    // check changelog, takes log: {collection, skip, data/(dataAdd, dataIgn)}
    else if (options.hasOwnProperty('log')) {
      return st.end(err => {
        if (err) return done (err);
        ChangelogModel.findOne({}).sort({_id: -1}).skip(options.log.skip? options.log.skip : 0)
          .lean().exec((err, data) => {  // latest entry
          if (err) return done(err);
          should(data).have.only.keys('_id', 'action', 'collection_name', 'conditions', 'data', 'user_id', '__v');
          should(data).have.property('action', options.method.toUpperCase() + ' ' + options.url);
          should(data).have.property('collection_name', options.log.collection);
          if (options.log.hasOwnProperty('data')) {
            should(data).have.property('data', options.log.data);
          }
          else {
            const ignore = ['_id', '__v'];
            if (options.log.hasOwnProperty('dataIgn')) {
              ignore.push(...options.log.dataIgn);
            }
            let tmp = options.req ? options.req : {};
            if (options.log.hasOwnProperty('dataAdd')) {
              _.assign(tmp, options.log.dataAdd)
            }
            should(IdValidate.stringify(_.omit(data.data, ignore))).be.eql(_.omit(tmp, ignore));
          }
          if (data.user_id) {
            should(data.user_id.toString()).be.eql(this.auth[options.auth.basic].id);
          }
          done();
        });
      });
    }
    else {  // return object to do .end() manually
      return st;
    }
  }

  static afterEach (server, done) {
    server.close(done);
  }

  static after(done) {
    db.disconnect(done);
  }
}