'use strict';
const dns = require('dns');
const kerberos = require('../kerberos');

class MongoAuthProcess {
  constructor(host, port, serviceName, options) {
    options = options || {};
    this.host = host;
    this.port = port;

    // Set up service name
    this.serviceName = serviceName || options.gssapiServiceName || 'mongodb';

    // Options
    this.canonicalizeHostName =
      typeof options.gssapiCanonicalizeHostName === 'boolean'
        ? options.gssapiCanonicalizeHostName
        : false;

    // Set up first transition
    this._transition = firstTransition(this);

    // Number of retries
    this.retries = 10;
  }

  init(username, password, callback) {
    const self = this;
    this.username = username;
    this.password = password;

    // Canonicialize host name if needed
    function performGssapiCanonicalizeHostName(canonicalizeHostName, host, callback) {
      if (!canonicalizeHostName) return callback();

      // Attempt to resolve the host name
      dns.resolveCname(host, (err, r) => {
        if (err) return callback(err);

        // Get the first resolve host id
        if (Array.isArray(r) && r.length > 0) {
          self.host = r[0];
        }

        callback();
      });
    }

    // Canonicialize host name if needed
    performGssapiCanonicalizeHostName(this.canonicalizeHostName, this.host, err => {
      if (err) return callback(err);

      const initOptions = {};
      if (password != null) {
        Object.assign(initOptions, { user: username, password });
      }

      const service =
        process.platform === 'win32'
          ? `${this.serviceName}/${this.host}`
          : `${this.serviceName}@${this.host}`;

      kerberos.initializeClient(service, initOptions, (err, client) => {
        if (err) return callback(err, null);

        self.client = client;
        callback(null, client);
      });
    });
  }

  transition(payload, callback) {
    if (this._transition == null) {
      return callback(new Error('Transition finished'));
    }

    this._transition(payload, callback);
  }
}

function firstTransition(auth) {
  return (payload, callback) => {
    auth.client.step('', (err, response) => {
      if (err) return callback(err);

      // Set up the next step
      auth._transition = secondTransition(auth);

      // Return the payload
      callback(null, response);
    });
  };
}

function secondTransition(auth) {
  return (payload, callback) => {
    auth.client.step(payload, (err, response) => {
      if (err && auth.retries === 0) return callback(err);

      // Attempt to re-establish a context
      if (err) {
        // Adjust the number of retries
        auth.retries = auth.retries - 1;

        // Call same step again
        return auth.transition(payload, callback);
      }

      // Set up the next step
      auth._transition = thirdTransition(auth);

      // Return the payload
      callback(null, response || '');
    });
  };
}

function thirdTransition(auth) {
  return (payload, callback) => {
    // GSS Client Unwrap
    auth.client.unwrap(payload, (err, response) => {
      if (err) return callback(err, false);

      // Wrap the response
      auth.client.wrap(response, { user: auth.username }, (err, wrapped) => {
        if (err) return callback(err, false);

        // Set up the next step
        auth._transition = fourthTransition(auth);

        // Return the payload
        callback(null, wrapped);
      });
    });
  };
}

function fourthTransition(auth) {
  return (payload, callback) => {
    // Set the transition to null
    auth._transition = null;

    // Callback with valid authentication
    callback(null, true);
  };
}

// Set the process
module.exports = {
  MongoAuthProcess
};
