/**
* Copyright 2013-2015 Centreon
*
* 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 <sys/stat.h>
#include <unistd.h>
#include <QSqlError>
#include <QSqlQuery>
#include <QVariant>
#include "com/centreon/broker/exceptions/msg.hh"
#include "test/cbd.hh"
#include "test/config.hh"
#include "test/engine.hh"
#include "test/generate.hh"
#include "test/misc.hh"
#include "test/vars.hh"

using namespace com::centreon::broker;

#define DB_NAME "broker_delete_graphs"
#define HOST_COUNT 2
#define SERVICES_BY_HOST 5

/**
 *  Check that graphs can be properly deleted.
 *
 *  @return EXIT_SUCCESS on success.
 */
int main() {
  // Error flag.
  bool error(true);

  // Variables that need cleaning.
  std::list<command> commands;
  std::list<host> hosts;
  std::list<service> services;
  std::string cbd_config_path(tmpnam(NULL));
  std::string engine_config_path(tmpnam(NULL));
  std::string metrics_path(tmpnam(NULL));
  std::string status_path(tmpnam(NULL));
  engine monitoring;
  cbd broker;
  test_db db;

  // Log.
  std::clog << "status directory: " << status_path << "\n"
            << "metrics directory: " << metrics_path << std::endl;

  try {
    // Prepare database.
    db.open(DB_NAME);

    // Create RRD paths.
    mkdir(metrics_path.c_str(), S_IRWXU);
    mkdir(status_path.c_str(), S_IRWXU);

    // Write cbd configuration file.
    {
      std::ofstream ofs;
      ofs.open(cbd_config_path.c_str(),
               std::ios_base::out | std::ios_base::trunc);
      if (ofs.fail())
        throw(exceptions::msg() << "cannot open cbd configuration file '"
                                << cbd_config_path.c_str() << "'");
      ofs << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
          << "<centreonbroker>\n"
          << "  <include>" PROJECT_SOURCE_DIR
             "/test/cfg/broker_modules.xml</include>\n"
          << "  <instance>42</instance>\n"
          << "  <instance_name>MyBroker</instance_name>\n"
          << "  <!--\n"
          << "  <logger>\n"
          << "    <type>file</type>\n"
          << "    <name>cbd.log</name>\n"
          << "    <config>1</config>\n"
          << "    <debug>1</debug>\n"
          << "    <error>1</error>\n"
          << "    <info>1</info>\n"
          << "    <level>3</level>\n"
          << "  </logger>\n"
          << "  -->\n"
          << "  <input>\n"
          << "    <name>EngineToTCP</name>\n"
          << "    <type>tcp</type>\n"
          << "    <port>5681</port>\n"
          << "    <protocol>bbdo</protocol>\n"
          << "  </input>\n"
          << "  <output>\n"
          << "    <name>TCPToStorage</name>\n"
          << "    <type>storage</type>\n"
          << "    <db_type>" DB_TYPE "</db_type>\n"
          << "    <db_host>" DB_HOST "</db_host>\n"
          << "    <db_port>" DB_PORT "</db_port>\n"
          << "    <db_user>" DB_USER "</db_user>\n"
          << "    <db_password>" DB_PASSWORD "</db_password>\n"
          << "    <db_name>" DB_NAME "</db_name>\n"
          << "    <queries_per_transaction>0</queries_per_transaction>\n"
          << "    <length>" << 51840 * MONITORING_ENGINE_INTERVAL_LENGTH
          << "</length>\n"
          << "  </output>\n"
          << "  <output>\n"
          << "    <name>StorageToRRD</name>\n"
          << "    <type>rrd</type>\n"
          << "    <metrics_path>" << metrics_path << "</metrics_path>\n"
          << "    <status_path>" << status_path << "</status_path>\n"
          << "  </output>\n"
          << "</centreonbroker>\n";
      ofs.close();
    }

    // Prepare monitoring engine configuration parameters.
    generate_commands(commands, 1);
    {
      command& cmd(commands.front());
      char const* cmdline;
      cmdline = MY_PLUGIN_PATH " 0 \"output|metric=100\"";
      cmd.command_line = new char[strlen(cmdline) + 1];
      strcpy(cmd.command_line, cmdline);
    }
    generate_hosts(hosts, HOST_COUNT);
    for (std::list<host>::iterator it(hosts.begin()), end(hosts.end());
         it != end; ++it) {
      it->host_check_command = new char[2];
      strcpy(it->host_check_command, "1");
    }
    generate_services(services, hosts, SERVICES_BY_HOST);
    for (std::list<service>::iterator it(services.begin()), end(services.end());
         it != end; ++it) {
      it->service_check_command = new char[2];
      strcpy(it->service_check_command, "1");
    }
    std::string engine_additional;
    {
      std::ostringstream oss;
      oss << "broker_module=" << CBMOD_PATH << " "
          << PROJECT_SOURCE_DIR "/test/cfg/delete_graphs.xml";
      engine_additional = oss.str();
    }

    // Insert entries in index_data.
    {
      QSqlQuery q(*db.centreon_db());
      for (uint32_t i(1); i <= HOST_COUNT * SERVICES_BY_HOST; ++i) {
        uint32_t host_id((i - 1) / SERVICES_BY_HOST + 1);
        std::ostringstream query;
        query << "INSERT INTO rt_index_data (host_id, service_id)"
              << "  VALUES(" << host_id << ", " << i << ")";
        if (!q.exec(query.str().c_str()))
          throw(exceptions::msg() << "cannot create index of service ("
                                  << host_id << ", " << i << ")");
      }
    }

    // Generate monitoring engine configuration files.
    config_write(engine_config_path.c_str(), engine_additional.c_str(), &hosts,
                 &services, &commands);

    // Start cbd.
    broker.set_config_file(cbd_config_path);
    broker.start();
    sleep_for(2);

    // Start monitoring engine.
    std::string engine_config_file(engine_config_path);
    engine_config_file.append("/nagios.cfg");
    monitoring.set_config_file(engine_config_file);
    monitoring.start();
    sleep_for(25);

    // Check and get index_data entries.
    std::map<std::pair<unsigned long, unsigned long>, unsigned long> indexes;
    {
      QSqlQuery q(*db.centreon_db());
      std::string query(
          "SELECT host_id, service_id, index_id"
          "  FROM rt_index_data"
          "  ORDER BY host_id, service_id");
      if (!q.exec(query.c_str()))
        throw(exceptions::msg()
              << "cannot get index list: " << qPrintable(q.lastError().text()));
      for (unsigned long i(0); i < HOST_COUNT; ++i)
        for (unsigned long j(0); j < SERVICES_BY_HOST; ++j) {
          unsigned long host_id(i + 1);
          unsigned long service_id(i * SERVICES_BY_HOST + j + 1);
          if (!q.next())
            throw(exceptions::msg() << "not enough index entries in DB");
          else if ((q.value(0).toUInt() != host_id) ||
                   (q.value(1).toUInt() != service_id))
            throw(exceptions::msg()
                  << "index entry mismatch: got (" << q.value(0).toUInt()
                  << ", " << q.value(1).toUInt() << "), expected (" << host_id
                  << ", " << service_id << ")");
          indexes[std::make_pair(host_id, service_id)] = q.value(2).toUInt();
        }
    }

    // Get metrics entries.
    std::map<unsigned long, std::list<unsigned long> > metrics;
    {
      QSqlQuery q(*db.centreon_db());
      std::string query("SELECT index_id, metric_id FROM rt_metrics");
      if (!q.exec(query.c_str()))
        throw(exceptions::msg() << "cannot get metrics list: "
                                << qPrintable(q.lastError().text()));
      while (q.next())
        metrics[q.value(0).toUInt()].push_back(q.value(1).toUInt());
    }

    // Check index_data and metrics entries along with their RRD files.
    {
      if (indexes.size() != metrics.size())
        throw(exceptions::msg()
              << "size mismatch between index_data and metrics: got "
              << indexes.size() << " indexes for " << metrics.size()
              << " referenced indexes");
      for (std::map<unsigned long, std::list<unsigned long> >::const_iterator
               it1(metrics.begin()),
           end1(metrics.end());
           it1 != end1; ++it1) {
        if (it1->second.size() != 1)
          throw(exceptions::msg()
                << "too much metrics for index " << it1->first << ": got "
                << it1->second.size() << ", expected 1");
        {
          std::ostringstream status_file;
          status_file << status_path << "/" << it1->first << ".rrd";
          if (access(status_file.str().c_str(), F_OK))
            throw(exceptions::msg()
                  << "status RRD file '" << status_file.str().c_str()
                  << "' does not exist");
        }
        {
          std::ostringstream metrics_file;
          metrics_file << metrics_path << "/" << it1->second.front() << ".rrd";
          if (access(metrics_file.str().c_str(), F_OK))
            throw(exceptions::msg()
                  << "metrics RRD file '" << metrics_file.str().c_str()
                  << "' does not exist");
        }
      }
    }

    // Terminate monitoring engine (no more performance data).
    monitoring.stop();
    sleep_for(2);

    // Flag an index to delete.
    {
      QSqlQuery q(*db.centreon_db());
      std::string query(
          "UPDATE rt_index_data"
          "  SET to_delete=1"
          "  WHERE host_id=2");
      if (!q.exec(query.c_str()))
        throw(exceptions::msg() << "cannot flag index_data to delete: "
                                << qPrintable(q.lastError().text()));
    }

    // Flag metrics to delete.
    {
      QSqlQuery q(*db.centreon_db());
      std::string query(
          "UPDATE rt_metrics AS m JOIN rt_index_data AS i"
          "  ON m.index_id=i.index_id"
          "  SET m.to_delete=1"
          "  WHERE i.host_id=1 AND i.service_id<>1");
      if (!q.exec(query.c_str()))
        throw(exceptions::msg() << "cannot flag metrics to delete: "
                                << qPrintable(q.lastError().text()));
    }

    // Signal entries to delete to cbd and wait a little.
    broker.update();
    sleep_for(7);

    // Terminate cbd.
    broker.stop();
    sleep_for(2);

    // Check that only one entry remains in index_data (1, 1).
    {
      QSqlQuery q(*db.centreon_db());
      std::string query("SELECT host_id, service_id FROM rt_index_data");
      if (!q.exec(query.c_str()))
        throw(exceptions::msg() << "cannot read index_data: "
                                << qPrintable(q.lastError().text()));
      for (unsigned long i(0); i < SERVICES_BY_HOST; ++i) {
        if (!q.next())
          throw(exceptions::msg()
                << "not enough entries in index_data after deletion: "
                << "got " << i << ", expected " << SERVICES_BY_HOST);
        else if ((q.value(0).toUInt() != 1) || (q.value(1).toUInt() != (i + 1)))
          throw(exceptions::msg()
                << "last index_data entry is (" << q.value(0).toUInt() << ", "
                << q.value(1).toUInt() << "), expected (1, " << (i + 1) << ")");
      }
      if (q.next())
        throw(exceptions::msg()
              << "too much index_data entries after deletion");
    }

    // Check that only one entry remains in metrics.
    {
      QSqlQuery q(*db.centreon_db());
      std::string query(
          "SELECT i.host_id, i.service_id"
          "  FROM rt_metrics AS m JOIN rt_index_data AS i"
          "  ON m.index_id=i.index_id");
      if (!q.exec(query.c_str()))
        throw(exceptions::msg()
              << "cannot read metrics: " << qPrintable(q.lastError().text()));
      else if (!q.next())
        throw(exceptions::msg()
              << "no entry remains in index_data after deletion");
      else if ((q.value(0).toUInt() != 1) || (q.value(1).toUInt() != 1))
        throw(exceptions::msg()
              << "last metrics entry is for service (" << q.value(0).toUInt()
              << ", " << q.value(1).toUInt() << "), expected (1, 1)");
      else if (q.next())
        throw(exceptions::msg() << "too much metrics entries after deletion");
    }

    // Remove entries that are present.
    metrics.erase(indexes[std::make_pair(1ul, 1ul)]);
    for (unsigned long i(1); i <= SERVICES_BY_HOST; ++i)
      indexes.erase(std::make_pair(1ul, i));

    // Check that deleted indexes got their RRD files deleted.
    for (std::map<std::pair<unsigned long, unsigned long>,
                  unsigned long>::const_iterator it(indexes.begin()),
         end(indexes.end());
         it != end; ++it) {
      std::ostringstream status_file;
      status_file << status_path << "/" << it->second << ".rrd";
      if (!access(status_file.str().c_str(), F_OK))
        throw(exceptions::msg()
              << "status RRD file '" << status_file.str().c_str()
              << "' still exists");
    }

    // Check that deleted metrics got their RRD files deleted.
    for (std::map<unsigned long, std::list<unsigned long> >::const_iterator
             it(metrics.begin()),
         end(metrics.end());
         it != end; ++it) {
      {
        std::ostringstream metrics_file;
        metrics_file << metrics_path << "/" << it->second.front() << ".rrd";
        if (!access(metrics_file.str().c_str(), F_OK))
          throw(exceptions::msg()
                << "metrics RRD file '" << metrics_file.str().c_str()
                << "' still exists");
      }
    }

    // Success.
    error = false;
  } catch (std::exception const& e) {
    std::cerr << e.what() << std::endl;
  } catch (...) {
    std::cerr << "unknown exception" << std::endl;
  }

  // Cleanup.
  monitoring.stop();
  broker.stop();
  config_remove(engine_config_path.c_str());
  ::remove(cbd_config_path.c_str());
  free_hosts(hosts);
  free_services(services);
  free_commands(commands);
  recursive_remove(metrics_path);
  recursive_remove(status_path);

  // Return check result.
  return (error ? EXIT_FAILURE : EXIT_SUCCESS);
}
