/*
	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 moduleName = 'services'; // - name for clarification during logging

var serverSupport = require('mdo-server-support');
var strings = require('ibm-strings');
var errors = require('mdo-errors');
var messages = require('./externalizedStrings');
var async = require('async');
var fs = require('fs');
var findup = require('findup-sync');
var log = require('npmlog');
var path = require('path');
var Q = require('q');
var http = require('http');
var https = require('https');

const servicesMap = {
    push : configurePush,
    liveupdate: configureLiveUpdate,
}

module.exports.configureServices = function(appJson, serverInfo, appName) {
    var deferred = Q.defer();

    if (appJson.services) {
        async.forEachSeries(appJson.services, function (service, callback) {
            var serviceName = service.name;
            if (Object.keys(servicesMap).includes(serviceName)) {
                servicesMap[serviceName](service, serverInfo, appName, function (err) {
                    if (err) {
                        callback(err);
                    } else {
                        callback();
                    }
                });
            } else {
                callback();
            }
        }, function (err) {
            if (err) {
                deferred.reject(err);
            } else {
                deferred.resolve();
            }
        });
    } else {
        deferred.resolve();
    }   

    return deferred.promise;
}

function configurePush(pushConfig, serverInfo, appName, callback) {
  async.parallel([
      postTags.bind(null, pushConfig, serverInfo, appName),
      putGcmConf.bind(null, pushConfig, serverInfo, appName),
      putApnsConf.bind(null, pushConfig, serverInfo, appName),
  ], function(err, results) {
      if (err) {
          callback(err);
      } else {
          callback();
      }
  });                                                   
}

function postTags(pushConfig, serverInfo, appName, callback) {    
    if (pushConfig.tags && pushConfig.tags.length > 0) {
      async.forEachSeries(pushConfig.tags, function(tag, innerCallback) {
          var options = {
            path : `/mfpadmin/management-apis/2.0/runtimes/mfp/notifications/applications/${appName}/tags`,
            method : 'POST'
          }
          sendRequest(serverInfo, options, tag).then(function (response) {
              innerCallback();                       
          }).catch(function (err) {
              innerCallback(err);
          });              
      }, function(err) {
          if (err) {
              callback(err);
          } else {
              callback();
          }
      });
    } else {
        callback();
    }
}

function putGcmConf(pushConfig, serverInfo, appName, callback) {
    if (pushConfig.settings && pushConfig.settings.gcm) {
      var options = {
        path : `/mfpadmin/management-apis/2.0/runtimes/mfp/notifications/applications/${appName}/gcmConf`,
        method : 'PUT'
      }      
      sendRequest(serverInfo, options, pushConfig.settings.gcm).then(function (response) {
          callback();                       
      }).catch(function (err) {
          callback(err);
      });              
    } else {
        callback();
    }
}

function putApnsConf(pushConfig, serverInfo, appName, callback) {
    if (pushConfig.settings && pushConfig.settings.apns) {
      var options = {
        path : `/mfpadmin/management-apis/2.0/runtimes/mfp/notifications/applications/${appName}/apnsConf`,
        method : 'PUT'
      }      
      const mfpConfigPath = findup('mfpconfig');
      const payload = {
          'certificate' : fs.createReadStream(path.join(mfpConfigPath, pushConfig.settings.apns.certificate)),
          'isSandBox' : pushConfig.settings.apns.isSandBox.toString(),
          'password' : pushConfig.settings.apns.password
      }      
      sendFileRequest(serverInfo, options, payload).then(function (response) {
          callback();                       
      }).catch(function (err) {
          callback(err);
      });              
    } else {
        callback();
    }
}

function configureLiveUpdate(liveUpdateConfig, serverInfo, appName, callback) { 
  async.parallel([
      putSchema.bind(null, liveUpdateConfig, serverInfo, appName),
  ], function(err, results) {
      if (err) {
          callback(err);
      } else {
          callback();
      }
  });                                                   
}

function putSchema(liveUpdateConfig, serverInfo, appName, callback) {
  if (liveUpdateConfig.schema) {
      var options = {
        path : `/mfpadmin/management-apis/2.0/runtimes/mfp/mfpliveupdate/${appName}/schema`,
        method : 'PUT'
      }
      sendRequest(serverInfo, options, liveUpdateConfig.schema).then(function (response) {
          callback();                       
      }).catch(function (err) {
          callback(err);
      });              
  } else {
      callback();
  }
}

function sendRequest(serverInfo, options, payload) {
    var deferred = Q.defer();

    options.hostname = serverInfo.host;
    options.port = serverInfo.port;
    options.runtime = serverInfo.runtime;
    options.protocol = serverInfo.protocol + ':';

    options.headers = {
      'Content-Type' : 'application/json',
      'Authorization': 'Basic ' + new Buffer(serverInfo.login + ':' + serverInfo.password).toString('base64'),
      'Accept-Charset':'UTF-8',
      'User-Agent':''
    };
    var requestCallback = function(response) {
        var content = '';
        var errs;
  
        if (response.statusCode === 401) {
          var invalidConfidentialClient = errors.getError('INVALIDCONFIDENTIALCLIENT');
          deferred.reject(invalidConfidentialClient);
        }
  
        response.on('data', function (data) {

          if (data) {
            try {
              content += decodeURI(data);
            } catch(err) {
              log.silly(moduleName, 'Failed to perform decodeURI: ' + err);
              content += data;
            }
            if (data.errors) errs = data.errors;
          }
        });
  
        response.on('end', function() {
          if (response.statusCode === 200 || response.statusCode === 201 ) {
            if (content !== '') {
              try {
                var results = JSON.parse(content);
                log.silly(moduleName, 'Results: ' + JSON.stringify(results, null, 2));
  
                if (results.errors && results.errors.length) {
                  strings.fatal(messages.logErrorLabel);
                  results.errors.forEach(function (error) {
                    strings.fatal(error);
                  });
                }
                if (results.warnings && results.warnings.length) {
                  strings.warn(messages.logWarningLabel);
                  results.warnings.forEach(function (warning) {
                    strings.warn(warning);
                  });
                }
              } catch (e) {
                strings.log(content);
              }
            } else {
              log.silly(moduleName, 'Received empty response');
            }
          } else {
            log.silly(moduleName, (strings.format(messages.statusCodeMessage,response.statusCode,http.STATUS_CODES[response.statusCode]))[response.statusCode > 400 ? strings.theme.error : strings.theme.warn]);
            if (content !== '') strings.log(JSON.parse(content));
          }
          deferred.resolve();
        });
    };
  
    var request, formattedParm;
    options.path = encodeURI(options.path);
 
    if (!formattedParm) {
        formattedParm = JSON.stringify(payload);
    }

    log.silly(moduleName, 'call push api:  \'%j\'', options);
    request = getRequest(options, requestCallback);

    if (options.method === 'POST' || options.method === 'PUT') {
        log.silly(moduleName, 'call push api: writing form data:\n', formattedParm);
        request.write(formattedParm);
    }
       

    request.on('error', function (e) {
        // If there's an error with the request, log it and resolve the deferral
        if (e.code === 'ECONNREFUSED') {
          strings.fatal(e.message);
          deferred.reject(e.code);
        }
        else {
          strings.log(messages.errorInvoking);
          strings.log(e.message, strings.theme.error);
        }
        deferred.reject();
    });
    request.end();

    return deferred.promise;
}

function sendFileRequest(serverInfo, options, payload) {
    var deferred = Q.defer();

    const FormData = require('form-data');
    const formData = new FormData();

    options.hostname = serverInfo.host;
    options.port = serverInfo.port;
    options.runtime = serverInfo.runtime;
    options.protocol = serverInfo.protocol + ':';

    options.headers = formData.getHeaders({'Authorization': 'Basic ' + new Buffer(serverInfo.login + ':' + serverInfo.password).toString('base64'), 'User-Agent':''});

    var requestCallback = function(response) {
        var content = '';
        var errs;
  
        if (response.statusCode === 401) {
          var invalidConfidentialClient = errors.getError('INVALIDCONFIDENTIALCLIENT');
          deferred.reject(invalidConfidentialClient);
        }
  
        response.on('data', function (data) {

          if (data) {
            try {
              content += decodeURI(data);
            } catch(err) {
              log.silly(moduleName, 'Failed to perform decodeURI: ' + err);
              content += data;
            }
            if (data.errors) errs = data.errors;
          }
        });
  
        response.on('end', function() {
          if (response.statusCode === 200 || response.statusCode === 201 ) {
            if (content !== '') {
              try {
                var results = JSON.parse(content);
                log.silly(moduleName, 'Results: ' + JSON.stringify(results, null, 2));
  
                if (results.errors && results.errors.length) {
                  strings.fatal(messages.logErrorLabel);
                  results.errors.forEach(function (error) {
                    strings.fatal(error);
                  });
                }
                if (results.warnings && results.warnings.length) {
                  strings.warn(messages.logWarningLabel);
                  results.warnings.forEach(function (warning) {
                    strings.warn(warning);
                  });
                }
              } catch (e) {
                strings.log(content);
              }
            } else {
              strings.warn(messages.emptyResponse);
            }
          } else {
            log.silly(moduleName, (strings.format(messages.statusCodeMessage,response.statusCode,http.STATUS_CODES[response.statusCode]))[response.statusCode > 400 ? strings.theme.error : strings.theme.warn]);
            if (content !== '') strings.log(JSON.parse(content));
          }
          deferred.resolve();
        });
    };
  
    var request, formattedParm;
    options.path = encodeURI(options.path);
 
    if (!formattedParm) {
        formattedParm = JSON.stringify(payload);
    }

    log.silly(moduleName, 'call push api:  \'%j\'', options);
    request = getRequest(options, requestCallback);

    Object.keys(payload).forEach(function(keyName) {
        formData.append(keyName, payload[keyName]);
    })
    formData.pipe(request);

    // if (options.method === 'POST' || options.method === 'PUT') {
    //     log.silly(moduleName, 'call push api: writing form data:\n', formattedParm);
    //     request.write(formattedParm);
    // }
       

    request.on('error', function (e) {
        // If there's an error with the request, log it and resolve the deferral
        if (e.code === 'ECONNREFUSED') {
          strings.fatal(e.message);
          deferred.reject(e.code);
        }
        else {
          strings.log(messages.errorInvoking);
          strings.log(e.message, strings.theme.error);
        }
        deferred.reject();
    });
    //request.end();

    return deferred.promise;
}

function getRequest(options, callback) {
    serverSupport.getCertificate(options, moduleName);
  
    if (options.protocol === 'https:') {
      log.silly(moduleName, 'performing https request');
      return https.request(options, callback);
    } else {
      log.silly(moduleName, 'performing http request');
      return http.request(options, callback);
    }
  }
