/**
 * Copyright 2024 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 "grpc_config.hh"
#include "com/centreon/common/rapidjson_helper.hh"
#include "com/centreon/engine/globals.hh"

using com::centreon::common::rapidjson_helper;

static constexpr std::string_view _grpc_config_schema(R"(
{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "grpc config",
    "properties": {
        "host": {
            "description": "IP or dns name",
            "type": "string",
            "minLength": 5
        },
        "port": {
            "description": "port to listen",
            "type": "integer",
            "minimum": 1024,
            "maximum": 65535
        },
        "encryption": {
            "description": "encryption mode: full, insecure, or no",
            "anyOf": [
              { "type": "boolean" },
              { "type": "string", "enum": ["full", "insecure", "no", "true", "false"] }
            ]
        },
        "public_cert": {
            "description": "path of certificate file .crt",
            "type": "string"
        },
        "private_key": {
            "description": "path of certificate file .key",
            "type": "string"
        },
        "ca_certificate": {
            "description": "path of authority certificate file .crt",
            "type": "string"
        },
        "ca_name": {
            "description": "name of authority certificate",
            "type": "string"
        },
        "compression": {
            "description": "activate compression",
            "type": "boolean"
        },
        "keepalive_interval": {
            "description": "delay between 2 keepalive tcp packet",
            "type": "integer",
            "minimum": -1,
            "maximum": 3600
        },
        "second_max_reconnect_backoff": {
            "description": "maximum time between subsequent connection attempts, in seconds. Default: 60s",
            "type": "integer",
            "minimum": 0,
            "maximum": 600
        },
        "minute_certificate_ttl": {
            "description:": "peremption in minutes of certificate built from self signed ca",
            "type": "integer",
            "min": 1
        },
        "max_message_length": {
            "description": "maximum protobuf message length in Mo",
            "type": "integer",
            "minimum": 4
        },"token":{
          "description": "key for token",
          "type": "string"
      },"trusted_tokens":{
            "description": "tokens for authentication",
            "type": "array",
              "items": {
                "type": "string"
              }
        }
    },
    "required": [
                 "host",
                 "port"
                 ],
    "type": "object"
}

)");

using namespace com::centreon::engine::modules::opentelemetry;

/**
 * @brief Construct a new grpc config::grpc config object
 *
 * @param json_config_v content of the json config file
 */
grpc_config::grpc_config(const rapidjson::Value& json_config_v,
                         const std::string_view& default_cert_path,
                         const std::string_view& default_key_path) {
  common::rapidjson_helper json_config(json_config_v);

  static common::json_validator validator(_grpc_config_schema);
  try {
    json_config.validate(validator);
  } catch (const std::exception& e) {
    SPDLOG_LOGGER_ERROR(config_logger,
                        "forbidden values in grpc otl config: {}", e.what());
    throw;
  }

  std::string hostport = fmt::format("{}:{}", json_config.get_string("host"),
                                     json_config.get_unsigned("port"));
  grpc_config::e_security_mode security_mode = NONE;
  std::string certificate, cert_key, ca_cert;
  std::string ca_name;
  bool compress = false;
  int second_keepalive_interval;

  if (json_config.has_member("encryption")) {
    const auto& encryption_value = json_config_v["encryption"];
    if (encryption_value.IsString()) {
      const std::string& encryption = json_config.get_string("encryption");
      if (encryption == "full") {
        security_mode = TLS_SECURE;
      } else if (encryption == "insecure" || encryption == "true") {
        security_mode = TLS_INSECURE;
      } else {
        security_mode = NONE;
      }
    } else if (encryption_value.IsBool()) {
      bool crypted = encryption_value.GetBool();
      if (crypted) {
        security_mode = TLS_INSECURE;
      } else {
        security_mode = NONE;
      }
    }
  }

  if (security_mode != NONE) {
    _cert_path = json_config.get_string("public_cert", "");
    if (_cert_path.empty()) {
      _cert_path = default_cert_path;
    }
    if (!_cert_path.empty()) {
      _cert_mtime = read_file(_cert_path, certificate);
    }

    _key_path = json_config.get_string("private_key", "");
    if (_key_path.empty()) {
      _key_path = default_key_path.data();
    }
    if (!_key_path.empty()) {
      _key_mtime = read_file(_key_path, cert_key);
    }

    _ca_path = json_config.get_string("ca_certificate", "");
    if (!_ca_path.empty()) {
      _ca_mtime = read_file(_ca_path, ca_cert);
    }
    if (json_config.has_member("ca_name"))
      ca_name = json_config.get_string("ca_name");
  }
  if (json_config.has_member("compression"))
    compress = json_config.get_bool("compression");

  _minute_certificate_ttl = json_config.get_unsigned(
      "minute_certificate_ttl", 30 * 24 * 60);  // 30 days by default

  if (json_config.has_member("keepalive_interval"))
    second_keepalive_interval = json_config.get_int("keepalive_interval");
  else
    second_keepalive_interval = 30;

  unsigned second_max_reconnect_backoff =
      json_config.get_unsigned("second_max_reconnect_backoff", 60);

  unsigned max_message_length =
      json_config.get_unsigned("max_message_length", 4) * 1024 * 1024;

  std::string token;
  if (json_config.has_member("token")) {
    token = json_config.get_string("token");
  }
  absl::flat_hash_set<std::string> trusted_tokens;
  if (json_config.has_member("trusted_tokens")) {
    const rapidjson::Value& tokens_v = json_config.get_member("trusted_tokens");
    for (const auto& v : tokens_v.GetArray()) {
      trusted_tokens.insert(v.GetString());
    }
  }

  static_cast<common::grpc::grpc_config&>(*this) = common::grpc::grpc_config(
      hostport, security_mode, certificate, cert_key, ca_cert, ca_name,
      compress, second_keepalive_interval, second_max_reconnect_backoff,
      max_message_length, token,
      std::make_shared<const absl::flat_hash_set<std::string>>(
          std::move(trusted_tokens)));
}

/**
 * @brief read a file as certificate
 *
 * @param path of file
 * @param file_content out: file content
 * @return mtime of the file if file path is not empty
 */
std::filesystem::file_time_type grpc_config::read_file(
    std::string_view path,
    std::string& file_content) {
  try {
    path = absl::StripLeadingAsciiWhitespace(path);
    path = absl::StripTrailingAsciiWhitespace(path);
    if (path.empty()) {
      return {};
    }
    std::ifstream file(path.data());
    std::stringstream ss;
    ss << file.rdbuf();
    file.close();
    file_content = ss.str();
    return std::filesystem::last_write_time((path));
  } catch (const std::exception& e) {
    SPDLOG_LOGGER_ERROR(config_logger, "fail to read {}: {}", path, e.what());
    throw;
  }
}

bool grpc_config::operator==(const grpc_config& right) const {
  return static_cast<const common::grpc::grpc_config>(*this) ==
             static_cast<const common::grpc::grpc_config>(right) &&
         _minute_certificate_ttl == right._minute_certificate_ttl;
}

/**
 * @brief reload certificate if usefull (changed on disk)
 *
 * @return true certificate had been reloaded
 * @return false
 */
bool grpc_config::reload_certificates() {
  std::filesystem::file_time_type cert_mtime, key_mtime, ca_mtime;
  std::error_code err;
  if (!_cert_path.empty()) {
    cert_mtime = std::filesystem::last_write_time(_cert_path, err);
    if (err) {
      return false;
    }
  }
  if (!_key_path.empty()) {
    key_mtime = std::filesystem::last_write_time(_key_path, err);
    if (err) {
      return false;
    }
  }
  if (!_ca_path.empty()) {
    ca_mtime = std::filesystem::last_write_time(_ca_path, err);
    if (err) {
      return false;
    }
  }

  std::string cert, key, ca;
  unsigned ret = 0;
  if (!_cert_path.empty() && cert_mtime != _cert_mtime) {
    try {
      _cert_mtime = read_file(_cert_path, cert);
      ret |= 1;
    } catch (const std::exception&) {
      return false;
    }
  }
  if (!_key_path.empty() && key_mtime != _key_mtime) {
    try {
      _key_mtime = read_file(_key_path, key);
      ret |= 2;
    } catch (const std::exception&) {
      return false;
    }
  }
  if (!_ca_path.empty() && ca_mtime != _ca_mtime) {
    try {
      _ca_mtime = read_file(_ca_path, ca);
      ret |= 4;
    } catch (const std::exception&) {
      return false;
    }
  }
  if (ret & 1) {
    set_cert(cert);
  }
  if (ret & 2) {
    set_key(key);
  }
  if (ret & 4) {
    set_ca(ca);
  }
  return ret;
}
