var expect = require('expect.js')
var _ = require('lodash')

var describe = require('mocha').describe
var it = require('mocha').it
var Promise = require('bluebird')

var Pool = require('../')

if (typeof global.Promise === 'undefined') {
  global.Promise = Promise
}

describe('pool', function () {
  it('can be used as a factory function', function () {
    var pool = Pool()
    expect(pool instanceof Pool).to.be.ok()
    expect(typeof pool.connect).to.be('function')
  })

  describe('with callbacks', function () {
    it('works totally unconfigured', function (done) {
      var pool = new Pool()
      pool.connect(function (err, client, release) {
        if (err) return done(err)
        client.query('SELECT NOW()', function (err, res) {
          release()
          if (err) return done(err)
          expect(res.rows).to.have.length(1)
          pool.end(done)
        })
      })
    })

    it('passes props to clients', function (done) {
      var pool = new Pool({ binary: true })
      pool.connect(function (err, client, release) {
        release()
        if (err) return done(err)
        expect(client.binary).to.eql(true)
        pool.end(done)
      })
    })

    it('can run a query with a callback without parameters', function (done) {
      var pool = new Pool()
      pool.query('SELECT 1 as num', function (err, res) {
        expect(res.rows[0]).to.eql({ num: 1 })
        pool.end(function () {
          done(err)
        })
      })
    })

    it('can run a query with a callback', function (done) {
      var pool = new Pool()
      pool.query('SELECT $1::text as name', ['brianc'], function (err, res) {
        expect(res.rows[0]).to.eql({ name: 'brianc' })
        pool.end(function () {
          done(err)
        })
      })
    })

    it('passes connection errors to callback', function (done) {
      var pool = new Pool({host: 'no-postgres-server-here.com'})
      pool.query('SELECT $1::text as name', ['brianc'], function (err, res) {
        expect(res).to.be(undefined)
        expect(err).to.be.an(Error)
        pool.end(function (err) {
          done(err)
        })
      })
    })

    it('removes client if it errors in background', function (done) {
      var pool = new Pool()
      pool.connect(function (err, client, release) {
        release()
        if (err) return done(err)
        client.testString = 'foo'
        setTimeout(function () {
          client.emit('error', new Error('on purpose'))
        }, 10)
      })
      pool.on('error', function (err) {
        expect(err.message).to.be('on purpose')
        expect(err.client).to.not.be(undefined)
        expect(err.client.testString).to.be('foo')
        err.client.connection.stream.on('end', function () {
          pool.end(done)
        })
      })
    })

    it('should not change given options', function (done) {
      var options = { max: 10 }
      var pool = new Pool(options)
      pool.connect(function (err, client, release) {
        release()
        if (err) return done(err)
        expect(options).to.eql({ max: 10 })
        pool.end(done)
      })
    })

    it('does not create promises when connecting', function (done) {
      var pool = new Pool()
      var returnValue = pool.connect(function (err, client, release) {
        release()
        if (err) return done(err)
        pool.end(done)
      })
      expect(returnValue).to.be(undefined)
    })

    it('does not create promises when querying', function (done) {
      var pool = new Pool()
      var returnValue = pool.query('SELECT 1 as num', function (err) {
        pool.end(function () {
          done(err)
        })
      })
      expect(returnValue).to.be(undefined)
    })

    it('does not create promises when ending', function (done) {
      var pool = new Pool()
      var returnValue = pool.end(done)
      expect(returnValue).to.be(undefined)
    })
  })

  describe('with promises', function () {
    it('connects and disconnects', function () {
      var pool = new Pool()
      return pool.connect().then(function (client) {
        expect(pool.pool.availableObjectsCount()).to.be(0)
        return client.query('select $1::text as name', ['hi']).then(function (res) {
          expect(res.rows).to.eql([{ name: 'hi' }])
          client.release()
          expect(pool.pool.getPoolSize()).to.be(1)
          expect(pool.pool.availableObjectsCount()).to.be(1)
          return pool.end()
        })
      })
    })

    it('properly pools clients', function () {
      var pool = new Pool({ poolSize: 9 })
      return Promise.map(_.times(30), function () {
        return pool.connect().then(function (client) {
          return client.query('select $1::text as name', ['hi']).then(function (res) {
            client.release()
            return res
          })
        })
      }).then(function (res) {
        expect(res).to.have.length(30)
        expect(pool.pool.getPoolSize()).to.be(9)
        return pool.end()
      })
    })

    it('supports just running queries', function () {
      var pool = new Pool({ poolSize: 9 })
      return Promise.map(_.times(30), function () {
        return pool.query('SELECT $1::text as name', ['hi'])
      }).then(function (queries) {
        expect(queries).to.have.length(30)
        expect(pool.pool.getPoolSize()).to.be(9)
        expect(pool.pool.availableObjectsCount()).to.be(9)
        return pool.end()
      })
    })

    it('recovers from all errors', function () {
      var pool = new Pool()

      var errors = []
      return Promise.mapSeries(_.times(30), function () {
        return pool.query('SELECT asldkfjasldkf')
          .catch(function (e) {
            errors.push(e)
          })
      }).then(function () {
        return pool.query('SELECT $1::text as name', ['hi']).then(function (res) {
          expect(errors).to.have.length(30)
          expect(res.rows).to.eql([{ name: 'hi' }])
          return pool.end()
        })
      })
    })
  })
})

describe('pool error handling', function () {
  it('Should complete these queries without dying', function (done) {
    var pgPool = new Pool()
    var pool = pgPool.pool
    pool._factory.max = 1
    pool._factory.min = null
    var errors = 0
    var shouldGet = 0
    function runErrorQuery () {
      shouldGet++
      return new Promise(function (resolve, reject) {
        pgPool.query("SELECT 'asd'+1 ").then(function (res) {
          reject(res) // this should always error
        }).catch(function (err) {
          errors++
          resolve(err)
        })
      })
    }
    var ps = []
    for (var i = 0; i < 5; i++) {
      ps.push(runErrorQuery())
    }
    Promise.all(ps).then(function () {
      expect(shouldGet).to.eql(errors)
      done()
    })
  })
})

process.on('unhandledRejection', function (e) {
  console.error(e.message, e.stack)
  setImmediate(function () {
    throw e
  })
})
