/*
 * Copyright (c) 2017, 2023, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2.0,
 * as published by the Free Software Foundation.
 *
 * This program is also distributed with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms, as
 * designated in a particular file or component or in included license
 * documentation.  The authors of MySQL hereby grant you an additional
 * permission to link the program and your derivative works with the
 * separately licensed software that they have included with MySQL.
 * This program is distributed in the hope that it will be useful,  but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
 * the GNU General Public License, version 2.0, for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */
#include "mysqlshdk/libs/db/connection_options.h"
#include <algorithm>
#include <cassert>
#include "mysqlshdk/include/shellcore/shell_options.h"
#include "mysqlshdk/libs/db/uri_encoder.h"
#include "mysqlshdk/libs/db/uri_parser.h"
#include "mysqlshdk/libs/db/utils_connection.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#include "mysqlshdk/libs/utils/utils_net.h"
#include "mysqlshdk/libs/utils/utils_string.h"

using mysqlshdk::db::uri::Uri_encoder;
using mysqlshdk::utils::nullable_options::Comparison_mode;
using mysqlshdk::utils::nullable_options::Set_mode;

namespace mysqlshdk {
namespace db {

std::string to_string(Transport_type type) {
  switch (type) {
    case Transport_type::Tcp:
      return "TCP/IP";
    case Transport_type::Socket:
      return "Unix socket";
    case Transport_type::Pipe:
      return "Pipe";
  }
  return "Unknown";
}

Connection_options::Connection_options(Comparison_mode mode)
    : IConnection("connection", mode),
      m_ssl_options(m_mode),
      m_ssh_options(m_mode),
      m_extra_options(m_mode),
      m_enable_connection_attributes(true),
      m_connection_attributes(m_mode) {
  for (auto o : {kSocket, kPipe}) m_options.set(o, nullptr, Set_mode::CREATE);
}

Connection_options::Connection_options(const std::string &uri,
                                       Comparison_mode mode)
    : Connection_options(mode) {
  try {
    uri::Uri_parser parser(mysqlshdk::db::uri::Type::DevApi);
    *this = parser.parse(uri, m_options.get_mode());
  } catch (const std::invalid_argument &error) {
    std::string msg = "Invalid URI: ";
    msg.append(error.what());
    throw std::invalid_argument(msg);
  }
}

void Connection_options::override_with(const Connection_options &options) {
  m_options.override_from(options.m_options, true);
  m_extra_options.override_from(options.m_extra_options, true);
  m_ssl_options.override_from(options.m_ssl_options, true);
  m_connection_attributes.override_from(options.m_connection_attributes, true);
  m_enable_connection_attributes = options.m_enable_connection_attributes;

  if (options.has_compression_level()) {
    clear_compression_level();
    set_compression_level(options.get_compression_level());
  }

  if (options.has_port()) {
    clear_port();
    set_port(options.get_port());
  } else {
    // "default" port should also override old value
    clear_port();
  }

  if (options.has_transport_type()) {
    m_transport_type = options.get_transport_type();
  } else {
    m_transport_type.reset();
  }

  for (int i = 0; i < 3; i++) {
    if (options.m_mfa_passwords[i].has_value())
      set_mfa_password(i, *options.m_mfa_passwords[i]);
  }
}

void Connection_options::set_login_options_from(
    const Connection_options &options) {
  clear_user();
  if (options.has_user()) {
    set_user(options.get_user());
  }
  clear_password();
  if (options.has_password()) {
    set_password(options.get_password());
  }
  clear_mfa_passwords();
  if (options.has_mfa_passwords()) {
    set_mfa_passwords(options.get_mfa_passwords());
  }

  m_ssl_options.clear_ca();
  m_ssl_options.clear_capath();
  m_ssl_options.clear_cert();
  m_ssl_options.clear_cipher();
  m_ssl_options.clear_key();
  m_ssl_options.clear_crl();
  m_ssl_options.clear_crlpath();
  m_ssl_options.clear_tls_version();
  m_ssl_options.clear_tls_ciphersuites();
  const Ssl_options &ssl = options.get_ssl_options();
  if (ssl.has_ca()) m_ssl_options.set_ca(ssl.get_ca());
  if (ssl.has_capath()) m_ssl_options.set_capath(ssl.get_capath());
  if (ssl.has_cert()) m_ssl_options.set_cert(ssl.get_cert());
  if (ssl.has_cipher()) m_ssl_options.set_cipher(ssl.get_cipher());
  if (ssl.has_key()) m_ssl_options.set_key(ssl.get_key());
  if (ssl.has_crl()) m_ssl_options.set_crl(ssl.get_crl());
  if (ssl.has_crlpath()) m_ssl_options.set_crlpath(ssl.get_crlpath());
  if (ssl.has_tls_version())
    m_ssl_options.set_tls_version(ssl.get_tls_version());
  if (ssl.has_tls_ciphersuites())
    m_ssl_options.set_tls_ciphersuites(ssl.get_tls_ciphersuites());
}

void Connection_options::set_ssl_options(const Ssl_options &options) {
  m_ssl_options = options;
}

void Connection_options::set_password(const std::string &password) {
  IConnection::set_password(password);
  m_mfa_passwords[0] = password;
}

void Connection_options::set_mfa_password(int factor,
                                          const std::string &password) {
  if (factor < 0 || factor >= 3)
    throw std::invalid_argument("invalid factor #");
  if (factor == 0) set_password(password);
  m_mfa_passwords[factor] = password;
}

void Connection_options::set_mfa_passwords(const Mfa_passwords &mfa_passwords) {
  m_mfa_passwords = mfa_passwords;
  if (mfa_passwords[0].has_value()) set_password(*mfa_passwords[0]);
}

const std::string &Connection_options::get_password() const {
  return get_value(kPassword);
}

void Connection_options::clear_password() {
  IConnection::clear_password();
  m_mfa_passwords[0].reset();
}

void Connection_options::set_ssh_options(
    const mysqlshdk::ssh::Ssh_connection_options &options) {
  m_ssh_options = options;
}

ssh::Ssh_connection_options &Connection_options::get_ssh_options_handle(
    int fallback_port) {
  if (m_ssh_options.has_data()) {
    int mysql_port = fallback_port;
    if (m_port.has_value()) {
      mysql_port = *m_port;
    } else if (has_scheme()) {
      if (get_scheme() == "mysql") {
        mysql_port = k_default_mysql_port;
      } else if (get_scheme() == "mysqlx") {
        mysql_port = k_default_mysql_x_port;
      }
    }

    if (has_host() && mysql_port != 0) {
      m_ssh_options.set_remote_host(get_host());

      m_ssh_options.clear_remote_port();
      m_ssh_options.set_remote_port(mysql_port);
    } else {
      throw std::invalid_argument(
          "Host and port for database are required in advance when using "
          "SSH tunneling functionality");
    }
  }

  return m_ssh_options;
}

bool Connection_options::has_data() const {
  return IConnection::has_data() || has_mfa_passwords() || has_port() ||
         has_value(kSocket) || m_ssh_options.has_data() ||
         m_ssl_options.has_data();
}

void Connection_options::_set_fixed(const std::string &key,
                                    const std::string &val) {
  m_options.set(key, val, Set_mode::CREATE_AND_UPDATE);
}

void Connection_options::set_pipe(const std::string &pipe) {
  if (m_options.has_value(kScheme) && get_value(kScheme) != "mysql") {
    throw std::invalid_argument{"Pipe can only be used with Classic session"};
  }

  m_options.set(kSocket, pipe, Set_mode::CREATE_AND_UPDATE);
  m_options.set(kPipe, pipe, Set_mode::CREATE_AND_UPDATE);
  m_transport_type = Pipe;
}

void Connection_options::check_compression_conflicts() {
  if (has_compression_algorithms() &&
      get_compression_algorithms() == "uncompressed" && has_compression() &&
      get_compression() == kCompressionRequired)
    throw std::invalid_argument(
        "Conflicting connection options: compression=REQUIRED, "
        "compression-algorithms=uncompressed.");
}

void Connection_options::set_compression(const std::string &compression) {
  if (has_compression())
    throw std::invalid_argument(std::string("Redefinition of '") +
                                kCompression + "' option");

  auto c = shcore::str_upper(shcore::str_strip(compression));
  if (c == "1" || c == "TRUE" || c == kCompressionRequired)
    m_extra_options.set(mysqlshdk::db::kCompression, kCompressionRequired);
  else if (c == "0" || c == "FALSE" || c == kCompressionDisabled)
    m_extra_options.set(mysqlshdk::db::kCompression, kCompressionDisabled);
  else if (c == kCompressionPreferred)
    m_extra_options.set(mysqlshdk::db::kCompression, kCompressionPreferred);
  else
    throw std::invalid_argument(
        "Invalid value '" + compression + "' for '" + kCompression +
        "'. Allowed values: '" + kCompressionRequired + "', '" +
        kCompressionPreferred + "', '" + kCompressionDisabled +
        "', 'True', 'False', '1', and '0'.");

  check_compression_conflicts();
}

void Connection_options::set_compression_algorithms(
    const std::string &compression_algorithms) {
  auto ca = shcore::str_lower(compression_algorithms);
  m_extra_options.set(kCompressionAlgorithms, ca);
  check_compression_conflicts();
}

void Connection_options::set_socket(const std::string &socket) {
  m_options.set(kSocket, socket, Set_mode::CREATE_AND_UPDATE);
  m_transport_type = Socket;
}

void Connection_options::set_host(const std::string &host) {
  m_options.set(kHost, host, Set_mode::CREATE_AND_UPDATE);

#ifdef _WIN32
  if (host == ".") {
    m_transport_type = Pipe;
    return;
  }
#endif  // _WIN32i
  if (host != "localhost")
    m_transport_type = Tcp;
  else if (!m_port.has_value())
    m_transport_type.reset();
}

void Connection_options::set_port(int port) {
  m_port = port;
  m_transport_type = Tcp;
}

std::string Connection_options::get_iname(const std::string &name) const {
  // Deprecated options are only supported as input
  // Internally, they are handled with replacements
  const auto it = deprecated_connection_attributes.find(name);
  if (it != deprecated_connection_attributes.end() && !it->second.empty())
    return it->second;
  return name;
}

bool Connection_options::is_extra_option(const std::string &option) {
  return std::find_if(uri_extra_options.begin(), uri_extra_options.end(),
                      [this, &option](
                          const decltype(uri_extra_options)::value_type &item) {
                        return m_options.compare(option, item) == 0;
                      }) != uri_extra_options.end();
}

bool Connection_options::is_bool_value(const std::string &value) {
  auto lower_case_value = shcore::str_lower(value);
  return lower_case_value == "true" || lower_case_value == "false" ||
         lower_case_value == "1" || lower_case_value == "0";
}

void Connection_options::set(const std::string &name,
                             const std::string &value) {
  const std::string iname = get_iname(name);
  if (name != iname)
    m_warnings.emplace_back("'" + name +
                            "' connection option is deprecated, use '" + iname +
                            "' option instead.");
  if (compare(iname, db::kScheme) == 0) {
    set_scheme(value);
  } else if (compare(iname, db::kUser) == 0) {
    set_user(value);
  } else if (compare(iname, db::kPassword) == 0) {
    set_password(value);
  } else if (compare(iname, db::kHost) == 0) {
    set_host(value);
  } else if (compare(iname, db::kPath) == 0) {
    set_path(value);
  } else if (compare(iname, db::kSocket) == 0) {
    set_socket(value);
  } else if (compare(iname, db::kPipe) == 0) {
    set_pipe(value);
  } else if (compare(iname, db::kSchema) == 0) {
    set_schema(value);
  } else if (m_ssl_options.has(iname)) {
    m_ssl_options.set(iname, value);
  } else if (iname == kCompression) {
    set_compression(value);
  } else if (iname == kCompressionAlgorithms) {
    set_compression_algorithms(value);
  } else if (is_extra_option(iname)) {
    if (iname == kGetServerPublicKey) {
      if (!is_bool_value(value)) {
        throw std::invalid_argument(
            shcore::str_format("Invalid value '%s' for '%s'. Allowed "
                               "values: true, false, 1, 0.",
                               value.c_str(), iname.c_str()));
      }
    } else if (iname == kConnectTimeout) {
      for (auto digit : value) {
        if (!std::isdigit(digit)) {
          throw_invalid_connect_timeout(value);
        }
      }
    }

    m_extra_options.set(iname, value, Set_mode::CREATE);
  } else if (iname == kConnectionAttributes) {
    // When connection-attributes has assigned a single value, it must
    // be a boolean. We do NOT do anything with it: in DevAPI standard
    // it should avoid ANY connection attribute to be sent, however
    // on the shell this behavior is not present, see WL12446 NOTES on FR10
    // for more details
    if (!value.empty() && !is_bool_value(value)) {
      throw std::invalid_argument(
          "The value of 'connection-attributes' must be either a boolean or a "
          "list of key-value pairs.");
    } else {
      auto lower_case_value = shcore::str_lower(value);
      if (lower_case_value == "0" || lower_case_value == "false") {
        m_enable_connection_attributes = false;
      }
    }
  } else if (iname == kCompressionLevel) {
    try {
      set_compression_level(shcore::lexical_cast<int>(value));
    } catch (...) {
      throw std::invalid_argument(std::string("The value of '") +
                                  kCompressionLevel + "' must be an integer.");
    }
  } else {
    throw std::invalid_argument("Invalid connection option '" + name + "'.");
  }
}

void Connection_options::set(const std::string &name, int value) {
  if (name == db::kPort) {
    set_port(value);
  } else if (name == db::kCompressionLevel) {
    set_compression_level(value);
  } else {
    throw std::invalid_argument("Invalid connection option '" + name + "'.");
  }
}

void Connection_options::set_unchecked(const std::string &name,
                                       const char *value) {
  m_extra_options.set(name, value);
}

void Connection_options::set(const std::string &name,
                             const std::vector<std::string> &values) {
  std::string iname = get_iname(name);

  if (name == kConnectionAttributes) {
    set_connection_attributes(values);
  } else if (shcore::str_caseeq(name, kSslTlsVersions)) {
    m_ssl_options.set_tls_version(
        shcore::str_join(values.begin(), values.end(), ","));
  } else if (shcore::str_caseeq(name, kSslTlsCiphersuites)) {
    m_ssl_options.set_tls_ciphersuites(
        shcore::str_join(values.begin(), values.end(), ":"));
  } else if (m_options.has(iname) || m_ssl_options.has(iname) ||
             is_extra_option(iname)) {
    // All the connection parameters accept only 1 value
    throw std::invalid_argument("The connection option '" + name +
                                "' requires exactly one value.");
  } else {
    throw std::invalid_argument("Invalid connection option '" + name + "'.");
  }
}

bool Connection_options::has(const std::string &name) const {
  std::string iname = get_iname(name);

  if (iname == db::kCompressionLevel) return has_compression_level();

  return m_options.has(iname) || m_ssl_options.has(iname) ||
         m_extra_options.has(iname) || m_options.compare(iname, kPort) == 0;
}

bool Connection_options::has_value(const std::string &name) const {
  std::string iname = get_iname(name);

  if (m_options.compare(iname, kSocket) == 0)
    return has_socket();
  else if (m_options.compare(iname, kPipe) == 0)
    return has_pipe();
  else if (m_options.has(iname))
    return m_options.has_value(iname);
  else if (m_ssl_options.has(iname))
    return m_ssl_options.has_value(iname);
  else if (m_options.compare(iname, kPort) == 0)
    return m_port.has_value();
  else if (m_extra_options.has(iname))
    return m_extra_options.has_value(iname);

  return false;
}

std::vector<std::pair<std::string, std::optional<std::string>>>
Connection_options::query_attributes() const {
  std::vector<std::pair<std::string, std::optional<std::string>>> attributes;

  // From SSL options, only the options with value are considered
  for (const auto &ssl_option : m_ssl_options) {
    if (ssl_option.second.has_value()) {
      attributes.push_back(ssl_option);
    }
  }

  // From the extra options, only the predefined list of attributes is
  // considered, with or without value
  for (const auto &option : m_extra_options) {
    if (uri_connection_attributes.find(option.first) !=
            uri_connection_attributes.end() ||
        uri_extra_options.find(option.first) != uri_extra_options.end()) {
      attributes.push_back(option);
    }
  }

  if (has_compression_level()) {
    attributes.push_back(
        {kCompressionLevel, std::to_string(get_compression_level())});
  }

  return attributes;
}

bool Connection_options::has_ssh_options() const {
  return m_ssh_options.has_data();
}

int Connection_options::get_port() const {
  if (!m_port.has_value()) {
    throw std::invalid_argument(
        shcore::str_format("The connection option '%s' has no value.", kPort));
  }

  return *m_port;
}

/**
 * This function is used to determine the port to be used to create the MySQL
 * connection, i.e if through SSH tunnels returns the tunnel local port,
 * otherwise the configured port in the connection data.
 */
int Connection_options::get_target_port() const {
  int port = 0;
  if (m_ssh_options.has_data()) {
    port = m_ssh_options.get_local_port();
  } else if (has_port()) {
    port = get_port();
  }
  return port;
}

void Connection_options::set_transport_type(Transport_type type) {
  m_transport_type = type;
}

Transport_type Connection_options::get_transport_type() const {
  if (!m_transport_type.has_value())
    throw std::invalid_argument("Transport Type is undefined.");

  return *m_transport_type;
}

int64_t Connection_options::get_compression_level() const {
  if (!m_compress_level.has_value())
    throw std::invalid_argument("Compression level is undefined.");
  return *m_compress_level;
}

const Mfa_passwords &Connection_options::get_mfa_passwords() const {
  if (has_password()) m_mfa_passwords[0] = get_password();
  return m_mfa_passwords;
}

const std::string &Connection_options::get(const std::string &name) const {
  std::string iname = get_iname(name);
  if (m_ssl_options.has(iname))
    return m_ssl_options.get_value(iname);
  else if (m_extra_options.has(iname))
    return m_extra_options.get_value(iname);
  else if (m_options.compare(iname, kSocket) == 0)
    return get_socket();
  else if (m_options.compare(iname, kPipe) == 0)
    return get_pipe();

  return get_value(name);
}

int Connection_options::get_numeric(const std::string &name) const {
  if (name == db::kPort) return get_port();
  if (name == db::kCompressionLevel) return get_compression_level();

  throw std::invalid_argument(
      shcore::str_format("Invalid URI property: %s", name.c_str()));
}

bool Connection_options::has_mfa_passwords() const {
  for (const auto &p : m_mfa_passwords) {
    if (p.has_value()) return true;
  }
  return has_password();
}

void Connection_options::clear_mfa_passwords() {
  clear_password();
  for (auto &p : m_mfa_passwords) {
    p.reset();
  }
}

void Connection_options::clear_host() {
  if (m_transport_type.has_value() &&
      *m_transport_type == Transport_type::Tcp && !m_port.has_value())
    m_transport_type.reset();

  clear_value(kHost);
}

void Connection_options::clear_port() {
  if (has_value(kHost) && get_value(kHost) == "localhost")
    m_transport_type.reset();

  m_port.reset();
}

void Connection_options::clear_socket() {
  m_transport_type.reset();
  clear_value(kSocket);
}

void Connection_options::clear_pipe() {
  m_transport_type.reset();
  clear_value(kSocket);
  clear_value(kPipe);
}

void Connection_options::remove(const std::string &name) {
  // Can't delete fixed values
  if (m_options.has(name))
    throw std::invalid_argument(
        "Unable to delete Connection option, use "
        "clear instead!!");

  if (m_ssl_options.has(name))
    throw std::invalid_argument(
        "Unable to delete SSL Connection option, use "
        "clear instead!!");

  m_extra_options.remove(name);
}

bool Connection_options::operator==(const Connection_options &other) const {
  return m_options == other.m_options && m_ssl_options == other.m_ssl_options &&
         m_extra_options == other.m_extra_options &&
         m_enable_connection_attributes ==
             other.m_enable_connection_attributes &&
         m_connection_attributes == other.m_connection_attributes &&
         m_port == other.m_port && m_transport_type == other.m_transport_type;
}

bool Connection_options::operator!=(const Connection_options &other) const {
  return !(*this == other);
}

mysqlsh::SessionType Connection_options::get_session_type() const {
  if (!has_scheme()) {
    return mysqlsh::SessionType::Auto;
  } else {
    std::string scheme = get_scheme();
    if (scheme == "mysqlx")
      return mysqlsh::SessionType::X;
    else if (scheme == "mysql")
      return mysqlsh::SessionType::Classic;
    else
      throw std::invalid_argument("Unknown MySQL URI type " + scheme);
  }
}

void Connection_options::set_default_data() {
  // Default values
  if (!has_user() && !is_auth_method(mysqlshdk::db::kAuthMethodKerberos)) {
    // The system user is not used with kerberos to trigger the client lib
    // functionality of using cashed TGT
    set_user(shcore::get_system_user());
  }

  bool has_transp = has_transport_type();
  if (!has_host() &&
      (!has_transp || get_transport_type() == mysqlshdk::db::Tcp)) {
    set_host("localhost");
  }
#ifdef _WIN32
  if (!has_transp) {
    // Windows always uses TCP by default
    m_transport_type = mysqlshdk::db::Tcp;
  }
#else
  if (!has_transp) {
    // xproto connections connect via TCP by default
    if (!has_scheme() || get_scheme() == "mysqlx") {
      if (has_port() || !has_socket())
        m_transport_type = mysqlshdk::db::Tcp;
      else
        m_transport_type = mysqlshdk::db::Socket;
    } else {
      // classic connections connect via socket by default
      if (has_port())
        m_transport_type = mysqlshdk::db::Tcp;
      else
        m_transport_type = mysqlshdk::db::Socket;
    }
  }
#endif

#ifdef _WIN32
  if (!has_pipe() && has_host() && get_host() == ".") {
    set_pipe("MySQL");
  }

  if (has_pipe() && !has_scheme()) {
    set_scheme("mysql");
  }
#endif  // _WIN32

  if (m_ssh_options.has_data()) {
    m_ssh_options.set_default_data();
  }
}

void Connection_options::throw_invalid_connect_timeout(
    const std::string &value) {
  throw std::invalid_argument(
      shcore::str_format("Invalid value '%s' for '%s'. The "
                         "connection timeout value must be a positive integer "
                         "(including 0).",
                         value.c_str(), kConnectTimeout));
}

bool Connection_options::is_auth_method(const std::string &method_id) const {
  bool ret_val = false;

  if (has_value(mysqlshdk::db::kAuthMethod)) {
    auto auth_method = get(mysqlshdk::db::kAuthMethod);
    if (method_id == mysqlshdk::db::kAuthMethodKerberos) {
      ret_val = auth_method == mysqlshdk::db::kAuthMethodKerberos ||
                auth_method == mysqlshdk::db::kAuthMethodLdapSasl;
    } else {
      ret_val = auth_method == method_id;
    }
  }

  return ret_val;
}

void Connection_options::set_connection_attribute(const std::string &attribute,
                                                  const std::string &value) {
  std::string error;

  if (attribute.empty()) {
    error =
        "Invalid attribute on 'connection-attributes', empty name is not "
        "allowed.";
  } else if (attribute[0] == '_') {
    error = "Key names in 'connection-attributes' cannot start with '_'";
  } else if (m_connection_attributes.has(attribute)) {
    error =
        "Duplicate key '" + attribute + "' used in 'connection-attributes'.";
  }

  if (error.empty())
    m_connection_attributes.set(attribute, value);
  else
    throw std::invalid_argument(error);
}

void Connection_options::set_connection_attributes(
    const std::vector<std::string> &attributes) {
  // Enables the connection attributes
  m_enable_connection_attributes = true;

  for (const auto &pair : attributes) {
    if (!pair.empty()) {
      auto tokens = shcore::split_string(pair, "=");
      size_t len = tokens.size();

      std::string attribute, value;
      switch (len) {
        case 2:
          attribute = tokens[0];
          value = tokens[1];
          break;
        case 1:
          attribute = tokens[0];
          break;
        default:
          throw std::invalid_argument(
              "The value of 'connection-attributes' must be "
              "either a boolean or a list of key=value pairs.");
      }
      set_connection_attribute(attribute, value);
    }
  }
}

int64_t default_connect_timeout() {
  // some tests execute SQL before options are available, in that case use the
  // default value
  const auto options = mysqlsh::current_shell_options(true);
  const auto &storage =
      options ? options->get() : mysqlsh::Shell_options::Storage();
  return storage.connect_timeout * 1000;
}

std::string default_mysql_plugins_dir() {
  // some tests execute SQL before options are available, in that case use the
  // default value

  if (const auto options = mysqlsh::current_shell_options(true)) {
    return options->get().mysql_plugin_dir;
  }

  return "";
}

}  // namespace db
}  // namespace mysqlshdk
