text: var should = require('should'),
      testUtils = require('../../utils'),
      _ = require('lodash'),
      // Stuff we are testing
      context = testUtils.context,
  
      TagAPI = require('../../../server/api/tags');
  
  // there are some random generated tags in test database
  // which can't be sorted easily using _.sortBy()
  // so we filter them out and leave only pre-built fixtures
  // usage: tags.filter(onlyFixtures)
  function onlyFixtures(slug) {
      return testUtils.DataGenerator.Content.tags.indexOf(slug) >= 0;
  }
  
  describe('Tags API', function () {
      // Keep the DB clean
      before(testUtils.teardown);
      after(testUtils.teardown);
  
      before(testUtils.setup('settings', 'users:roles', 'perms:tag', 'perms:init'));
  
      should.exist(TagAPI);
  
      describe('Add', function () {
          var newTag;
  
          beforeEach(function () {
              return testUtils.fixtures.insertTags();
          });
  
          afterEach(function () {
              return testUtils.truncate('tags');
          });
  
          beforeEach(function () {
              newTag = _.clone(_.omit(testUtils.DataGenerator.forKnex.createTag(testUtils.DataGenerator.Content.tags[0]), 'id'));
          });
  
          it('can add a tag (admin)', function (done) {
              TagAPI.add({tags: [newTag]}, testUtils.context.admin)
                  .then(function (results) {
                      should.exist(results);
                      should.exist(results.tags);
                      results.tags.length.should.be.above(0);
                      done();
                  }).catch(done);
          });
  
          it('can add a tag (editor)', function (done) {
              TagAPI.add({tags: [newTag]}, testUtils.context.editor)
                  .then(function (results) {
                      should.exist(results);
                      should.exist(results.tags);
                      results.tags.length.should.be.above(0);
                      results.tags[0].visibility.should.eql('public');
                      done();
                  }).catch(done);
          });
  
          it('can add a tag (author)', function (done) {
              TagAPI.add({tags: [newTag]}, testUtils.context.author)
              .then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.length.should.be.above(0);
                  results.tags[0].visibility.should.eql('public');
                  done();
              }).catch(done);
          });
  
          it('add internal tag', function (done) {
              TagAPI
                  .add({tags: [{name: '#test'}]}, testUtils.context.editor)
                  .then(function (results) {
                      should.exist(results);
                      should.exist(results.tags);
                      results.tags.length.should.be.above(0);
                      results.tags[0].visibility.should.eql('internal');
                      results.tags[0].name.should.eql('#test');
                      results.tags[0].slug.should.eql('hash-test');
                      done();
                  }).catch(done);
          });
  
          it('CANNOT add tag (contributor)', function (done) {
              TagAPI.add({tags: [newTag]}, testUtils.context.contributor)
              .then(function () {
                  done(new Error('Add tag is not denied for contributor.'));
              }, function () {
                  done();
              }).catch(done);
          });
  
          it('No-auth CANNOT add tag', function (done) {
              TagAPI.add({tags: [newTag]}).then(function () {
                  done(new Error('Add tag is not denied without authentication.'));
              }, function () {
                  done();
              }).catch(done);
          });
  
          it('rejects invalid names with ValidationError', function (done) {
              var invalidTag = _.clone(newTag);
  
              invalidTag.name = ', starts with a comma';
  
              TagAPI.add({tags: [invalidTag]}, testUtils.context.admin)
                  .then(function () {
                      done(new Error('Adding a tag with an invalid name is not rejected.'));
                  }).catch(function (errors) {
                  errors[0].errorType.should.eql('ValidationError');
                  done();
              }).catch(done);
          });
      });
  
      describe('Edit', function () {
          beforeEach(function () {
              return testUtils.fixtures.insertTags();
          });
  
          afterEach(function () {
              return testUtils.truncate('tags');
          });
  
          var newTagName = 'tagNameUpdated',
              firstTag = testUtils.DataGenerator.Content.tags[0].id;
  
          it('can edit a tag (admin)', function (done) {
              TagAPI.edit({tags: [{name: newTagName}]}, _.extend({}, context.admin, {id: firstTag}))
                  .then(function (results) {
                      should.exist(results);
                      should.exist(results.tags);
                      results.tags.length.should.be.above(0);
                      done();
                  }).catch(done);
          });
  
          it('can edit a tag (editor)', function (done) {
              TagAPI.edit({tags: [{name: newTagName}]}, _.extend({}, context.editor, {id: firstTag}))
                  .then(function (results) {
                      should.exist(results);
                      should.exist(results.tags);
                      results.tags.length.should.be.above(0);
                      done();
                  }).catch(done);
          });
  
          it('CANNOT edit a tag (author)', function (done) {
              TagAPI.edit({tags: [{name: newTagName}]}, _.extend({}, context.author, {id: firstTag}))
              .then(function () {
                  done(new Error('Add tag is not denied for author.'));
              }, function () {
                  done();
              }).catch(done);
          });
  
          it('No-auth CANNOT edit tag', function (done) {
              TagAPI.edit({tags: [{name: newTagName}]}, _.extend({}, {id: firstTag}))
                  .then(function () {
                      done(new Error('Add tag is not denied without authentication.'));
                  }, function () {
                      done();
                  }).catch(done);
          });
  
          it('rejects invalid names with ValidationError', function (done) {
              var invalidTagName = ', starts with a comma';
  
              TagAPI.edit({tags: [{name: invalidTagName}]}, _.extend({}, context.editor, {id: firstTag}))
                  .then(function () {
                      done(new Error('Adding a tag with an invalid name is not rejected.'));
                  }).catch(function (errors) {
                  errors[0].errorType.should.eql('ValidationError');
                  done();
              }).catch(done);
          });
      });
  
      describe('Destroy', function () {
          beforeEach(function () {
              return testUtils.fixtures.insertTags();
          });
  
          afterEach(function () {
              return testUtils.truncate('tags');
          });
  
          var firstTag = testUtils.DataGenerator.Content.tags[0].id;
  
          it('can destroy Tag', function (done) {
              TagAPI.destroy(_.extend({}, testUtils.context.admin, {id: firstTag}))
                  .then(function (results) {
                      should.not.exist(results);
  
                      done();
                  }).catch(done);
          });
      });
  
      describe('Browse', function () {
          before(function () {
              return testUtils.fixtures.insertPostsAndTags();
          });
  
          after(testUtils.teardown);
  
          before(function () {
              return testUtils.fixtures.insertExtraTags();
          });
  
          it('can browse (internal)', function (done) {
              TagAPI.browse(testUtils.context.internal).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.should.have.lengthOf(15);
                  testUtils.API.checkResponse(results.tags[0], 'tag');
                  results.tags[0].created_at.should.be.an.instanceof(Date);
  
                  results.meta.pagination.should.have.property('page', 1);
                  results.meta.pagination.should.have.property('limit', 15);
                  results.meta.pagination.should.have.property('pages', 4);
                  results.meta.pagination.should.have.property('total', 55);
                  results.meta.pagination.should.have.property('next', 2);
                  results.meta.pagination.should.have.property('prev', null);
  
                  done();
              }).catch(done);
          });
  
          it('can browse page 2 (internal)', function (done) {
              TagAPI.browse(_.extend({}, testUtils.context.internal, {page: 2})).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.should.have.lengthOf(15);
                  testUtils.API.checkResponse(results.tags[0], 'tag');
                  results.tags[0].created_at.should.be.an.instanceof(Date);
  
                  results.meta.pagination.should.have.property('page', 2);
                  results.meta.pagination.should.have.property('limit', 15);
                  results.meta.pagination.should.have.property('pages', 4);
                  results.meta.pagination.should.have.property('total', 55);
                  results.meta.pagination.should.have.property('next', 3);
                  results.meta.pagination.should.have.property('prev', 1);
  
                  done();
              }).catch(done);
          });
  
          it('can browse (owner)', function (done) {
              TagAPI.browse({context: {user: 1}}).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.length.should.be.above(0);
                  testUtils.API.checkResponse(results.tags[0], 'tag');
                  results.tags[0].created_at.should.be.an.instanceof(Date);
  
                  done();
              }).catch(done);
          });
  
          it('can browse (admin)', function (done) {
              TagAPI.browse(testUtils.context.admin).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.length.should.be.above(0);
                  testUtils.API.checkResponse(results.tags[0], 'tag');
                  results.tags[0].created_at.should.be.an.instanceof(Date);
  
                  done();
              }).catch(done);
          });
  
          it('can browse (editor)', function (done) {
              TagAPI.browse(testUtils.context.editor).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.length.should.be.above(0);
                  testUtils.API.checkResponse(results.tags[0], 'tag');
                  results.tags[0].created_at.should.be.an.instanceof(Date);
  
                  done();
              }).catch(done);
          });
  
          it('can browse (author)', function (done) {
              TagAPI.browse(testUtils.context.author).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.length.should.be.above(0);
                  testUtils.API.checkResponse(results.tags[0], 'tag');
                  results.tags[0].created_at.should.be.an.instanceof(Date);
  
                  done();
              }).catch(done);
          });
  
          it('can browse with include count.posts', function (done) {
              TagAPI.browse({context: {user: 1}, include: 'count.posts'}).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.should.have.lengthOf(15);
                  testUtils.API.checkResponse(results.tags[0], 'tag', 'count');
                  should.exist(results.tags[0].count.posts);
  
                  results.tags[0].count.posts.should.eql(2);
                  results.tags[1].count.posts.should.eql(2);
                  results.meta.pagination.should.have.property('page', 1);
                  results.meta.pagination.should.have.property('limit', 15);
                  results.meta.pagination.should.have.property('pages', 4);
                  results.meta.pagination.should.have.property('total', 55);
                  results.meta.pagination.should.have.property('next', 2);
                  results.meta.pagination.should.have.property('prev', null);
  
                  done();
              }).catch(done);
          });
  
          it('can browse page 4 with include count.posts', function (done) {
              TagAPI.browse({context: {user: 1}, include: 'count.posts', page: 4}).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.should.have.lengthOf(10);
                  testUtils.API.checkResponse(results.tags[0], 'tag', 'count');
                  should.exist(results.tags[0].count.posts);
  
                  results.meta.pagination.should.have.property('page', 4);
                  results.meta.pagination.should.have.property('limit', 15);
                  results.meta.pagination.should.have.property('pages', 4);
                  results.meta.pagination.should.have.property('total', 55);
                  results.meta.pagination.should.have.property('next', null);
                  results.meta.pagination.should.have.property('prev', 3);
  
                  done();
              }).catch(done);
          });
  
          it('can browse and order by slug using asc', function (done) {
              var expectedTags;
  
              TagAPI.browse({context: {user: 1}})
                  .then(function (results) {
                      should.exist(results);
  
                      expectedTags = _(results.tags).map('slug').filter(onlyFixtures).sortBy().value();
  
                      return TagAPI.browse({context: {user: 1}, order: 'slug asc'});
                  })
                  .then(function (results) {
                      var tags;
  
                      should.exist(results);
  
                      tags = _(results.tags).map('slug').filter(onlyFixtures).value();
                      tags.should.eql(expectedTags);
                  })
                  .then(done)
                  .catch(done);
          });
  
          it('can browse and order by slug using desc', function (done) {
              var expectedTags;
  
              TagAPI.browse({context: {user: 1}})
                  .then(function (results) {
                      should.exist(results);
  
                      expectedTags = _(results.tags).map('slug').filter(onlyFixtures).sortBy().reverse().value();
  
                      return TagAPI.browse({context: {user: 1}, order: 'slug desc'});
                  })
                  .then(function (results) {
                      var tags;
  
                      should.exist(results);
  
                      tags = _(results.tags).map('slug').filter(onlyFixtures).value();
                      tags.should.eql(expectedTags);
                  })
                  .then(done)
                  .catch(done);
          });
      });
  
      describe('Read', function () {
          before(testUtils.setup('users:roles', 'posts'));
  
          it('returns count.posts with include count.posts', function (done) {
              TagAPI.read({context: {user: 1}, include: 'count.posts', slug: 'kitchen-sink'}).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.length.should.be.above(0);
  
                  testUtils.API.checkResponse(results.tags[0], 'tag', 'count');
                  should.exist(results.tags[0].count.posts);
                  results.tags[0].count.posts.should.equal(2);
  
                  done();
              }).catch(done);
          });
  
          it('with slug', function (done) {
              TagAPI.browse({context: {user: 1}}).then(function (results) {
                  should.exist(results);
                  should.exist(results.tags);
                  results.tags.length.should.be.above(0);
  
                  var firstTag = _.find(results.tags, {id: testUtils.DataGenerator.Content.tags[0].id});
  
                  return TagAPI.read({context: {user: 1}, slug: firstTag.slug});
              }).then(function (found) {
                  should.exist(found);
                  testUtils.API.checkResponse(found.tags[0], 'tag');
  
                  done();
              }).catch(done);
          });
  
          // TODO: this should be a 422?
          it('cannot fetch a tag with an invalid slug', function (done) {
              TagAPI.read({slug: 'invalid!'}).then(function () {
                  done(new Error('Should not return a result with invalid slug'));
              }).catch(function (err) {
                  should.exist(err);
                  err.message.should.eql('Tag not found.');
  
                  done();
              });
          });
      });
  });
  