var _ = require('lodash')
  , async = require('async')
  , should = require('should')
  , assert = require('assert')

module.exports = function(idProperty, getEngine) {

  describe('#find()', function () {

    it('should return empty array when no data matches query ', function (done) {
      getEngine(function (error, engine) {
        engine.find({ a: 1 }, {}, function (error, objects) {

          objects.length.should.eql(0)
          done()
        })
      })

    })

    it('should emit a \'find\' event', function (done) {
      getEngine(function (error, engine) {
        engine.on('find', function (entity) {
          entity.should.eql({ a: 1 })
          done()
        })

        engine.find({ a: 1 }, {}, function () {
        })

      })
    })

    it('should emit a \'received\' event', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { a: 1 } ], engine.create, function () {

          engine.on('received', function (data) {
            assert.equal(data.length, 1)
            assert.equal(data[0].a, 1)
            done()
          })

          engine.find({ a: 1 }, {}, function () {
          })

        })
      })
    })

    it('should return array of objects for a single clause query that matches existing objects', function (done) {

      getEngine(function (error, engine) {
        async.mapSeries([ { a: 1 } ], engine.create, function () {
          engine.find({ a: 1 }, {}, function (error, objects) {
            objects.length.should.not.eql(0)
            objects[0].a.should.equal(1)
            done()
          })
        })
      })

    })

    it('should still return expected objects when callback is second parameter', function (done) {

      getEngine(function (error, engine) {
        async.mapSeries([ { a: 1 } ], engine.create, function () {
          engine.find({ a: 1 }, function (error, objects) {
            objects.length.should.not.eql(0)
            objects[0].a.should.equal(1)
            done()
          })
        })
      })

    })

    it('should not error if a query property is not in object collection', function (done) {

      getEngine(function (error, engine) {
        async.mapSeries([ { a: 1 } ], engine.create, function () {
          engine.find({ b: 1 }, {}, function (error, objects) {
            should.not.exist(error)
            objects.length.should.eql(0)
            done()
          })
        })
      })

    })

    it('should return array of objects that match all properties in query ', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { findTest: 1 }, { findTest: 1 }, { findTest: 1 }, { b: 1 } ], engine.create, function () {
          engine.find({ findTest: 1 }, {}, function (error, objects) {
            objects.length.should.equal(3)
            done()
          })
        })
      })
    })

    it('should allow $gt operator', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { findTest: 1 }, { findTest: 2 }, { findTest: 3 }, { findTest: 4 } ], engine.create, function () {
          engine.find({ findTest: { $gt: 2 } }, function (error, objects) {
            objects.length.should.equal(2)
            done()
          })
        })
      })
    })

    it('should allow $lt operator', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { findTest: 0.8 }, { findTest: 1.9 }, { findTest: 3 }, { findTest: 4 } ], engine.create, function () {
          engine.find({ findTest: { $lt: 2 } }, function (error, objects) {
            objects.length.should.equal(2)
            objects[0].findTest.should.equal(0.8)
            done()
          })
        })
      })
    })

    it('should return all objects with properties in a given array ($in)', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { findTest: 1 }, { findTest: 2 }, { findTest: 3 } ], engine.create, function () {
          engine.find({ findTest: { $in: [ 1, 3 ] } }, {}, function (error, objects) {
            objects.length.should.equal(2)
            done()
          })
        })
      })
    })

    it('should return array of objects that match specified fields of a subdocument in query', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { findTest: { nested: 1 } }, { findTest: { nested: 1 } }, { findTest: { nested: 2 } }, { b: 1 } ], engine.create, function () {
          engine.find({ 'findTest.nested': 1 }, {}, function (error, objects) {
            objects.length.should.equal(2)
            done()
          })
        })
      })
    })

    it('should return array of objects that match specified fields of a deep subdocument in query', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries(
            [ { findTest: { nested: 1 } }
            , { findTest: { nested: { nested: 1 } } }
            , { findTest: { nested: { nested: 1 } } }
            , { b: 1 } ]
            , engine.create, function () {
          engine.find({ 'findTest.nested.nested': 1 }, {}, function (error, objects) {
            objects.length.should.equal(2)
            done()
          })
        })
      })
    })

    it('should return array of all objects for an empty query {}', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { a: 1 }, { a: 1 }, { a: 1 }, { b: 1 } ], engine.create, function () {
          engine.find({}, {}, function (error, objects) {
            objects.length.should.equal(4)
            done()
          })
        })
      })
    })

    it('should support { fields: {} } projection in options', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { a: 1, b: 1 }, { a: 2, b: 2 }, { b: 3, c: 3 }, { b: 4, c: 4 } ], engine.create, function () {
          engine.find({}, { fields: { b: 1, c: 1 }, sort: { b: 1 } }, function (error, objects) {
            objects.map(function(object) { delete object._id; return object })
              .should.eql([ { b: 1 }, { b: 2 }, { b: 3, c: 3 }, { b: 4, c: 4 } ])
            done()
          })
        })
      })
    })

    it('should support { limit: n } property in options', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { a: 1, b: 1 }, { a: 2, b: 2 }, { b: 3, c: 3 }, { b: 4, c: 4 } ], engine.create, function (error) {
          if (error) return done(error)
          engine.find({}, { sort: { b: 1 }, fields: { b: 1, c: 1 }, limit: 1 }, function (error, objects) {
            objects.map(function(object) { delete object._id; return object })
              .should.eql([ { b: 1 } ])
            done()
          })
        })
      })
    })

    it('should return array of all objects for an empty query {} when there are no options', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { a: 1 }, { a: 1 }, { a: 1 }, { b: 1 } ], engine.create, function () {
          engine.find({}, function (error, objects) {
            objects.length.should.equal(4)
            done()
          })
        })
      })
    })

    it('should return array of objects in ascending order', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { a: 3 }, { a: 1 }, { a: 2 } ], engine.create, function () {
          engine.find({}, { sort: { a: 1 } }, function (error, objects) {
            objects[0].a.should.equal(1)
            objects[1].a.should.equal(2)
            objects[2].a.should.equal(3)
            done()
          })
        })
      })
    })

    it('should return array of objects in descending order', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { a: 3 }, { a: 1 }, { a: 2 } ], engine.create, function () {
          engine.find({}, { sort: { a: -1 } }, function (error, objects) {
            objects[0].a.should.equal(3)
            objects[1].a.should.equal(2)
            objects[2].a.should.equal(1)
            done()
          })
        })
      })
    })

    it('should return a new cloned object', function (done) {
      var item = { a: 1 }
        , dataClone = _.clone(item)

      getEngine(function (error, engine) {
        async.mapSeries([ item ], engine.create, function (error, createdObject) {
          delete createdObject.a

          createdObject[0].should.not.eql(dataClone)
          item.should.have.property('a')
          engine.read(createdObject[0]._id, function (error, item) {
            item.should.have.property('a')
            item.should.have.property('_id')
            done()
          })
        })
      })
    })

    it('should return id of type string', function (done) {
      getEngine(function (error, engine) {
        async.mapSeries([ { a: 3 } ], engine.create, function () {
          engine.find({}, { sort: [ [ 'a', 'desc' ] ] }, function (error, objects) {
            objects[0][idProperty].should.be.type('string')
            done()
          })
        })
      })
    })

  })

}
