/**
 * Copyright 2019-2021 Centreon (https://www.centreon.com/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 *
 * For more information : contact@centreon.com
 *
 */

#include "com/centreon/engine/commands/connector.hh"
#include <gtest/gtest.h>
#include <signal.h>

#include "../timeperiod/utils.hh"
#include "com/centreon/engine/commands/forward.hh"
#include "helper.hh"

extern std::shared_ptr<asio::io_context> g_io_context;

using namespace com::centreon;
using namespace com::centreon::engine;
using namespace com::centreon::engine::commands;

class connector_listener : public commands::command_listener {
 public:
  result const& get_result() const {
    std::lock_guard<std::mutex> guard(_mutex);
    return _res;
  }

  void finished(result const& res) throw() override {
    std::lock_guard<std::mutex> guard(_mutex);
    _res = res;
    std::cout << "output:" << res.output << std::endl;
    _outputs.insert(res.output);
  }

  void clear() {
    _res.command_id = 0u;
    _res.output = "";
    _outputs.clear();
  }

  const absl::flat_hash_set<std::string>& get_outputs() const {
    return _outputs;
  }

 private:
  mutable std::mutex _mutex;
  commands::result _res;
  absl::flat_hash_set<std::string> _outputs;
};

class Connector : public ::testing::Test {
 public:
  std::shared_ptr<connector> cmd_connector;

  void SetUp() override {
    signal(SIGPIPE, SIG_IGN);
    commands_logger->set_level(spdlog::level::trace);
    init_config_state();
  }

  void TearDown() override {
    deinit_config_state();
    if (cmd_connector) {
      cmd_connector->stop_connector();
      cmd_connector.reset();
      std::this_thread::sleep_for(std::chrono::seconds(1));
    }
  }
};

// Given an empty name
// When the add_command method is called with it as argument,
// Then it returns a NULL pointer.
TEST_F(Connector, NewConnector) {
  ASSERT_THROW(commands::connector::load("", "bar", g_io_context),
               std::exception);
}

TEST_F(Connector, ForwardWithoutName) {
  cmd_connector = commands::connector::load(
      "test segfault", "tests/bin_connector_test_run --kill=2", g_io_context);
  ASSERT_THROW(new commands::forward("", "bar", cmd_connector), std::exception);
}

TEST_F(Connector, ForwardWithoutCmd) {
  cmd_connector = commands::connector::load(
      "test segfault", "tests/bin_connector_test_run --kill=2", g_io_context);
  ASSERT_THROW(new commands::forward("foo", "", cmd_connector), std::exception);
}

// Given an already existing command
// When the add_command method is called with the same name
// Then it returns a NULL pointer.
TEST_F(Connector, SimpleConnector) {
  cmd_connector = commands::connector::load("toto", "/bin/ls", g_io_context);
}

// This test is just a test of the run command in usual conditions.
// We don't test timeout because time is replaced by a fake function
// and we don't control it during the execution of run().
TEST_F(Connector, RunWithTimeout) {
  nagios_macros macros = nagios_macros();
  cmd_connector = commands::connector::load(
      "RunWithTimeout", "tests/bin_connector_test_run", g_io_context);
  result res;
  cmd_connector->run("commande --timeout=on", macros, 1, res);

  ASSERT_TRUE(res.command_id != 0u);
}

TEST_F(Connector, RunConnectorAsync) {
  auto lstnr = std::make_shared<connector_listener>();
  nagios_macros macros = nagios_macros();
  cmd_connector = commands::connector::load(
      "RunConnectorAsync", "tests/bin_connector_test_run", g_io_context);
  cmd_connector->set_listener(lstnr);
  cmd_connector->run("commande", macros, 1, std::make_shared<check_result>());

  int timeout = 0;
  int max_timeout{15};
  while (timeout < max_timeout && lstnr->get_result().output == "") {
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    set_time(std::time(nullptr) + 1);
    ++timeout;
  }
  result res{lstnr->get_result()};
  ASSERT_NE(res.command_id, 0u);
  ASSERT_EQ(res.output, "commande");
}

/**
 * @brief we send a command and a kill command with a small timeout and other
 * commands we expect that connector restart fake connector and that all
 * commands except kill one will be then executed
 *
 */
TEST_F(Connector, RunWithConnectorSwitchedOff) {
  cmd_connector =
      commands::connector::load("RunWithConnectorSwitchedOff",
                                "tests/bin_connector_test_run", g_io_context);

  auto lstnr = std::make_shared<connector_listener>();
  nagios_macros macros = nagios_macros();
  cmd_connector->set_listener(lstnr);
  cmd_connector->run("beforekill", macros, 60,
                     std::make_shared<check_result>());
  cmd_connector->run("commande --kill=1", macros, 1,
                     std::make_shared<check_result>());
  for (unsigned int ii = 0; ii < 20; ++ii) {
    cmd_connector->run(fmt::format("commande{}", ii), macros, 60,
                       std::make_shared<check_result>());
  }

  int timeout = 0;
  int max_timeout{15};
  while (timeout < max_timeout && lstnr->get_outputs().size() < 21) {
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    set_time(std::time(nullptr) + 1);
    ++timeout;
  }
  ASSERT_EQ(lstnr->get_outputs().size(), 22);
  EXPECT_TRUE(lstnr->get_outputs().contains("beforekill"));
  EXPECT_TRUE(lstnr->get_outputs().contains("(Process Timeout)"));
  for (unsigned int ii = 0; ii < 20; ++ii) {
    EXPECT_TRUE(lstnr->get_outputs().contains(fmt::format("commande{}", ii)));
  }
}

TEST_F(Connector, RunConnectorSetCommandLine) {
  auto lstnr = std::make_shared<connector_listener>();
  nagios_macros macros = nagios_macros();
  cmd_connector = commands::connector::load(
      "SetCommandLine", "tests/bin_connector_test_run", g_io_context);
  cmd_connector->set_listener(lstnr);
  cmd_connector->run("commande1", macros, 1, std::make_shared<check_result>());

  int timeout = 0;
  int max_timeout{15};
  while (timeout < max_timeout && lstnr->get_result().output == "") {
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    set_time(std::time(nullptr) + 1);
    ++timeout;
  }
  result res{lstnr->get_result()};
  ASSERT_NE(res.command_id, 0u);
  ASSERT_EQ(res.output, "commande1");

  lstnr->clear();
  cmd_connector->set_command_line("tests/bin_connector_test_run");
  cmd_connector->run("commande2", macros, 1, std::make_shared<check_result>());

  timeout = 0;
  max_timeout = 15;
  while (timeout < max_timeout && lstnr->get_result().output == "") {
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    set_time(std::time(nullptr) + 1);
    ++timeout;
  }
  res = lstnr->get_result();
  ASSERT_NE(res.command_id, 0u);
  ASSERT_EQ(res.output, "commande2");
}
