/*
 * This file is a part of the LinkAhead Project.
 *
 * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
 * Copyright (C) 2021-2024 IndiScale GmbH <info@indiscale.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 *
 */

#ifndef LINKAHEAD_CONFIGURATION_H
#define LINKAHEAD_CONFIGURATION_H

#include "linkahead/authentication.h"       // for Authenticator, PlainPassw...
#include "linkahead/certificate_provider.h" // for CertificateProvider, path
#include "linkahead/exceptions.h"           // for ConfigurationError
#include "linkahead/utility.h"              // for load_json_file
#include <google/protobuf/arena.h>          // for Arena
#include <google/protobuf/extension_set.h>  // for Arena
#include <grpcpp/security/credentials.h>    // for ChannelCredentials
#include <filesystem>                       // for path, exists
#include <iosfwd>                           // for ostream
#include <memory>                           // for shared_ptr, unique_ptr
#include <string>                           // for string

namespace linkahead::configuration {
using google::protobuf::Arena;
using grpc::ChannelCredentials;
using linkahead::authentication::Authenticator;
using linkahead::authentication::PlainPasswordAuthenticator;
using linkahead::exceptions::ConfigurationError;
using linkahead::utility::JsonValue;
using linkahead::utility::load_json_file;
using std::filesystem::exists;
using std::filesystem::path;

const std::string logger_name = "linkahead::configuration";

/**
 * @brief Configuration of the LinkAhead connection.
 */
class ConnectionConfiguration {
private:
  std::string host;
  int port;

public:
  ConnectionConfiguration(std::string host, int port);
  virtual ~ConnectionConfiguration() = default;
  friend auto operator<<(std::ostream &out, const ConnectionConfiguration &configuration)
    -> std::ostream &;

  [[nodiscard]] auto virtual ToString() const -> std::string = 0;
  [[nodiscard]] auto GetHost() const -> std::string;
  [[nodiscard]] auto GetPort() const -> int;
  [[nodiscard]] auto virtual GetChannelCredentials() const
    -> std::shared_ptr<ChannelCredentials> = 0;
};

class InsecureConnectionConfiguration : public ConnectionConfiguration {
private:
  std::shared_ptr<ChannelCredentials> credentials;

public:
  InsecureConnectionConfiguration(const std::string &host, int port);
  [[nodiscard]] auto GetChannelCredentials() const -> std::shared_ptr<ChannelCredentials> override;
  [[nodiscard]] auto ToString() const -> std::string override;
};

class TlsConnectionConfiguration : public ConnectionConfiguration {
private:
  std::shared_ptr<ChannelCredentials> credentials;
  std::string certificate_provider;

public:
  TlsConnectionConfiguration(const std::string &host, int port);
  TlsConnectionConfiguration(const std::string &host, int port, const Authenticator &authenticator);
  TlsConnectionConfiguration(const std::string &host, int port,
                             const CertificateProvider &certificate_provider);
  TlsConnectionConfiguration(const std::string &host, int port,
                             const CertificateProvider &certificate_provider,
                             const Authenticator &authenticator);
  [[nodiscard]] auto GetChannelCredentials() const -> std::shared_ptr<ChannelCredentials> override;
  [[nodiscard]] auto ToString() const -> std::string override;
};

/**
 * Reads the configuration file and keeps the configuration. Singleton.
 *
 * Currently, this class can only read a single configuration file. No merging
 * or overwriting is supported.
 */
class ConfigurationManager {
public:
  static ConfigurationManager &GetInstance();
  ;

  /**
   * See mReset.
   */
  inline static auto Reset() noexcept -> int { return GetInstance().mReset(); }

  /**
   * See mClear.
   */
  inline static auto Clear() noexcept -> int { return GetInstance().mClear(); }

  /**
   * See mLoadSingleJSONConfiguration.
   */
  inline static auto LoadSingleJSONConfiguration(const path &json_file) -> void {
    GetInstance().mLoadSingleJSONConfiguration(json_file);
  }

  /**
   * See mGetConnectionConfiguration.
   */
  inline static auto GetConnectionConfiguration(const std::string &name)
    -> std::unique_ptr<ConnectionConfiguration> {
    return GetInstance().mGetConnectionConfiguration(name);
  }

  /**
   * Return the ConnectionConfiguration for the default connection.
   */
  inline static auto GetDefaultConnectionConfiguration()
    -> std::unique_ptr<ConnectionConfiguration> {
    return GetInstance().mGetConnectionConfiguration(GetInstance().mGetDefaultConnectionName());
  }

  /**
   * See mGetDefaultConnectionName.
   */
  inline static auto GetDefaultConnectionName() -> std::string {
    return GetInstance().mGetDefaultConnectionName();
  }

  ConfigurationManager(ConfigurationManager const &) = delete;
  void operator=(ConfigurationManager const &) = delete;

  inline static auto GetArena() -> Arena * { return &GetInstance().arena; }

private:
  Arena arena;
  JsonValue json_configuration;
  static ConfigurationManager mInstance;

  inline ConfigurationManager()
    : json_configuration(nullptr){
        // InitializeDefaults();
      };

  /**
   * Initialize this ConfigurationManager with the defaults.
   *
   * Currently, this means, that the ConfigurationManager attempts to load the
   * first existing file from the LIBLINKAHEAD_CONFIGURATION_FILES_PRECEDENCE list
   * of file locations.
   */
  auto InitializeDefaults() -> int;

  /**
   * Reset this ConfigurationManager.
   *
   * The current configuration is deleted and a new configuration is being
   * loaded via InitializeDefaults.
   */
  auto mReset() noexcept -> int;

  /**
   * Clear this ConfigurationManager.
   *
   * Afterwards, this ConfigurationManager is uninitilized.
   *
   * In contrast to mReset, this method only deletes the current configuration
   * but does not load a new one via InitializeDefaults.
   */
  auto mClear() noexcept -> int;

  /**
   * Load a configuration from a json file.
   */
  auto mLoadSingleJSONConfiguration(const path &json_file) -> void;

  /**
   * Return the ConnectionConfiguration for the connection of the given name.
   */
  auto mGetConnectionConfiguration(const std::string &name) const
    -> std::unique_ptr<ConnectionConfiguration>;

  /**
   * Return the ConnectionConfiguration for the default connection.
   */
  auto mGetDefaultConnectionName() const -> std::string;
};

} // namespace linkahead::configuration
#endif