//  OpenVPN 3 Linux client -- Next generation OpenVPN client
//
//  SPDX-License-Identifier: AGPL-3.0-only
//
//  Copyright (C) 2017-  OpenVPN Inc <sales@openvpn.net>
//  Copyright (C) 2017-  David Sommerseth <davids@openvpn.net>
//  Copyright (C) 2018-  Arne Schwabe <arne@openvpn.net>
//  Copyright (C) 2018-  Lev Stipakov <lev@openvpn.net>
//  Copyright (C) 2024-  Răzvan Cojocaru <razvan.cojocaru@openvpn.com>
//

/**
 * @file configmgr-service.hpp
 *
 * @brief Declaration of the net.openvpn.v3.configuration D-Bus service
 */

#pragma once

#include <string>
#include <vector>
#include <gdbuspp/connection.hpp>
#include <gdbuspp/credentials/query.hpp>
#include <gdbuspp/object/base.hpp>
#include <gdbuspp/service.hpp>

#include "log/logwriter.hpp"
#include "log/proxy-log.hpp"
#include "common/utils.hpp"
#include "configmgr-configuration.hpp"
#include "configmgr-signals.hpp"


namespace ConfigManager {

class ConfigHandler : public DBus::Object::Base
{
  public:
    using Ptr = std::shared_ptr<ConfigHandler>;
    using ConfigCollection = std::vector<Configuration::Ptr>;

    /**
     *  method_fetch_available_configs() does the specific filtering
     *  via a search filter function.  This is a simple lambda function
     *  which will receive the ConfigManager::Configuration object to
     *  evaluate.  If this is to be included in the search result, the
     *  lambda need to return true.  It will be skipped if the function
     *  returns false.
     */
    using fn_search_filter = std::function<bool(Configuration::Ptr)>;

  public:
    /**
     *  Create the main handler object for the Configuration Manager service
     *
     * @param dbuscon         DBus::Connection where this object is created
     * @param object_manager  DBus::Object::Manager handling D-Bus configuration
     *                        profile objects
     * @param loglevel        uint8_t log verbosity level to use
     * @param logwr           LogWriter object handling all logging in the
     *                        service
     */
    ConfigHandler(DBus::Connection::Ptr dbuscon,
                  DBus::Object::Manager::Ptr object_manager,
                  uint8_t loglevel,
                  LogWriter::Ptr logwr);

  public:
    /**
     *  Authorization callback for D-Bus object access.
     *
     *  The service handler does not have any authorziation checks, so
     *  it will always return true.
     *
     * @param request   Authz::Request object
     * @return Will always return true for the service handler object
     */
    const bool Authorize(const DBus::Authz::Request::Ptr authzreq) override;

    /**
     *  Sets the directory where the configuration manager should store
     *  persistent configuration profiles.
     *
     *  When calling this function, all already saved configuration files
     *  will be imported and registered before continuing.
     *
     * @param state_dir  std::string containing the file system directory
     *                   for the persistent configuration profile storage
     */
    void SetStateDirectory(const std::string &state_dir);

  private:
    /**
     *  Get a list (std::vector<std::string>) of all persistent configuration
     *  files in the given directory.
     *
     *  The filtering is based on a course filename check.  Filenames must be
     *  41 characters long and must end with '.json'.  Only files and
     *  symbolic links are reported back.  It will not traverse any
     *  directories.
     *
     * @param directory  std::string of the directory where to find files
     *
     * @return  Returns a std::vector<std::string> of all matching files.
     *          Each entry will have the full path to the file.
     *
     * @throws  Throws DBusException if the directory cannot be opened
     */
    std::vector<std::string> get_persistent_config_file_list(const std::string &directory);

    /**
     *  Loads a persistent configuration file from the file system.  It
     *  will register the configuration with the D-Bus path provided in
     *  the file.
     *
     *  The file must be a JSON formatted text file based on the file
     *  format generated by @ConfigurationObject::Export()
     *
     * @param fname  std::string with the filename to import
     */
    void import_persistent_configuration(const std::string &fname);

    void method_import(DBus::Object::Method::Arguments::Ptr args);
    void method_fetch_available_configs(DBus::Object::Method::Arguments::Ptr args);
    void method_lookup_config_name(DBus::Object::Method::Arguments::Ptr args);
    void method_search_by_tag(DBus::Object::Method::Arguments::Ptr args);
    void method_search_by_owner(DBus::Object::Method::Arguments::Ptr args);
    void method_transfer_ownership(DBus::Object::Method::Arguments::Ptr args);

    /**
     *  Returns only those configurations which are accessable by caller,
     *  and for which also filter_fn returns true.
     *
     * @param caller      D-Bus object caller ID
     * @param filter_fn   Predicate filtering configuration objects
     * @param grant_root  Don't filter out root (uid 0) in the ACL check
     *                    if it is the caller
     *
     * @return A (possibly empty) container of configuration smart pointers
     */
    ConfigCollection helper_retrieve_configs(const std::string &caller,
                                             fn_search_filter &&filter_fn,
                                             bool grant_root = false) const;

  private:
    DBus::Connection::Ptr dbuscon_;
    DBus::Object::Manager::Ptr object_manager_;
    DBus::Credentials::Query::Ptr creds_qry_;
    std::string prop_version_{get_package_version()};
    ConfigManager::Log::Ptr signals_;
    ::Signals::ConfigurationManagerEvent::Ptr sig_configmgr_event_;
    std::string state_dir_;
    LogWriter::Ptr logwr_;
};



class Service : public DBus::Service
{
  public:
    Service(DBus::Connection::Ptr con, LogWriter::Ptr logwr, uint8_t loglevel);
    ~Service() noexcept;

    void BusNameAcquired(const std::string &busname) override;
    void BusNameLost(const std::string &busname) override;


    /**
     *  Enables the persistent storage feature.  This defines where
     *  these configurations will saved on the file system.  Calling this
     *  method will also trigger loading configuration profiles already stored
     *  in this directory.
     *
     * @param stdir  std::string containing the directory where to load and
     *               save persistent configuration profiles.
     */
    void SetStateDirectory(const std::string &stdir);

  private:
    DBus::Connection::Ptr con_;
    LogWriter::Ptr logwr_;
    LogServiceProxy::Ptr logsrvprx_;
    uint8_t loglevel_{3};
    ConfigHandler::Ptr config_handler_ = nullptr;
};

} // namespace ConfigManager
