import should from 'should/as-function';
import ModelFileModel from '../models/model_file';
import TestHelper from "../test/helper";
import ModelModel from '../models/model';
import _ from 'lodash';


describe('/model', () => {
  let server;
  before(done => TestHelper.before(done));
  beforeEach(done => server = TestHelper.beforeEach(server, done));
  afterEach(done => TestHelper.afterEach(server, done));
  after(done => TestHelper.after(done));

  describe('GET /model/groups', () => {
    it('returns all groups', done => {
      TestHelper.request(server, done, {
        method: 'get',
        url: '/model/groups',
        auth: {basic: 'janedoe'},
        httpStatus: 200,
      }).end((err, res) => {
        if (err) return done (err);
        const json = require('../test/db.json');
        should(res.body).have.lengthOf(json.collections.models.length);
        should(res.body).matchEach(group => {
          should(group).have.only.keys('group', 'models');
          should(group).have.property('group').be.type('string');
          should(group.models).matchEach(model => {
            should(model).have.only.keys('name', 'url', 'label');
            should(model).have.property('name').be.type('string');
            should(model).have.property('url').be.type('string');
            should(model).have.property('label').be.type('string');
          });
        });
        done();
      });
    });
    it('rejects an API key', done => {
      TestHelper.request(server, done, {
        method: 'get',
        url: '/model/groups',
        auth: {key: 'janedoe'},
        httpStatus: 401,
      });
    });
    it('rejects an unauthorized request', done => {
      TestHelper.request(server, done, {
        method: 'get',
        url: '/model/groups',
        httpStatus: 401,
      });
    });
  });

  describe('POST /model/{group}', () => {
    it('adds a new model', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        auth: {basic: 'admin'},
        httpStatus: 200,
        req: {name: 'Model C', url: 'http://model-c.com', label: 'ml/g'}
      }).end((err, res) => {
        if (err) return done(err);
        should(res.body).be.eql({status: 'OK'});
        ModelModel.findOne({group: 'VN'}).lean().exec((err, res) => {
          if (err) return done(err);
          const model = res.models.find(e => e.name === 'Model C');
          should(model).have.property('url', 'http://model-c.com');
          should(model).have.property('label', 'ml/g');
          done();
        });
      });
    });
    it('adds a new group', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/classification',
        auth: {basic: 'admin'},
        httpStatus: 200,
        req: {name: 'Model 0.1', url: 'http://model-0-1.com', label: 'group'}
      }).end((err, res) => {
        if (err) return done(err);
        should(res.body).be.eql({status: 'OK'});
        ModelModel.findOne({group: 'classification'}).lean().exec((err, res) => {
          if (err) return done(err);
          should(_.omit(res, ['_id', '__v'])).be.eql({group: 'classification', models: [{name: 'Model 0.1', url: 'http://model-0-1.com', label: 'group'}]});
          done();
        });
      });
    });
    it('replaces a model', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        auth: {basic: 'admin'},
        httpStatus: 200,
        req: {name: 'Model A', url: 'http://model-a-new.com', label: 'ml/cm3'}
      }).end((err, res) => {
        if (err) return done(err);
        should(res.body).be.eql({status: 'OK'});
        ModelModel.findOne({group: 'VN'}).lean().exec((err, res) => {
          if (err) return done(err);
          const model = res.models.find(e => e.name === 'Model A');
          should(model).have.property('url', 'http://model-a-new.com');
          should(model).have.property('label', 'ml/cm3');
          done();
        });
      });
    });
    it('accepts an empty label', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        auth: {basic: 'admin'},
        httpStatus: 200,
        req: {name: 'Model C', url: 'http://model-c.com', label: ''}
      }).end((err, res) => {
        if (err) return done(err);
        should(res.body).be.eql({status: 'OK'});
        ModelModel.findOne({group: 'VN'}).lean().exec((err, res) => {
          if (err) return done(err);
          const model = res.models.find(e => e.name === 'Model C');
          should(model).have.property('url', 'http://model-c.com');
          should(model).have.property('label', '');
          done();
        });
      });
    });
    it('rejects an empty name', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        auth: {basic: 'admin'},
        httpStatus: 400,
        req: {name: '', url: 'http://model-c.com', label: 'ml/g'},
        res:{status: 'Invalid body format', details: '"name" is not allowed to be empty'}
      });
    });
    it('rejects a missing name', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        auth: {basic: 'admin'},
        httpStatus: 400,
        req: {url: 'http://model-c.com', label: 'ml/g'},
        res:{status: 'Invalid body format', details: '"name" is required'}
      });
    });
    it('rejects an invalid URL', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        auth: {basic: 'admin'},
        httpStatus: 400,
        req: {name: 'Model C', url: 'model-c', label: 'ml/g'},
        res:{status: 'Invalid body format', details: '"url" must be a valid uri'}
      });
    });
    it('rejects a missing URL', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        auth: {basic: 'admin'},
        httpStatus: 400,
        req: {name: 'Model C', label: 'ml/g'},
        res:{status: 'Invalid body format', details: '"url" is required'}
      });
    });
    it('rejects a write user', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        auth: {basic: 'janedoe'},
        httpStatus: 403,
        req: {name: 'Model C', url: 'http://model-c.com', label: 'ml/g'}
      });
    });
    it('rejects an API key', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        auth: {key: 'admin'},
        httpStatus: 401,
        req: {name: 'Model C', url: 'http://model-c.com', label: 'ml/g'}
      });
    });
    it('rejects an unauthorized request', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/VN',
        httpStatus: 401,
        req: {name: 'Model C', url: 'http://model-c.com', label: 'ml/g'}
      });
    });
  });

  describe('DELETE /model/{group}/{name}', () => {
    it('deletes the model', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/VN/Model%20A',
        auth: {basic: 'admin'},
        httpStatus: 200
      }).end((err, res) => {
        if (err) return done(err);
        should(res.body).be.eql({status: 'OK'});
        ModelModel.findOne({group: 'VN'}).lean().exec((err, res) => {
          if (err) return done(err);
          should(_.omit(res, ['_id'])).be.eql({group: 'VN', models: [{name: 'Model B', url: 'http://model-b.com', label: 'ml/g'}]});
          done();
        });
      });
    });
    it('deletes the group, if empty afterwards', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/Moisture/Model%201',
        auth: {basic: 'admin'},
        httpStatus: 200
      }).end((err, res) => {
        if (err) return done(err);
        should(res.body).be.eql({status: 'OK'});
        ModelModel.find({group: 'Moisture'}).lean().exec((err, res) => {
          if (err) return done(err);
          should(res).have.lengthOf(0);
          done();
        });
      });
    });
    it('returns 404 for an unknown group', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/xxx/Model%201',
        auth: {basic: 'admin'},
        httpStatus: 404
      });
    });
    it('returns 404 for an unknown model', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/VN/xxx',
        auth: {basic: 'admin'},
        httpStatus: 404
      });
    });
    it('rejects an API key', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/VN/Model%20A',
        auth: {key: 'admin'},
        httpStatus: 401
      });
    });
    it('rejects a write user', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/VN/Model%20A',
        auth: {basic: 'janedoe'},
        httpStatus: 403
      });
    });
    it('rejects an unauthorized request', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/VN/Model%20A',
        httpStatus: 401
      });
    });
  });

  describe('GET /model/file/{name}', (() => {
    it('returns the binary data', done => {
      TestHelper.request(server, done, {
        method: 'get',
        url: '/model/file/modela',
        auth: {basic: 'admin'},
        httpStatus: 200,
        contentType: 'application/octet-stream; charset=utf-8',
      }).end((err, res) => {
        if (err) return done (err);
        should(res.body.toString()).be.eql('binary data');
        done();
      });
    });
    it('returns the binary data for an API key', done => {
      TestHelper.request(server, done, {
        method: 'get',
        url: '/model/file/modela',
        auth: {key: 'admin'},
        httpStatus: 200,
        contentType: 'application/octet-stream; charset=utf-8',
      }).end((err, res) => {
        if (err) return done (err);
        should(res.body.toString()).be.eql('binary data');
        done();
      });
    });
    it('returns 404 for an unknown name', done => {
      TestHelper.request(server, done, {
        method: 'get',
        url: '/model/file/modelx',
        auth: {basic: 'admin'},
        httpStatus: 404
      })
    });
    it('rejects requests from a write user', done => {
      TestHelper.request(server, done, {
        method: 'get',
        url: '/model/file/modela',
        auth: {basic: 'janedoe'},
        httpStatus: 403
      })
    });
    it('rejects unauthorized requests', done => {
      TestHelper.request(server, done, {
        method: 'get',
        url: '/model/file/modela',
        httpStatus: 401
      })
    });
  }));

  describe('POST /model/file/{name}', () => {
    it('stores the data', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/file/modelb',
        auth: {basic: 'admin'},
        httpStatus: 200,
        reqContentType: 'application/octet-stream',
        req: 'another binary data'
      }).end((err, res) => {
        if (err) return done (err);
        should(res.body).be.eql({status: 'OK'});
        ModelFileModel.find({name: 'modelb'}).lean().exec((err, data) => {
          if (err) return done (err);
          should(data).have.lengthOf(1);
          should(data[0]).have.only.keys('_id', 'name', 'data', '__v');
          should(data[0]).have.property('name', 'modelb');
          should(data[0].data.buffer.toString()).be.eql('another binary data');
          done();
        });
      });
    });
    it('stores the data with an API key', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/file/modelb',
        auth: {key: 'admin'},
        httpStatus: 200,
        reqContentType: 'application/octet-stream',
        req: 'another binary data'
      }).end((err, res) => {
        if (err) return done (err);
        should(res.body).be.eql({status: 'OK'});
        ModelFileModel.find({name: 'modelb'}).lean().exec((err, data) => {
          if (err) return done (err);
          should(data).have.lengthOf(1);
          should(data[0]).have.only.keys('_id', 'name', 'data', '__v');
          should(data[0]).have.property('name', 'modelb');
          should(data[0].data.buffer.toString()).be.eql('another binary data');
          done();
        });
      });
    });
    it('overwrites existing data', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/file/modela',
        auth: {basic: 'admin'},
        httpStatus: 200,
        reqContentType: 'application/octet-stream',
        req: 'another binary data'
      }).end((err, res) => {
        if (err) return done (err);
        should(res.body).be.eql({status: 'OK'});
        ModelFileModel.find({name: 'modela'}).lean().exec((err, data) => {
          if (err) return done (err);
          should(data).have.lengthOf(1);
          should(data[0]).have.only.keys('_id', 'name', 'data', '__v');
          should(data[0]).have.property('name', 'modela');
          should(data[0].data.buffer.toString()).be.eql('another binary data');
          done();
        });
      });
    });
    it('rejects requests from a write user', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/file/modelb',
        auth: {basic: 'janedoe'},
        httpStatus: 403,
        req: 'another binary data'
      });
    });
    it('rejects unauthorized requests', done => {
      TestHelper.request(server, done, {
        method: 'post',
        url: '/model/file/modelb',
        httpStatus: 401,
        req: 'another binary data'
      });
    });
  });

  describe('DELETE /model/file/{name}', () => {
    it('deletes the data', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/file/modela',
        auth: {basic: 'admin'},
        httpStatus: 200
      }).end((err, res) => {
        if (err) return done(err);
        should(res.body).be.eql({status: 'OK'});
        ModelFileModel.find({name: 'modela'}).lean().exec((err, data) => {
          if (err) return done(err);
          should(data).have.lengthOf(0);
          done();
        });
      });
    });
    it('returns 404 for an unknown name', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/file/modelx',
        auth: {basic: 'admin'},
        httpStatus: 404
      });
    });
    it('rejects an API key', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/file/modela',
        auth: {key: 'admin'},
        httpStatus: 401
      });
    });
    it('rejects a write user', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/file/modela',
        auth: {basic: 'janedoe'},
        httpStatus: 403
      });
    });
    it('rejects an unauthorized request', done => {
      TestHelper.request(server, done, {
        method: 'delete',
        url: '/model/file/modela',
        httpStatus: 401
      });
    });
  });
});