// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_REPORTING_CLIENT_REPORT_QUEUE_CONFIGURATION_H_
#define COMPONENTS_REPORTING_CLIENT_REPORT_QUEUE_CONFIGURATION_H_

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/functional/callback.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/proto/synced/record_constants.pb.h"
#include "components/reporting/util/rate_limiter_interface.h"
#include "components/reporting/util/status.h"
#include "components/reporting/util/statusor.h"
#include "components/reporting/util/wrapped_rate_limiter.h"

namespace reporting {

// |EventType| enum is used to distinguish between user and device event types,
// and inherently determine the type of DM tokens (user vs device) generated.
enum class EventType { kDevice, kUser };

// ReportQueueConfiguration configures a report queue.
// |dm_token| if set will be attached to all records generated with this queue.
// Pass user DM tokens where applicable so the server can associate these events
// with the user. |event_type| describes the event type being reported and is
// indirectly used to retrieve DM tokens for downstream processing. Please use
// |EventType::kUser| for events that need to be associated with the current
// user. |destination| indicates what server side handler will be handling the
// records that are generated by the ReportQueueImpl. |policy_check_callback_|
// is a RepeatingCallback that verifies the specific report queue is allowed.
class ReportQueueConfiguration {
 public:
  // PolicyCheckCallbacks should return error::UNAUTHENTICATED if a policy check
  // fails due to policies, any other error as appropriate, and OK if a policy
  // check is successful.
  using PolicyCheckCallback = base::RepeatingCallback<Status(void)>;

  // Transient settings used by `ReportQueueConfiguration` instantiation.
  struct Settings {
    EventType event_type = EventType::kDevice;
    Destination destination = Destination::UNDEFINED_DESTINATION;
    int64_t reserved_space = 0L;
  };

  // Transient moveable helper class for composing `ReportQueueConfiguration`
  // out of settings and then allowing to set non-trivial fields.
  class Builder {
   public:
    explicit Builder(const Settings& settings);
    Builder(Builder&&);
    Builder& operator=(const Builder&) = delete;
    ~Builder();

    // Modifiers for non-trivial fields.
    Builder SetPolicyCheckCallback(PolicyCheckCallback policy_check_callback);
    Builder SetRateLimiter(std::unique_ptr<RateLimiterInterface> rate_limiter);
    Builder SetDMToken(std::string_view dm_token);
    Builder SetSourceInfo(std::optional<SourceInfo> source_info);

    // Finalizes the builder (no modifications are accepted after that) and
    // outputs the final `ReportQueueConfiguration` or status.
    StatusOr<std::unique_ptr<ReportQueueConfiguration>> Build();

   private:
    StatusOr<std::unique_ptr<ReportQueueConfiguration>> final_value_;
  };

  ~ReportQueueConfiguration();
  ReportQueueConfiguration(const ReportQueueConfiguration& other) = delete;
  ReportQueueConfiguration& operator=(const ReportQueueConfiguration& other) =
      delete;

  // Factory for generating a `ReportQueueConfiguration`.
  // The factory produces `Builder` thus allowing to set non-trivial fields.
  // Once everything is set, `Builder` can be assigned (once!) using
  // cast operator to `StatusOr<std::unique_ptr<ReportQueueConfiguration>>()`.
  //
  // Example usage:
  //   StatusOr<reporting::ReportQueueConfiguration> config_result =
  //       reporting::ReportQueueConfiguration::Create(
  //           {.event_type = EventType::kUser,
  //            .destination = Destination::HEART_BEAT})
  //           .SetRateLimiter(std::make_unique<RateLimiterTokenBucket>(
  //               /*max_level=*/1024,
  //               /*filling_time=*/base::Minutes(10)))
  //           .Build();
  //   if (!config_result.has_value()) {
  //     return config_result.error();
  //   }
  //   auto config = config_result.value();
  static Builder Create(const Settings& settings);

  // Deprecated and should not be used. Use `Create({settings})` instead.
  //
  // Factory for generating a ReportQueueConfiguration.
  // If any of the parameters are invalid, will return error::INVALID_ARGUMENT.
  // `dm_token` is valid when dm_token.is_valid() is true.
  // `destination` is valid when it is any value other than
  // Destination::UNDEFINED_DESTINATION.
  // If `reserved_space` > 0, underlying Storage would only accept enqueue
  // request if after it remaining amount of disk space will not drop below
  // `reserved_space`. Intended for use by opportunistic queue.
  static StatusOr<std::unique_ptr<ReportQueueConfiguration>> Create(
      std::string_view dm_token,
      Destination destination,
      PolicyCheckCallback policy_check_callback,
      std::unique_ptr<RateLimiterInterface> rate_limiter = nullptr,
      int64_t reserved_space = 0L);

  Destination destination() const { return destination_; }

  std::string dm_token() { return dm_token_; }

  EventType event_type() const { return event_type_; }

  // Returns a repeating callback that checks whether an event of the given size
  // is allowed by the attached rate limiter. If there is no rate limiter,
  // allowed always.
  WrappedRateLimiter::AsyncAcquireCb is_event_allowed_cb() const {
    return is_event_allowed_cb_;
  }

  int64_t reserved_space() const { return reserved_space_; }

  std::optional<SourceInfo> source_info() const { return source_info_; }

  Status SetDMToken(std::string_view dm_token);

  Status CheckPolicy() const;

 private:
  friend class Builder;

  ReportQueueConfiguration();

  Status SetEventType(EventType event_type);
  Status SetDestination(Destination destination);
  Status SetReservedSpace(int64_t reserved_space);
  Status SetPolicyCheckCallback(PolicyCheckCallback policy_check_callback);
  Status SetRateLimiter(std::unique_ptr<RateLimiterInterface> rate_limiter);
  Status SetSourceInfo(std::optional<SourceInfo> source_info);

  std::string dm_token_;
  EventType event_type_;
  Destination destination_;

  PolicyCheckCallback policy_check_callback_;

  WrappedRateLimiter::SmartPtr wrapped_rate_limiter_{
      nullptr, base::OnTaskRunnerDeleter(nullptr)};
  WrappedRateLimiter::AsyncAcquireCb is_event_allowed_cb_;

  int64_t reserved_space_ = 0L;  // By default queues are not opportunistic.
  std::optional<SourceInfo> source_info_ = std::nullopt;
};

}  // namespace reporting

#endif  // COMPONENTS_REPORTING_CLIENT_REPORT_QUEUE_CONFIGURATION_H_
