'use strict';

// //////////////////////////////////////////////////////////////////////////////
// / DISCLAIMER
// /
// / Copyright 2014-2024 ArangoDB GmbH, Cologne, Germany
// / Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
// /
// / Licensed under the Business Source License 1.1 (the "License");
// / you may not use this file except in compliance with the License.
// / You may obtain a copy of the License at
// /
// /     https://github.com/arangodb/arangodb/blob/devel/LICENSE
// /
// / 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.
// /
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
// /
// //////////////////////////////////////////////////////////////////////////////

var fs = require('fs');
var arangodb = require('@arangodb');
var db = arangodb.db;
var internal = require('internal');

var errors = arangodb.errors;
var ArangoError = arangodb.ArangoError;
var mountRegEx = /^(\/[a-zA-Z0-9_\-%]+)+$/;
var mountAppRegEx = /\/APP(\/|$)/i;
var mountNumberRegEx = /^\/[\d\-%]/;
var pathRegex = /^((\.{0,2}(\/|\\))|(~\/)|[a-zA-Z]:\\)/;

const DEFAULT_REPLICATION_FACTOR_SYSTEM = internal.DEFAULT_REPLICATION_FACTOR_SYSTEM;

function getReadableName (name) {
  const readable = name
  .replace(/([-_]|\s)+/g, ' ')
  .replace(/[a-z][A-Z]/g, (str) => `${str.charAt(0)} ${str.slice(1)}`)
  .replace(/[A-Z][A-Z][a-z]/g, (str) => `${str.charAt(0)} ${str.slice(1)}`)
  .replace(/\s[a-z]/g, (str) => `${str.toUpperCase()}`);
  return readable.charAt(0).toUpperCase() + readable.substr(1);
}

function getStorage () {
  return db._collection('_apps');
}

function getBundleStorage () {
  return db._collection('_appbundles');
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief comparator for mount points
// //////////////////////////////////////////////////////////////////////////////

function compareMounts (l, r) {
  var left = l.mount.toLowerCase();
  var right = r.mount.toLowerCase();

  if (left < right) {
    return -1;
  }
  return 1;
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief builds a github repository URL
// //////////////////////////////////////////////////////////////////////////////

function buildGithubUrl (repository, version) {
  if (version === undefined) {
    version = 'master';
  }

  var urlPrefix = require('process').env.FOXX_BASE_URL;
  if (urlPrefix === undefined) {
    urlPrefix = 'https://github.com/';
  }
  return urlPrefix + repository + '/archive/' + version + '.zip';
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief prints all running Foxx applications
// //////////////////////////////////////////////////////////////////////////////

function list (onlyDevelopment) {
  var services = getStorage().toArray().map((definition) => ({
    mount: definition.mount,
    development: definition.options.development
  }));

  arangodb.printTable(
    services.sort(compareMounts),
    [ 'mount', 'development' ],
    {
      prettyStrings: true,
      totalString: '%s service(s) found',
      emptyString: 'no services found',
      rename: {
        'mount': 'Mount',
        'development': 'Development'
      }
    }
  );
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief prints all running Foxx applications
// //////////////////////////////////////////////////////////////////////////////

function listDevelopment () {
  return list(true);
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief validate the mount point of an app
// //////////////////////////////////////////////////////////////////////////////

function validateMount (mount, internal) {
  if (mount[0] !== '/') {
    throw new ArangoError({
      errorNum: errors.ERROR_INVALID_MOUNTPOINT.code,
      errorMessage: 'Mount point has to start with /.'
    });
  }
  if (!mountRegEx.test(mount)) {
    // Controller routes may be /. Foxxes are not allowed to
    if (!internal || mount.length !== 1) {
      throw new ArangoError({
        errorNum: errors.ERROR_INVALID_MOUNTPOINT.code,
        errorMessage: 'Mount point can only contain a-z, A-Z, 0-9 or _.'
      });
    }
  }
  if (!internal) {
    // routes starting with _ are disallowed...
    // ...except they start with _open. the _open prefix provides a non-authenticated
    // way to access routes
    if (mount[1] === '_' && !/^\/_open\//.test(mount)) {
      throw new ArangoError({
        errorNum: errors.ERROR_INVALID_MOUNTPOINT.code,
        errorMessage: '/_ apps are reserved for internal use.'
      });
    }
    if (mountNumberRegEx.test(mount)) {
      throw new ArangoError({
        errorNum: errors.ERROR_INVALID_MOUNTPOINT.code,
        errorMessage: 'Mount points are not allowed to start with a number, - or %.'
      });
    }
    if (mountAppRegEx.test(mount)) {
      throw new ArangoError({
        errorNum: errors.ERROR_INVALID_MOUNTPOINT.code,
        errorMessage: 'Mount point is not allowed to contain /app/.'
      });
    }
  }
  return true;
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief get the app installed at this mount point
// //////////////////////////////////////////////////////////////////////////////

function getServiceDefinition (mount) {
  return getStorage().firstExample({mount});
}

// //////////////////////////////////////////////////////////////////////////////
// / @brief Update the app installed at this mount point with the new app
// //////////////////////////////////////////////////////////////////////////////

function updateService (mount, update) {
  return getStorage().replaceByExample({mount}, update);
}


// //////////////////////////////////////////////////////////////////////////////
// / @brief Exports
// //////////////////////////////////////////////////////////////////////////////

exports.getServiceDefinition = getServiceDefinition;
exports.updateService = updateService;
exports.getReadableName = getReadableName;
exports.list = list;
exports.listDevelopment = listDevelopment;
exports.buildGithubUrl = buildGithubUrl;
exports.validateMount = validateMount;
exports.getStorage = getStorage;
exports.getBundleStorage = getBundleStorage;
exports.pathRegex = pathRegex;
exports.throwFileNotFound = fs.throwFileNotFound;
exports.zipDirectory = fs.zipDirectory;
exports.joinLastPath = fs.joinLastPath;
