var util = require('util')

var ldap = require('ldapjs')
var Promise = require('bluebird')

function InvalidCredentialsError(user) {
  Error.call(this, util.format('Invalid credentials for user "%s"', user))
  this.name = 'InvalidCredentialsError'
  this.user = user
  Error.captureStackTrace(this, InvalidCredentialsError)
}

util.inherits(InvalidCredentialsError, Error)

// Export
module.exports.InvalidCredentialsError = InvalidCredentialsError

// Export
module.exports.login = function(options, username, password) {
  function tryConnect() {
    var resolver = Promise.defer()
      , client = ldap.createClient({
          url: options.url
        , timeout: options.timeout
        , maxConnections: 1
        })

    if (options.bind.dn) {
      client.bind(options.bind.dn, options.bind.credentials, function(err) {
        if (err) {
          resolver.reject(err)
        }
        else {
          resolver.resolve(client)
        }
      })
    }
    else {
      resolver.resolve(client)
    }

    return resolver.promise
  }

  function tryFind(client) {
    var resolver = Promise.defer()
      , query = {
          scope: options.search.scope
        , filter: new ldap.AndFilter({
            filters: [
              new ldap.EqualityFilter({
                attribute: 'objectClass'
              , value: options.search.objectClass
              })
            , new ldap.EqualityFilter({
                attribute: options.search.field
              , value: username
              })
            ]
          })
        }

    client.search(options.search.dn, query, function(err, search) {
      if (err) {
        return resolver.reject(err)
      }

      function entryListener(entry) {
        resolver.resolve(entry)
      }

      function endListener() {
        resolver.reject(new InvalidCredentialsError(username))
      }

      function errorListener(err) {
        resolver.reject(err)
      }

      search.on('searchEntry', entryListener)
      search.on('end', endListener)
      search.on('error', errorListener)

      resolver.promise.finally(function() {
        search.removeListener('searchEntry', entryListener)
        search.removeListener('end', endListener)
        search.removeListener('error', errorListener)
      })
    })

    return resolver.promise
  }

  function tryBind(client, entry) {
    return new Promise(function(resolve, reject) {
      client.bind(entry.object.dn, password, function(err) {
        if (err) {
          reject(new InvalidCredentialsError(username))
        }
        else {
          resolve(entry.object)
        }
      })
    })
  }

  return tryConnect().then(function(client) {
    return tryFind(client)
      .then(function(entry) {
        return tryBind(client, entry)
      })
      .finally(function() {
        client.unbind()
      })
  })
}

// Export
module.exports.email = function(user) {
  return user.mail || user.email || user.userPrincipalName
}
