/*
	Licensed Material - Property of IBM

	(C) Copyright 2015, 2016 IBM Corp.

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/


'use strict';

var Q = require('q');
var path = require('path');
var fs = require('fs');
var inquirer = require('inquirer');

var strings = require('ibm-strings');
var errors = require('mdo-errors');
var externalizedStrings = require('./externalizedStrings');
var runtimeSupport = require('../lib/rest-client-api/runtime.js');
var defaultOptions = require('../lib/rest-client-api/constants/defaultOptions.js');

var HOME = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
var IBM_HOME = path.join(HOME, '.ibm');
var MFP_HOME = path.join(IBM_HOME, 'mobilefirst');
var LOCAL_PROFILE = 'local';
var SERVER_PROFILES = 'server_profiles.json';

/**
 * Obtains the servers from the server_profiles.json file and returns a JSON object containing those details.
 *
 * Parameters:
 *
 *  createLocalProfile  // Optional String.  If true, a check will be done to see if the local server
 *                      profile exists and whether the local server is up and running.  If the local
 *                      profile does not exist and the server is up and running then a new local profile
 *                      will be created and returned as part of return object.  Default is false.
 *  conditionalCreate   // Optional String.  If true, and createLocalProfile is set to true the local server
 *                      profile will only be created if no existing profiles are found.  Default is false.
 *
 * Returns:
 *  Q.deferred.promise.
 *   On success, a JSON object representing the server profiles is returned
 *   On failure, an error definition is returned
 *
 * Example JSON ouput
 *  {
 *  "servers": {
 *    "server1": {
 *      "local": false,
 *      "protocol": "http",
 *      "host": "server1.raleigh.ibm.com",
 *      "port": "10080",
 *      "login": "admin",
 *      "password": "YWRtaW4="
 *    },
 *    "server2": {
 *      "local": false,
 *      "protocol": "http",
 *      "host": "server2.raleigh.ibm.com",
 *      "port": "10080",
 *      "login": "admin",
 *      "password": "YWRtaW4="
 *    }
 *  },
 *  "defaultServer": "server1"
 * }
 */
function getServerProfiles(createLocalProfile, conditionalCreate) {
  var deferred = Q.defer();

  var versionPath = path.join(MFP_HOME, global.MDO_CLI_FULL_VERSION);
  var profilePath = path.join(versionPath, SERVER_PROFILES);

  setupDirectory(versionPath)
    .then(function() {
      readServerProfiles(profilePath)
        .then(function (serverProfiles) {
          if (createLocalProfile && conditionalCreate) {
            //only create local profile if we have no existing profiles
            createLocalProfile = Object.keys(serverProfiles.servers).length === 0;
          }
          if (!serverProfiles.servers.hasOwnProperty(LOCAL_PROFILE) && createLocalProfile) {
            addLocalProfile(serverProfiles)
              .then(function (serverProfiles) {
                  deferred.resolve(serverProfiles);
                }, function (error) {
                  deferred.reject(error);
                });
          }
          else {
            deferred.resolve(serverProfiles);
          }
        })
        .catch(function (error) {
          deferred.reject(error);
        });
    });
  return deferred.promise;
}

function setupDirectory(versionPath) {
  var deferred = Q.defer();
  // Create the directory structure if non-existing
  if (!fs.existsSync(versionPath)) {
    if (!fs.existsSync(MFP_HOME)) {
      if (!fs.existsSync(IBM_HOME)) {
        fs.mkdirSync(IBM_HOME);
      }
      fs.mkdirSync(MFP_HOME);
    }
    fs.mkdirSync(versionPath);

    // Move the server_profiles.js file from previous version of mfp cli, if applicable
    var mfpVersions = fs.readdirSync(MFP_HOME);
    var previousVersion = null;
    for(var i in mfpVersions) {
      if(fs.existsSync(path.join(MFP_HOME, mfpVersions[i], SERVER_PROFILES))) {
        if(mfpVersions[i].substring(0,1) === global.MDO_CLI_FULL_VERSION.substring(0,1)) {
          previousVersion = path.join(MFP_HOME, mfpVersions[i], SERVER_PROFILES);
        }
      }
    }
    if (previousVersion) {
      try {
        var userKeys = JSON.parse(fs.readFileSync(previousVersion));
        fs.writeFileSync(path.join(versionPath, SERVER_PROFILES),
            JSON.stringify(userKeys, null, 2), 'utf8');
      } catch(err) {
        var cannotWriteFile = errors.getError('ERRWRITEFILE', profilePath);
        deferred.reject(cannotWriteFile);
      }
    }
  }
  deferred.resolve();
  return deferred.promise;
}
function readServerProfiles(profilePath) {
  var deferred = Q.defer();
  var serverProfiles;

  try {
    if (fs.existsSync(profilePath)) {
      serverProfiles = JSON.parse(fs.readFileSync(profilePath, 'utf8'));
      deferred.resolve(serverProfiles);
    }
    else {
      serverProfiles = {};
      serverProfiles.servers = {};
      deferred.resolve(serverProfiles);
    }
  } catch (e) {
    var badConfig = errors.getError('ERRGETSERVER');
    deferred.reject(badConfig);
  }

  return deferred.promise;
}

function addLocalProfile(profiles) {
  var deferred = Q.defer();

  var options = {
    protocol: defaultOptions.PROTOCOL,
    host: defaultOptions.HOST,
    port: defaultOptions.PORT,
    userName: defaultOptions.CREDENTIALS,
    userPassword: defaultOptions.CREDENTIALS,
    contextRoot: defaultOptions.CONTEXT_ROOT,
    timeout: '10'
  };

  //check if local server is up and running
  runtimeSupport.getRuntimes(options)
    .then(function () {
      profiles.servers[LOCAL_PROFILE] = {
        'protocol': options.protocol,
        'host': options.host,
        'port': options.port,
        'login': options.userName,
        'password': new Buffer(options.userPassword).toString('base64'),
        'contextRoot': options.contextRoot,
        'timeout': defaultOptions.TIMEOUT_SECONDS
      };
      if (profiles.defaultServer === undefined) {
        profiles.defaultServer = LOCAL_PROFILE;
      }
      return writeServerProfiles(profiles);
    })
    .then(function (profiles) {
      deferred.resolve(profiles);
    })
    .catch(function (error) {
      if (error.description) {
        //unable to write server profile
        deferred.reject(error);
      }
      else {
        //server is not up so we can't add local profile
        deferred.resolve(profiles);
      }
    });

  return deferred.promise;
}

function writeServerProfiles(profiles) {
  var deferred = Q.defer();

  var versionPath = path.join(MFP_HOME, global.MDO_CLI_FULL_VERSION);
  var profilePath = path.join(versionPath, SERVER_PROFILES);

  try {
      fs.writeFileSync(profilePath, JSON.stringify(profiles, null, 2));
      deferred.resolve(profiles);
  } catch (e) {
    var cannotWriteFile = errors.getError('ERRWRITEFILE', profilePath);
    deferred.reject(cannotWriteFile);
  }

  return deferred.promise;
}

/**
 * Obtains the default server profile, or a given profile if specified.
 * It will also convert any hostname with the name localhost to a static ip.  If multiple ip addresses
 * are found the user will be prompted to select one.  See parameters below to change the default
 * behavior.
 *
 * Parameters:
 *
 *  serverProfile  // Optional String.  If specified will get the server profile with this name.  If
 *                    one is not found an error is returned.  Default is to use the default profile.
 *  useLocalHost   // Optional Boolean. If true, does not convert localhost to a static ip.  Default is false
 *                    and localhost will be converted to a static ip.  User will be prompted to select an ip
 *                    if multiple are found.
 *
 *  Q.deferred.promise.
 *   On success, a JSON object containing the server and runtime details is returned
 *   On failure, an error is returned
 *
 * Example JSON ouput
 *
 *  {
 *    "protocol": "http",
 *    "host": "server1.raleigh.ibm.com",
 *    "port": "9080",
 *    "login": "admin",
 *    "password": "admin",
 *    "contextRoot": "mfpadmin",
 *    "timeout": "30"
 *  }
 *
 */
function getProfileDetails(serverProfile, useLocalHost) {
    var deferred = Q.defer();

    getServerProfiles().then(function (profiles) {
      if (serverProfile) {
        //validate that it exists
        if (profiles.servers[serverProfile] === undefined) {
          var profileNotFound = errors.getError('PROFILENOTFOUND', serverProfile);
          deferred.reject(profileNotFound);
          return deferred.promise;
        }
      }
      else {
        //validate that profiles.defaultServer exists
        if (profiles.defaultServer === undefined) {
          var noServerProfiles = errors.getError('MISSINGSERVERDEFINITION');
          deferred.reject(noServerProfiles);
          return deferred.promise;
        }
      }

      var profileName = serverProfile || profiles.defaultServer;
      var server = profiles.servers[profileName];
      handleLocalHost(useLocalHost, profiles, profileName).then(
        function (localIPAddress) {
          if (localIPAddress) {
            server.host = localIPAddress;
          }
          deferred.resolve(server);
      });
    }).fail(function (error) {
      deferred.reject(error);
    }
  );
  return deferred.promise;
}

function handleLocalHost(useLocalHost, profiles, profileName, neverPrompt) {
  var deferred = Q.defer();
  var server = profiles.servers[profileName];
  if (useLocalHost !== true && server.host === 'localhost') {
    var localIPAddress;
    var addresses = [];
    var choices = [];
    var os = require('os');
    var ifaces = os.networkInterfaces();
    if (ifaces) {
      var keys = Object.keys(ifaces);
      keys.forEach(function (dev) {
        ifaces[dev].forEach(function (details) {
          if (details && details.family === 'IPv4' && !details.internal &&
            details.address) {
            choices.push(details.address + ' (' + dev + ')');
            addresses.push(details.address);
          }
        });
      });
    }
    if (addresses.length === 1) {
      localIPAddress = addresses[0];
      //check if we need to store this value in server_profiles.json
      if (server.ipAddress === undefined ||
        server.ipAddress !== localIPAddress) {
        server.ipAddress = localIPAddress;
        writeServerProfiles(profiles).then(function () {
          deferred.resolve(localIPAddress);
        }).fail(function (error) {
          deferred.reject(error);
        });
      }
      else {
        deferred.resolve(localIPAddress);
      }
    }
    else if (addresses.length > 1) {
      //check if existing ip address is stored in server_profiles.json
      if (addresses.indexOf(server.ipAddress) !== -1) {
        localIPAddress = server.ipAddress;
        deferred.resolve(localIPAddress);
      }
      else if (neverPrompt) {
        //choose the first ip address
        localIPAddress = addresses[0];
        server.ipAddress = localIPAddress;
        writeServerProfiles(profiles).then(function () {
          deferred.resolve(localIPAddress);
        }).fail(function (error) {
          deferred.reject(error);
        });
      }
      else {
        strings.info(externalizedStrings.multipleIPAddresses);
        inquirer.prompt([{
            name: 'ipAddress',
            message: externalizedStrings.selectIPAddress,
            type: 'list',
            choices: choices
          }], function (answers) {
            var selected = answers.ipAddress.match(/ [(\()][\s\S]*(\))$/)[0];
            localIPAddress = answers.ipAddress.split(selected)[0];
            server.ipAddress = localIPAddress;
            writeServerProfiles(profiles).then(function () {
              deferred.resolve(localIPAddress);
            }).fail(function (error) {
              deferred.reject(error);
            });
          }
        );
      }
    }
    else {
      deferred.resolve();
    }
  }
  else {
    deferred.resolve();
  }
  return deferred.promise;
}

module.exports = {
  getServerProfiles: getServerProfiles,
  writeServerProfiles: writeServerProfiles,
  getProfileDetails: getProfileDetails,
  handleLocalHost: handleLocalHost
};
