'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _fsExtra = require('fs-extra');

var _fsExtra2 = _interopRequireDefault(_fsExtra);

var _findKey = require('find-key');

var _findKey2 = _interopRequireDefault(_findKey);

var _md = require('md5');

var _md2 = _interopRequireDefault(_md);

var _hashdirectory = require('hashdirectory');

var _hashdirectory2 = _interopRequireDefault(_hashdirectory);

var _unzip = require('unzip2');

var _unzip2 = _interopRequireDefault(_unzip);

var _jsYaml = require('js-yaml');

var _jsYaml2 = _interopRequireDefault(_jsYaml);

var _axios = require('axios');

var _axios2 = _interopRequireDefault(_axios);

var _readdirp = require('readdirp');

var _readdirp2 = _interopRequireDefault(_readdirp);

var _mkdirp = require('mkdirp');

var _mkdirp2 = _interopRequireDefault(_mkdirp);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _formData = require('form-data');

var _formData2 = _interopRequireDefault(_formData);

var _archiver = require('archiver');

var _archiver2 = _interopRequireDefault(_archiver);

var _bluebird = require('bluebird');

var _bluebird2 = _interopRequireDefault(_bluebird);

var _es6TemplateStrings = require('es6-template-strings');

var _es6TemplateStrings2 = _interopRequireDefault(_es6TemplateStrings);

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _sourceMap = require('source-map');

var _sourceMap2 = _interopRequireDefault(_sourceMap);

var _debug = require('../debug');

var _debug2 = _interopRequireDefault(_debug);

var _session = require('../session');

var _session2 = _interopRequireDefault(_session);

var _utils = require('./utils');

var _utils2 = _interopRequireDefault(_utils);

var _hosting = require('../hosting');

var _hosting2 = _interopRequireDefault(_hosting);

var _registry = require('../registry');

var _registry2 = _interopRequireDefault(_registry);

var _compile = require('../compile');

var _printTools = require('../print-tools');

var _Constants = require('../../constants/Constants');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new _bluebird2.default(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return _bluebird2.default.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

const { debug } = (0, _debug2.default)('utils-sockets');

class MetadataObject {
  constructor(name, metadata, socketName) {
    this.name = name;
    this.metadata = metadata;
    this.socketName = socketName;
    this.existRemotely = null;
    this.existLocally = null;
    this.isProjectRegistryDependency = null;
  }
  getStatus() {
    if (this.existLocally && this.existRemotely) {
      return { status: 'synced', type: 'ok' };
    }

    if (this.existLocally && !this.existRemotely) {
      return { status: 'not synced', type: 'warn' };
    }

    if (!this.existLocally && this.existRemotely) {
      return { status: 'no local configuration', type: 'fail' };
    }
  }
}

class Endpoint extends MetadataObject {

  call(params) {
    return _axios2.default.request({
      url: this.getURL(),
      method: 'POST',
      timeout: 3000,
      params,
      // Do not transform data automaticaly
      transformResponse: data => data
    });
  }

  getFullName() {
    return `${this.socketName}/${this.name}`;
  }

  getURL() {
    return `https://${_session2.default.getSpaceHost()}/${this.socketName}/${this.name}/`;
  }
}

class Handler extends MetadataObject {}

class Event extends MetadataObject {}

class Socket {
  constructor(socketName, socketPath) {
    debug('Sockets.constructor', socketName);
    this.name = socketName;
    this.settings = { loaded: false };
    this.socketPath = socketPath || _utils2.default.findLocalPath(socketName);

    if (this.socketPath) {
      this.settings = _session2.default.settings.getSocketSettings(this.socketPath, this.name);
    }

    this.existRemotely = null;
    this.existLocally = null;
    this.isProjectRegistryDependency = null;
    this.dependencies = [];
    this.dependencyOf = [];

    // that looks stupid
    this.remote = { spec: { endpoints: {}, event_handlers: {}, events: {} }, metadata: {} };
    this.spec = { spec: { endpoints: {}, event_handlers: {}, events: {} } };

    this.loadLocal();

    // SocketPath for non-local sockets
    if (this.isDependencySocket || this.isProjectRegistryDependency) {
      this.socketPath = _path2.default.join(_session2.default.getBuildPath(), this.name);
    }
  }

  static getTemplatesChoices() {
    return _utils2.default.getTemplatesChoices();
  }

  // Install / Uninstall
  static add(registrySocket) {
    debug('add()');
    _session2.default.settings.project.installSocket(registrySocket);
  }

  // Install / Uninstall
  static install(registrySocket, config) {
    debug('install()');

    const socketName = registrySocket.name;
    _session2.default.settings.project.installSocket(registrySocket);

    const socketFolder = _path2.default.join(_session2.default.getBuildPath(), socketName);
    _mkdirp2.default.sync(socketFolder);

    debug('Getting socket from registry');
    return _registry2.default.getSocket(registrySocket, config).then(resp => {
      debug('Registry socket:', registrySocket);
      const fileName = _path2.default.join(_session2.default.getBuildPath(), `${socketName}.zip`);

      return new _bluebird2.default((resolve, reject) => {
        _fsExtra2.default.createReadStream(fileName).pipe(_unzip2.default.Extract({ path: socketFolder })).on('close', () => {
          debug('Unzip finished');
          const newSocket = new Socket(socketName, socketFolder);
          return Socket.updateSocketNPMDeps(socketFolder).then(() => newSocket.loadRemote()).then(() => newSocket.update({ config })).then(updateStatus => {
            debug('updateStatus', updateStatus);
            newSocket.updateStatus = updateStatus;
            resolve(newSocket);
          }).catch(err => {
            reject(err);
          });
        });
      });
    });
  }

  static uninstall(socket = {}) {
    debug('uninstall', socket.name);

    if (socket.isProjectRegistryDependency) {
      _session2.default.settings.project.uninstallSocket(socket.name);
    }

    if (socket.existLocally && socket.localPath) {
      Socket.uninstallLocal(socket);
      if (socket.existRemotely) {
        this.uninstallRemote(socket.name);
      }
      return _bluebird2.default.resolve();
    }

    if (socket.existRemotely) {
      return this.uninstallRemote(socket.name);
    }

    return _bluebird2.default.reject();
  }

  static uninstallLocal(socket) {
    _fsExtra2.default.removeSync(socket.localPath);
  }

  // TODO: check if the socket is installed (it may be not yet installed yet (before sync))
  static uninstallRemote(socketName) {
    debug('uninstallRemote', socketName);
    return _session2.default.connection.Socket.please().delete({ name: socketName }).then(resp => resp).catch(err => err);
  }

  // list sockets based on call to Syncano (sockets are installed on Synano)
  static listRemote() {
    debug('listRemote()');
    return _session2.default.connection.Socket.please().list();
  }

  static listDeps() {
    // List project dependencies
    return Object.keys(_session2.default.settings.project.getDependSockets());
  }

  // list all sockets (mix of locally definde and installed on server)
  static list() {
    return _asyncToGenerator(function* () {
      debug('list()');
      // Local Socket defined in folders and in project deps
      const localSocketsList = _utils2.default.listLocal().concat(Socket.listDeps());
      return _bluebird2.default.all(localSocketsList.map(function (socketName) {
        return Socket.get(socketName);
      }));
    })();
  }

  static flatList(socketName) {
    return _asyncToGenerator(function* () {
      debug('flatList()');
      const sockets = [];

      const addToList = function (socket) {
        const duplicated = _lodash2.default.find(sockets, function (s) {
          return s.name === socket.name;
        });
        if (duplicated) {
          if (!_lodash2.default.includes(duplicated.dependencyOf, socket.name)) {
            duplicated.dependencyOf.concat(socket.dependencyOf);
          }
        } else {
          sockets.push(socket);
          socket.dependencies.forEach(function (dep) {
            addToList(dep);
          });
        }
      };

      if (socketName) {
        return Socket.get(socketName).then(function (socket) {
          addToList(socket);
          return sockets;
        });
      }

      // All Sockets
      return Socket.list().then(function (allSockets) {
        allSockets.forEach(function (socket) {
          addToList(socket);
        });
        return sockets;
      });
    })();
  }

  // Creating Socket simple object
  static getLocal(socketName) {
    return new Socket(socketName);
  }

  static get(socketName) {
    debug(`Getting Socket: ${socketName}`);
    const socket = Socket.getLocal(socketName);
    return socket.loadRemote().then(loadedSocket => loadedSocket.getDepsRegistrySockets()).then(() => {
      if (!socket.existLocally) {
        return socket.loadFromRegistry();
      }
      return _bluebird2.default.resolve();
    }).then(() => socket).catch(() => socket);
  }

  static create(socketName, templateName) {
    debug('create socket');
    const newSocketPath = _path2.default.join(_session2.default.projectPath, socketName);
    const socket = new Socket(socketName, newSocketPath);
    if (socket.existLocally) {
      return _bluebird2.default.reject('Socket with that name already exist!');
    }
    return socket.init(templateName);
  }

  static publish(socketName, version) {
    const registry = new _registry2.default();
    return registry.publishSocket(socketName, version);
  }

  init(templateName) {
    return new _bluebird2.default((resolve, reject) => {
      const socketPath = this.getSocketPath();
      if (!_fsExtra2.default.existsSync(socketPath)) {
        _mkdirp2.default.sync(socketPath);
      }

      try {
        const templateFolder = _path2.default.normalize((0, _Constants.getSocketTemplate)(templateName));
        const files = _fsExtra2.default.walkSync(templateFolder);
        files.forEach(file => {
          const oldContent = _fsExtra2.default.readFileSync(file, 'utf8');
          const socket = {
            socketName: this.name,
            socketDescription: `Description of ${this.name}`
          };

          const newContent = (0, _es6TemplateStrings2.default)(oldContent, socket, { partial: true });
          const fileToSave = _path2.default.join(socketPath, file.replace(templateFolder, ''));

          _mkdirp2.default.sync(_path2.default.parse(fileToSave).dir);
          _fsExtra2.default.writeFileSync(_path2.default.join(socketPath, file.replace(templateFolder, '')), newContent);
        });
        resolve(this);
      } catch (err) {
        return reject(err);
      }
    });
  }

  verify() {
    return new _bluebird2.default((resolve, reject) => {
      if (!(this.isDependencySocket || this.isProjectRegistryDependency)) {
        if (!_fsExtra2.default.existsSync(this.getSrcFolder())) {
          reject(new Error('No src folder!'));
        }
      }
      resolve();
    });
  }

  getFullConfig() {
    return this.settings.getFull();
  }

  getRemote() {
    return _session2.default.connection.Socket.please().get({ name: this.name }).then(socket => socket).catch(err => false);
  }

  getRemoteSpec() {
    debug('getRemoteSpec');
    if (this.remote.files['socket.yml']) {
      return _axios2.default.request({
        url: this.remote.files['socket.yml'].file,
        method: 'GET',
        timeout: 3000
      }).then(spec => {
        this.remote.spec = _jsYaml2.default.load(spec.data);
      });
    }
    return _bluebird2.default.resolve();
  }

  setRemoteState(socket) {
    this.existRemotely = true;
    this.remote.name = socket.name;
    this.remote.environment = socket.environment;
    this.remote.version = socket.version;
    this.remote.updatedAt = socket.updated_at;
    this.remote.installed = socket.installed;
    this.remote.files = socket.files;
    this.remote.status = socket.status;
    this.remote.statusInfo = socket.status_info;
    this.remote.config = socket.config;
    this.remote.metadata = socket.metadata;
  }

  loadRemote() {
    debug('loadRemote()');
    return this.getRemote().then(socket => {
      if (socket) {
        this.setRemoteState(socket);
        return this.getRemoteSpec();
      }

      this.existRemotely = false;
      return _bluebird2.default.resolve(this);
    }).then(() => this);
  }

  loadFromRegistry() {
    var _this = this;

    return _asyncToGenerator(function* () {
      debug(`loadFromRegistry: ${_this.name}`);
      const registry = new _registry2.default();
      const registrySocket = yield registry.getFullSocket(_this.name);
      _this.spec = registrySocket.config;
      _this.url = registrySocket.url;
    })();
  }

  loadLocal() {
    debug('loadLocal');
    if (this.settings.loaded) {
      this.existLocally = true;
      this.localPath = this.settings.baseDir;
      this.spec = this.settings.getFull();
    } else if (_lodash2.default.includes(Socket.listDeps(), this.name)) {
      this.isProjectRegistryDependency = true;
      this.existLocally = false;
      this.spec.version = _session2.default.settings.project.getDependSocket(this.name).version;
    }
  }

  isSocketFile(fileFullPath) {
    debug('isSocketFile', fileFullPath);
    return fileFullPath.includes(this.localPath);
  }

  getRawStatus() {
    return {
      isProjectRegistryDependency: this.isProjectRegistryDependency,
      existRemotely: this.existRemotely,
      existLocally: this.existLocally
    };
  }

  getStatus() {
    if (this.isProjectRegistryDependency && !this.existRemotely) {
      return { status: 'not synced', type: 'warn' };
    }

    if (this.existLocally && !this.existRemotely) {
      return { status: 'not synced', type: 'warn' };
    }

    let msg = this.remote.statusInfo || this.remote.status;
    if (msg && msg.error) {
      msg = msg.error;
    }

    if (this.remote.status === 'ok') {
      return { status: msg, type: 'ok' };
    } else if (this.remote.status === 'processing') {
      return { status: msg, type: 'warn' };
    }
    return { status: msg, type: 'fail' };
  }

  getType() {
    if (this.isProjectRegistryDependency) {
      return { msg: 'project dependency', type: 'ok' };
    }

    if (this.isDependencySocket) {
      return { msg: `dependency of ${this.dependencyOf.join(', ')}`, type: 'ok' };
    }

    if (this.existLocally) {
      return { msg: 'local Socket', type: 'ok' };
    }

    return { msg: 'no local configuration', type: 'warn' };
  }

  getVersion() {
    return this.remote ? this.remote.version : null;
  }

  getScripts() {
    return (0, _findKey2.default)(this.spec, 'file');
  }

  getSrcFolder() {
    return _path2.default.join(this.getSocketPath(), 'src', _path2.default.sep);
  }

  getCompiledScriptsFolder() {
    const folder = _path2.default.join(this.getSocketPath(), '.dist', 'src', _path2.default.sep);
    if (!_fsExtra2.default.existsSync(folder)) {
      _mkdirp2.default.sync(folder);
    }
    return folder;
  }

  getSocketZip() {
    debug('getSocketZip');
    return _path2.default.join(_session2.default.getDistPath(), `${this.name}.zip`);
  }

  getSocketEnvZip() {
    debug('getSocketEnvZip');
    return _path2.default.join(_session2.default.getDistPath(), `${this.name}.env.zip`);
  }

  getSocketNodeModulesChecksum() {
    debug('getSocketNodeModulesChecksum');
    return _hashdirectory2.default.sync(_path2.default.join(this.socketPath, '.dist', 'node_modules'));
  }

  getSocketConfigFile() {
    return _path2.default.join(_session2.default.projectPath, this.name, 'socket.yml');
  }

  composeFromSpec(objectType, ObjectClass) {
    debug('composeFromSpec', objectType, ObjectClass);
    const objects = Object.assign({}, this.remote.spec[objectType]);
    Object.assign(objects, this.spec[objectType]);

    debug('objects to process', objects);
    return Object.keys(objects).map(objectName => {
      debug(`checking ${objectName}`);
      const objectMetadata = objects[objectName];
      debug('objectMetadata', objectMetadata);
      const object = new ObjectClass(objectName, objectMetadata, this.name);

      debug('existRemotely', this.remote.spec[objectType], objectName);
      if (this.remote.spec[objectType][objectName]) {
        debug(`existRemotely: ${true}`);
        object.existRemotely = true;
      }

      debug('existLocally', this.spec[objectType], objectName);
      if (this.spec[objectType][objectName]) {
        object.existLocally = true;
      }
      return object;
    });
  }

  getEndpoints() {
    debug('getEndpoints');
    return this.composeFromSpec('endpoints', Endpoint);
  }

  getEndpoint(endpointName) {
    debug('getEndpoints');
    return _lodash2.default.find(this.getEndpoints(), { name: endpointName });
  }

  getEventHandlers() {
    debug('getEventHandlers');
    return this.composeFromSpec('event_handlers', Handler);
  }

  getEvents() {
    debug('getEvents');
    return this.composeFromSpec('events', Event);
  }

  getEndpointTrace(endpointName, traceId) {
    return _session2.default.connection.EndpointTrace.please().get({ socketName: this.name, endpointName, id: traceId });
  }

  getEndpointTraces(endpointName, lastId) {
    debug('getEndpointTraces', endpointName, lastId);
    return _session2.default.connection.EndpointTrace.please().list({ socketName: this.name, endpointName }).then(traces => {
      if (!lastId) {
        return traces;
      }
      const filteredTraces = [];
      traces.forEach(trace => {
        if (trace.id > lastId) {
          filteredTraces.push(trace);
        }
      });
      return filteredTraces;
    });
  }

  getTraces(lastId) {
    const params = { room: `socket:${this.name}` };
    if (lastId) {
      params.last_id = lastId;
    }

    return _axios2.default.request({
      url: `https://${_session2.default.getHost()}/v2/instances/${_session2.default.project.instance}/channels/eventlog/poll/`,
      method: 'GET',
      timeout: 50000,
      params,
      headers: {
        'X-Api-Key': _session2.default.settings.account.getAuthKey()
      }
    });
  }

  static getEndpointTraceByUrl(url) {
    return _axios2.default.request({
      url: `https://${_session2.default.getHost()}${url}`,
      method: 'GET',
      headers: {
        'X-Api-Key': _session2.default.settings.account.getAuthKey()
      }
    }).then(resp => resp.data);
  }

  createZip({ plainSources = false } = {}) {
    debug('createZip');
    return new _bluebird2.default((resolve, reject) => {
      let numberOfFiles = 0;

      const archive = (0, _archiver2.default)('zip', { zlib: { level: 9 } });
      const output = _fsExtra2.default.createWriteStream(this.getSocketZip(), { mode: 0o700 });

      archive.pipe(output);
      archive.on('error', reject);

      function fileFilter(file) {
        const remoteFiles = this.remote.files;
        if (file.fullPath.match(/yarn\.lock/)) {
          return false;
        }
        if (file.fullPath.match(/.*\.log$/)) {
          return false;
        }
        if (file.fullPath.match(/node_modules/)) {
          return false;
        }
        if (file.fullPath.match(/\.js\.map$/)) {
          return false;
        }
        if (!plainSources && file.fullPath.match(/\.js$/)) {
          return false;
        }
        if (file.fullPath.match(/\.js$/)) {
          return false;
        }
        if (file.path.match(/^\./)) {
          return false;
        }
        debug(`createZip: Checking file ${file.name}`);
        if (remoteFiles) {
          const remoteFile = remoteFiles[file.path];
          if (remoteFile) {
            return remoteFile.checksum !== (0, _md2.default)(_fsExtra2.default.readFileSync(file.fullPath));
          }
          return true;
        }
        return true;
      }

      const findFiles = (0, _readdirp2.default)({ root: this.getSrcFolder(), fileFilter: fileFilter.bind(this) });

      // Adding socket.yml if needed
      const localYMLChecksum = (0, _md2.default)(_fsExtra2.default.readFileSync(this.getSocketYMLFile()));
      const remoteYMLChecksum = this.remote.files && this.remote.files['socket.yml'] ? this.remote.files['socket.yml'].checksum : '';
      if (remoteYMLChecksum !== localYMLChecksum) {
        debug('Adding file to archive: \'socket.yml\'');
        archive.file(this.getSocketYMLFile(), { name: 'socket.yml' });
        numberOfFiles += 1;
      } else {
        debug('Ignoring file: socket.yml');
      }

      // Adding all files (besides those filtered out)
      findFiles.on('data', file => {
        // with "internal" path
        const fileNameWithPath = file.fullPath.replace(`${this.getSrcFolder()}`, '');
        debug(`Adding file to archive: ${fileNameWithPath}`);
        archive.file(file.fullPath, { name: fileNameWithPath });
        numberOfFiles += 1;
      });

      findFiles.on('end', () => {
        if (!plainSources) {
          return this.getScriptsInSocket().then(scripts => {
            scripts.forEach(script => {
              const scriptNameWithPath = script.srcFile.replace(`${this.getSrcFolder()}`, '');
              const scriptBundlePath = script.compiledFile;
              const remoteFile = this.remote.files ? this.remote.files[scriptNameWithPath] : null;

              if (remoteFile) {
                if (!_fsExtra2.default.existsSync(scriptBundlePath)) {
                  return;
                }
                const scriptLocalChecksum = (0, _md2.default)(_fsExtra2.default.readFileSync(scriptBundlePath));
                const scriptRemoteChecksum = remoteFile.checksum;
                if (scriptLocalChecksum === scriptRemoteChecksum) {
                  debug('Ignoring file:', scriptNameWithPath);
                  return;
                }
              }

              debug(`Adding file to archive: ${scriptNameWithPath}`);
              archive.file(scriptBundlePath, { name: scriptNameWithPath });
              numberOfFiles += 1;
            });
          }).then(() => {
            debug(`createZip: all files processed: ${numberOfFiles}`);
            archive.finalize();
          });
        }
        archive.finalize();
      });

      output.on('close', () => {
        resolve(numberOfFiles);
      });
    });
  }

  createPackageZip() {
    debug('createPackageZip');
    return new _bluebird2.default((resolve, reject) => {
      const archive = (0, _archiver2.default)('zip', { zlib: { level: 9 } });
      const output = _fsExtra2.default.createWriteStream(this.getSocketZip(), { mode: 0o700 });

      archive.pipe(output);
      archive.on('error', reject);

      archive.file(this.getSocketYMLFile(), { name: 'socket.yml' });
      archive.directory(this.getSrcFolder(), 'src');
      archive.finalize();

      output.on('close', () => {
        resolve();
      });
    });
  }

  createEnvZip() {
    debug('createZip');
    return new _bluebird2.default((resolve, reject) => {
      const output = _fsExtra2.default.createWriteStream(this.getSocketEnvZip(), { mode: 0o700 });
      const archive = (0, _archiver2.default)('zip', { zlib: { level: 9 } });

      archive.pipe(output);
      archive.on('error', reject);
      archive.directory(_path2.default.join(this.getSocketPath(), '.dist', 'node_modules'), 'node_modules');

      archive.finalize();
      output.on('close', () => {
        resolve();
      });
    });
  }

  updateEnvCall(method) {
    return new _bluebird2.default((resolve, reject) => {
      const form = new _formData2.default();

      let endpointPath = `/v2/instances/${_session2.default.project.instance}/environments/`;
      if (method === 'PATCH') {
        endpointPath = `/v2/instances/${_session2.default.project.instance}/environments/${this.name}/`;
      }

      debug('endpointPath', endpointPath);
      form.append('name', this.name);
      form.append('metadata', JSON.stringify({ checksum: this.getSocketNodeModulesChecksum() }));
      form.append('zip_file', _fsExtra2.default.createReadStream(this.getSocketEnvZip()));
      debug('upload env zip');
      form.submit({
        method,
        protocol: 'https:',
        host: _session2.default.getHost(),
        headers: {
          'X-Api-Key': _session2.default.settings.account.getAuthKey()
        },
        path: endpointPath

      }, (err, res) => {
        debug('end env upload');
        if (res.statusCode === 200) {
          resolve();
        }
        res.on('data', data => {
          const message = data.toString();

          if (err || res.statusCode === 404) {
            debug(`environment ${this.name} was not found`);
            return reject(err || res);
          }

          if (res.statusCode > 299) {
            debug(`error while updating environment (${res.statusCode})`);
            return reject(message);
          }

          debug(`environment ${this.name} was found`);
          resolve(message);
        });
      });
    });
  }

  updateEnv() {
    debug('updateEnv');
    return this.socketEnvShouldBeUpdated().then(resp => {
      if (resp) {
        return this.createEnvZip().then(() => this.updateEnvCall(resp));
      }
      return _bluebird2.default.resolve('No need to update');
    });
  }

  postSocketZip(config) {
    return this.zipCallback({ config, install: true });
  }

  patchSocketZip(config) {
    return this.zipCallback({ config, install: false });
  }

  zipCallback({ config, install = false }) {
    debug('zipCallback');
    let endpointPath = `/v2/instances/${_session2.default.project.instance}/sockets/`;

    if (!install) {
      endpointPath += `${this.name}/`;
    }

    return this.createZip().then(numberOfFiles => {
      if (numberOfFiles === 0 && this.isConfigSynced) {
        debug('config is synced and nothing to update');
        return _bluebird2.default.resolve();
      }

      return new _bluebird2.default((resolve, reject) => {
        const form = new _formData2.default();

        form.append('name', this.name);
        form.append('environment', this.name);

        if (config) {
          form.append('config', JSON.stringify(config));
        }

        const metadata = Object.assign({}, this.remote.metadata, { sources: this.getSocketSrcChecksum() });
        form.append('metadata', JSON.stringify(metadata));

        if (numberOfFiles > 0) {
          form.append('zip_file', _fsExtra2.default.createReadStream(this.getSocketZip()));
        }

        form.submit({
          method: install ? 'POST' : 'PATCH',
          protocol: 'https:',
          host: _session2.default.getHost(),
          headers: {
            'X-Api-Key': _session2.default.settings.account.getAuthKey()
          },
          path: endpointPath

        }, (err, res) => {
          debug('end upload');
          let responseData = '';
          let responseCode = '';
          res.on('data', data => {
            responseData += data.toString();
            responseCode = res.statusCode;
          });
          res.on('end', () => {
            if (err || responseCode === 404) {
              debug(`socket ${this.name} was not found`);
              return reject(err || res);
            }

            if (responseCode > 299) {
              debug(`error while updating socket (${res.statusCode})`);
              return reject(responseData);
            }

            debug(`socket ${this.name} was found`);
            resolve(responseData);
          });
        });
      });
    });
  }

  getSocketPath() {
    if (this.isDependencySocket || this.isProjectRegistryDependency) {
      const socketFolder = _path2.default.join(_session2.default.getBuildPath(), this.name);
      _mkdirp2.default.sync(socketFolder);
      return socketFolder;
    }
    return this.socketPath;
  }

  getSocketYMLFile() {
    return _path2.default.join(this.getSocketPath(), 'socket.yml');
  }

  preCompileRegistrySocket() {
    return _registry2.default.getSocket(this).then(resp => {
      const fileName = _path2.default.join(_session2.default.getBuildPath(), `${this.name}.zip`);

      return new _bluebird2.default((resolve, reject) => {
        _fsExtra2.default.createReadStream(fileName).pipe(_unzip2.default.Extract({ path: this.getSocketPath() })).on('close', () => {
          debug('Unzip finished');
          resolve(_utils2.default.updateSocketNPMDeps(this.getSocketPath()));
        });
      });
    });
  }

  compile(params = { updateSocketNPMDeps: false }) {
    debug(`compile: ${this.name}`);
    debug(`compile socketPath: ${this.getSocketPath()}`);

    let compilation = null;
    if (params.updateSocketNPMDeps) {
      if (this.isDependencySocket || this.isProjectRegistryDependency) {
        compilation = this.preCompileRegistrySocket();
      } else {
        compilation = _utils2.default.updateSocketNPMDeps(this.getSocketPath());
      }
    } else {
      compilation = _bluebird2.default.resolve();
    }

    return compilation.then(updateSocketDepsStatus => {
      debug('updateSocketDepsStatus', updateSocketDepsStatus);
      return (0, _compile.compile)([this], params.withSourceMaps);
    });
  }

  isConfigSynced(config) {
    debug('isConfigSynced');
    return _lodash2.default.isEqual(config, this.remote.config);
  }

  updateConfig(config) {
    if (this.isConfigSynced(config)) {
      return _bluebird2.default.resolve();
    }
    return _axios2.default.request({
      url: `https://${_session2.default.getHost()}/v2/instances/${_session2.default.project.instance}/sockets/${this.name}/`,
      method: 'PATCH',
      data: { config },
      headers: {
        'X-Api-Key': _session2.default.settings.account.getAuthKey()
      }
    });
  }

  update(params = { config: null, updateSocketNPMDeps: false, updateEnv: false }) {
    debug(`Socket update: ${this.name}`, params);
    const config = Object.assign({}, this.remote.config, params.config);

    // Get options from the env
    if (this.spec.config) {
      Object.keys(this.spec.config).forEach(optionName => {
        const envValue = this.getConfigOptionFromEnv(optionName);
        if (envValue) {
          debug(`setting value from env for: ${optionName}`);
          config[optionName] = envValue;
        }
      });
    }

    return this.verify().then(() => this.compile({ updateSocketNPMDeps: params.updateSocketNPMDeps })).then(() => {
      if (params.updateEnv) {
        return this.updateEnv();
      }
      return _bluebird2.default.resolve();
    }).then(() => {
      if (this.existRemotely) {
        return this.patchSocketZip(config);
      }
      return this.postSocketZip(config);
    }).then(resp => {
      if (resp && resp.status !== 'ok') {
        return this.waitForStatusInfo();
      }
      return _bluebird2.default.resolve({ status: 'stopped' });
    });
  }

  waitForStatusInfo() {
    debug('waitForStatusInfo');

    return new _bluebird2.default((resolve, reject) => {
      const getStatus = () => {
        this.getRemote().then(socket => {
          if (socket.status !== 'ok' && socket.status !== 'error') {
            setTimeout(getStatus, 200);
          } else {
            this.setRemoteState(socket);
            resolve({ status: socket.status, message: socket.status_info });
          }
        });
      };
      getStatus();
    });
  }

  // Hosting
  addHosting(hostingName, params) {
    this.settings.addHosting(hostingName, params);
    this.settings.save();
  }

  getHosting(hostingName) {
    return _hosting2.default.get(this, hostingName);
  }

  listHostings() {
    return _hosting2.default.list(this);
  }

  deleteHosting(hostingName) {
    this.settings.deleteHosting(hostingName);
    this.settings.save();
    (0, _printTools.echo)();
    (0, _printTools.echo)(4)(`Hosting ${hostingName} of ${this.name} has been deleted from config...`);
  }

  getScriptObject(fileFullPath) {
    const srcFile = fileFullPath;
    const compiledFile = fileFullPath.replace(this.getSrcFolder(), this.getCompiledScriptsFolder());
    return {
      srcFile,
      compiledFile
    };
  }

  getScriptsInSocket() {
    debug('getScriptsInSocket');

    return new _bluebird2.default((resolve, reject) => {
      function fileFilter(file) {
        const fileWithLocalPath = file.fullPath.replace(`${this.getSocketPath()}${_path2.default.sep}`, '');
        if (fileWithLocalPath.match(/test/)) {
          return false;
        }
        if (fileWithLocalPath.match(/tests/)) {
          return false;
        }
        if (fileWithLocalPath.match(/\.bundles/)) {
          return false;
        }
        if (fileWithLocalPath.match(/node_modules/)) {
          return false;
        }
        if (!fileWithLocalPath.match(/\.js$/)) {
          return false;
        }
        return true;
      }

      const findFiles = (0, _readdirp2.default)({ root: this.getSrcFolder(), fileFilter: fileFilter.bind(this) });
      const files = [];
      // Adding all files (besides those filtered out)
      findFiles.on('data', file => {
        files.push(this.getScriptObject(file.fullPath));
      });

      findFiles.on('end', () => {
        resolve(files);
      });
    });
  }

  getScriptsToCompile() {
    debug('getScriptsToCompile');

    return this.getScriptsInSocket().then(files => {
      const filesToCompile = [];
      files.forEach(file => {
        const fileNameWithLocalPath = file.srcFile.replace(this.getSrcFolder(), '');
        const localSrcChecksum = (0, _md2.default)(_fsExtra2.default.readFileSync(file.srcFile, 'utf8'));
        const remoteSrcChecksum = this.remote.metadata.sources ? this.remote.metadata.sources[fileNameWithLocalPath] : '';

        debug(localSrcChecksum, remoteSrcChecksum, fileNameWithLocalPath);
        if (localSrcChecksum !== remoteSrcChecksum) {
          filesToCompile.push(file);
        }
      });
      return filesToCompile;
    });
  }

  getFileForEndpoint(endpointName) {
    if (endpointName.startsWith('events')) {
      return this.spec.event_handlers[endpointName].file;
    }
    return this.spec.endpoints[endpointName].file;
  }

  getSourceMapPath(endpointName) {
    const filePath = this.getFileForEndpoint(endpointName);
    const { base, dir } = _path2.default.parse(filePath);
    return _path2.default.join(this.getSocketPath(), dir, '.bundles', `${base}.map`);
  }

  getOrigFileLine(traceData, endpointName) {
    const smc = new _sourceMap2.default.SourceMapConsumer(_fsExtra2.default.readFileSync(this.getSourceMapPath(endpointName), { encoding: 'utf-8' }));
    return smc.originalPositionFor({
      line: traceData.lineNumber,
      column: traceData.columnNumber
    });
  }

  getPrettyTrace(traceData, endpointName) {
    const origFileLine = this.getOrigFileLine(traceData, endpointName);
    const origFilePath = _utils2.default.getOrigFilePath(origFileLine);

    return {
      origFilePath,
      lineNumber: origFileLine.line,
      columnNumber: origFileLine.column,
      lines: [_fsExtra2.default.readFileSync(
      // origFilePath,
      _path2.default.join(this.getSocketPath(), '../', origFilePath), { encoding: 'utf-8' }).split('\n')[origFileLine.line - 1], (0, _printTools.p)(origFileLine.column)('^')]
    };
  }

  // Config
  getConfigOptions() {
    return this.spec.config;
  }

  getConfigOptionFromEnv(optionName) {
    return process.env[`${this.name.toUpperCase()}_${optionName.toUpperCase()}`];
  }

  getConfigOptionsToAsk() {
    // If there is not options in spec it is always no options to ask
    if (!this.spec.config) {
      return {};
    }

    const options = {};

    if (this.isDependencySocket || this.isProjectRegistryDependency) {
      if (this.remote.status === 'ok') {
        return options;
      }
      Object.keys(this.spec.config).forEach(optionName => {
        const envValue = this.getConfigOptionFromEnv(optionName);
        const option = this.spec.config[optionName];
        if (option.required && !envValue) {
          options[optionName] = option;
        }
      });
      return options;
    }

    if (this.existLocally) {
      Object.keys(this.spec.config).forEach(optionName => {
        const envValue = this.getConfigOptionFromEnv(optionName);
        const option = this.spec.config[optionName];
        if (option.required && !envValue) {
          if (!this.remote.config || !this.remote.config[optionName]) {
            options[optionName] = option;
          }
        }
      });
      return options;
    }
  }

  // Registry
  bumpVersion(bumpType) {
    const nextVersion = this.settings.bumpVersion(bumpType);
    this.spec.version = nextVersion;
    return nextVersion;
  }

  submit() {
    debug('submit');
    const registry = new _registry2.default();
    return registry.submitSocket(this);
  }

  // Socket dependencies
  getDeps() {
    return this.settings.getDependencies();
  }

  getDepsRegistrySockets() {
    debug(`getDepsRegistrySockets for: ${this.name}`);
    const registry = new _registry2.default();

    const getDeepDeps = (deps, socketToAdd) => {
      debug('getDeepDeps', deps);
      const sockets = [];
      if (socketToAdd) {
        sockets.push(socketToAdd);
      }
      return _bluebird2.default.all(sockets.concat(Object.keys(deps).map(dependencyName => {
        debug(`processing dependency: ${dependencyName}`);
        return registry.searchSocketByName(dependencyName, deps[dependencyName].version).then(socket => {
          // To keep track of the name of the parent
          // eslint-disable-next-line
          socket.dependencyOf = socketToAdd ? socketToAdd.name : this.name;

          if (!_lodash2.default.isEmpty(socket.config.dependencies)) {
            return getDeepDeps(socket.config.dependencies, socket);
          }
          debug(`returning socket without dependencies ${socket.name}`);
          return _bluebird2.default.resolve(socket);
        });
      })));
    };
    debug(`Defined dependencies of "${this.name}" to follow: ${this.spec.dependencies}`);
    if (this.spec.dependencies) {
      return getDeepDeps(this.spec.dependencies).then(arr => _bluebird2.default.all(_lodash2.default.flatten(arr).map(socket => Socket.get(socket.name).then(createdSocket => {
        // eslint-disable-next-line
        createdSocket.isDependencySocket = true;
        // eslint-disable-next-line
        createdSocket.dependencyOf = [socket.dependencyOf];
        return createdSocket;
      })))).then(depsObjects => {
        this.dependencies = depsObjects;
      });
    }

    return _bluebird2.default.resolve();
  }

  addDependency(socketFromRegistry) {
    const socketName = socketFromRegistry.name;
    const socketVersion = socketFromRegistry.version;
    this.settings.addDependency(socketName, socketVersion);
    return new _bluebird2.default((resolve, reject) => resolve());
  }

  socketEnvShouldBeUpdated() {
    debug('socketEnvShouldBeUpdated');
    return _axios2.default.request({
      url: `https://${_session2.default.getHost()}/v2/instances/${_session2.default.project.instance}/environments/${this.name}/`,
      method: 'GET',
      timeout: 50000,
      headers: {
        'X-Api-Key': _session2.default.settings.account.getAuthKey()
      }
    }).then(resp => {
      if (resp.data.metadata.checksum === this.getSocketNodeModulesChecksum()) {
        debug('socketEnvShouldBeUpdated', 'env is up to date');
        return false;
      }
      return 'PATCH';
    }).catch(err => 'POST');
  }

  getSocketSrcChecksum() {
    const files = _fsExtra2.default.walkSync(this.getSrcFolder());
    const checksums = {};
    files.forEach(file => {
      const fileReltivePath = file.replace(this.getSrcFolder(), '');
      checksums[fileReltivePath] = (0, _md2.default)(_fsExtra2.default.readFileSync(file, 'utf8'));
    });
    return checksums;
  }

  shouldBeUpdated() {
    debug('shouldBeUpdated');
    if (this.existLocally && this.existRemotely) {
      const files = this.remote.files;
      return Object.keys(files).some(file => {
        let filePath;

        if (file === 'socket.yml') {
          filePath = this.getSocketYMLFile();
        } else if (file.endsWith('.js')) {
          filePath = _path2.default.join(this.getCompiledScriptsFolder(), file);
        } else {
          filePath = _path2.default.join(this.getSrcFolder(), file);
        }

        const remoteChecksum = files[file].checksum;
        const localChecksum = (0, _md2.default)(_fsExtra2.default.readFileSync(filePath));

        debug(file);
        debug(localChecksum);
        debug(remoteChecksum);
        return localChecksum !== remoteChecksum;
      });
    } else if (!this.existRemotely) {
      return true;
    } else if (this.isDependencySocket || this.isProjectRegistryDependency) {
      if (this.remote.status !== 'ok') {
        return false;
      }

      debug(`Spec version: ${this.spec.version}`);
      debug(`Remote version: ${this.getVersion()}`);
      if (this.spec.version === this.getVersion()) {
        return false;
      }
    }
  }
}

Socket.listLocal = _utils2.default.listLocal;

exports.default = Socket;
module.exports = exports['default'];
//# sourceMappingURL=sockets.js.map