import binary from 'binary'
import domain from 'domain'

import {RFC_1929_REPLIES, RFC_1929_VERSION} from './constants'

import {AuthenticatePatameters, AuthenticateArgs} from '../types/authenticate'

/**
 * +----+------+----------+------+----------+
 * |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
 * +----+------+----------+------+----------+
 * | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
 * +----+------+----------+------+----------+
 *
 *
 * @param {Buffer} buffer - a buffer
 * @returns {undefined}
 **/
export function authenticate({
  socket,
  buffer,
  options,
  connect,
  end,
  onAuthenticated,
  onFailed,
}: AuthenticatePatameters) {
  const authDomain = domain.create()

  binary
    .stream(buffer)
    .word8('version')
    .word8('ulen')
    .buffer('username', 'ulen')
    .word8('plen')
    .buffer('password', 'plen')
    .tap((args: AuthenticateArgs) => {
      // capture the raw buffer
      args.requestBuffer = buffer

      // verify version is appropriate
      if (args.version !== RFC_1929_VERSION) {
        return end(RFC_1929_REPLIES.GENERAL_FAILURE, args)
      }

      authDomain.on('error', (err) => {
        // emit failed authentication event
        onFailed(args, err)

        // respond with auth failure
        return end(RFC_1929_REPLIES.GENERAL_FAILURE, args)
      })

      // perform authentication
      options.authenticate!(
        args.username.toString(),
        args.password.toString(),
        socket,
        authDomain.intercept(() => {
          // emit successful authentication event
          onAuthenticated(args)

          // respond with success...
          const responseBuffer = Buffer.allocUnsafe(2)
          responseBuffer[0] = RFC_1929_VERSION
          responseBuffer[1] = RFC_1929_REPLIES.SUCCEEDED

          // respond then listen for cmd and dst info
          socket.write(responseBuffer, () => {
            // now listen for more details
            socket.once('data', connect)
          })
        }),
      )
    })
}
