// Copyright 2023 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_METRICS_STRUCTURED_STRUCTURED_METRICS_RECORDER_H_
#define COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_RECORDER_H_

#include <deque>
#include <memory>
#include <optional>

#include "base/containers/enum_set.h"
#include "base/containers/flat_set.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "components/metrics/metrics_provider.h"
#include "components/metrics/structured/event.h"
#include "components/metrics/structured/lib/event_storage.h"
#include "components/metrics/structured/lib/key_data.h"
#include "components/metrics/structured/lib/key_data_provider.h"
#include "components/metrics/structured/project_validator.h"
#include "components/metrics/structured/recorder.h"
#include "third_party/metrics_proto/structured_data.pb.h"

namespace metrics::structured {

// StructuredMetricsRecorder is responsible for storing and managing the all
// Structured Metrics events recorded on-device.  This class is not thread safe
// and should only be called on the browser UI sequence, because calls from the
// metrics service come on the UI sequence.
//
// This is to be used as a base class for different platform implementations.
// The subclass will instantiate the desired KeyDataProvider and EventStorage.
//
// This class accepts events to record from
// StructuredMetricsRecorder::OnRecord via Recorder::Record via
// Event::Record. These events are not uploaded immediately, and are cached
// in ready-to-upload form.
class StructuredMetricsRecorder : public Recorder::RecorderImpl,
                                  KeyDataProvider::Observer {
 public:
  // Interface for watching for the recording of Structured Metrics Events.
  class Observer : public base::CheckedObserver {
   public:
    virtual void OnEventRecorded(const StructuredEventProto& event) = 0;
  };

  StructuredMetricsRecorder(
      std::unique_ptr<KeyDataProvider> key_data_provider,
      std::unique_ptr<EventStorage<StructuredEventProto>> event_storage);
  ~StructuredMetricsRecorder() override;
  StructuredMetricsRecorder(const StructuredMetricsRecorder&) = delete;
  StructuredMetricsRecorder& operator=(const StructuredMetricsRecorder&) =
      delete;

  // Manages whether or not Structured Metrics is recording.
  // If these functions are overloaded, make sure they are explicitly called in
  // the overriding function.
  virtual void EnableRecording();
  virtual void DisableRecording();

  void Purge();

  bool recording_enabled() const { return recording_enabled_; }

  void ProvideUmaEventMetrics(ChromeUserMetricsExtension& uma_proto);

  // Provides event metrics stored in the recorder into |uma_proto|.
  //
  // This calls OnIndependentMetrics() to populate |uma_proto| with metadata
  // fields.
  virtual void ProvideEventMetrics(ChromeUserMetricsExtension& uma_proto);

  // Returns true if ready to provide metrics via ProvideEventMetrics.
  bool CanProvideMetrics();

  // Returns true if there are metrics to provide.
  bool HasMetricsToProvide();

  // KeyDataProvider::Observer:
  void OnKeyReady() override;

  // Interface for adding and remove watchers.
  void AddEventsObserver(Observer* watcher);
  void RemoveEventsObserver(Observer* watcher);

  EventStorage<StructuredEventProto>* event_storage() {
    return event_storage_.get();
  }

  KeyDataProvider* key_data_provider() { return key_data_provider_.get(); }

 protected:
  friend class TestStructuredMetricsProvider;
  friend class StructuredMetricsMixin;

  // Recorder::RecorderImpl:
  void OnEventRecord(const Event& event) override;

  // Different initialization states for the recorder.
  enum State {
    kUninitialized,
    // Set once OnKeyReady has been called once.
    kKeyDataInitialized,
    // Set once the profile key data has been initialized.
    kProfileKeyDataInitialized,
    kMaxValue = kProfileKeyDataInitialized,
  };

  // Collection of InitValues that represents the current initialization state
  // of the recorder.
  //
  // For events to be persisted, both kKeyDataInitialized, kEventsInitialized,
  // and kProfileKeyDataInitialized msut be set for events to be recorded.
  using InitState =
      base::EnumSet<State, State::kUninitialized, State::kMaxValue>;

  bool HasState(State state) const;

 private:
  friend class Recorder;
  friend class StructuredMetricsMixin;
  friend class StructuredMetricsProviderTest;
  friend class StructuredMetricsRecorderTest;
  friend class StructuredMetricsRecorderHwidTest;
  friend class TestStructuredMetricsRecorder;
  friend class TestStructuredMetricsProvider;
  friend class StructuredMetricsServiceTest;

  // Records events before IsInitialized().
  void RecordEventBeforeInitialization(const Event& event);

  // Records events before IsProfileInitialized().
  void RecordProfileEventBeforeInitialization(const Event& event);

  // Records |event| to persistent disk to be eventually sent.
  void RecordEvent(const Event& event);

  // Sets the event and project fields and the identification fields.
  void InitializeEventProto(StructuredEventProto* proto,
                            const Event& event,
                            const ProjectValidator& project_validator,
                            const EventValidator& event_validator);

  // Processes the events metric to proto format.
  void AddMetricsToProto(StructuredEventProto* proto,
                         const Event& event,
                         const ProjectValidator& project_validator,
                         const EventValidator& validator);

  // Adds sequence metadata to the event.
  virtual void AddSequenceMetadata(StructuredEventProto* proto,
                                   const Event& event,
                                   const ProjectValidator& project_validator,
                                   const KeyData& key_data) {}

  // Hashes events and persists the events to disk. Should be called once |this|
  // has been initialized.
  void HashUnhashedEventsAndPersist();

  // Checks if |project_name_hash| can be uploaded.
  bool CanUploadProject(uint64_t project_name_hash) const;

  // Builds a cache of disallow projects from the Finch controlled variable.
  void CacheDisallowedProjectsSet();

  // Returns true if key data is ready to use.
  bool IsKeyDataInitialized();

  // Returns true if ready to record events.
  bool IsInitialized();

  // Returns true if ready to record profile events.
  bool IsProfileInitialized();

  // Returns whether the |event| can be recorded event if metrics is opted-out.
  // Note that uploading is still guarded by metrics opt-in state and that these
  // events will never be uploaded. In the event that a user opts-in, these
  // events will be purged.
  bool CanForceRecord(const Event& event) const;

  // Helper functions to determine scope of the event.
  bool IsDeviceEvent(const Event& event) const;
  bool IsProfileEvent(const Event& event) const;

  // Helper function to get the validators for |event|.
  std::optional<std::pair<const ProjectValidator*, const EventValidator*>>
  GetEventValidators(const Event& event) const;

  void SetOnReadyToRecord(base::OnceClosure callback);

  // Sets a callback to be made every time an event is recorded. This is exposed
  // so that tests can check if a specific event is recorded since recording
  // happens asynchronously.
  void SetEventRecordCallbackForTest(base::RepeatingClosure callback);

  // Adds a project to the diallowed list for testing.
  void AddDisallowedProjectForTest(uint64_t project_name_hash);

 protected:
  void NotifyEventRecorded(const StructuredEventProto& event);

  // Key data provider that provides device and profile keys.
  std::unique_ptr<KeyDataProvider> key_data_provider_;

  // Storage for events while on device.
  std::unique_ptr<EventStorage<StructuredEventProto>> event_storage_;

  // Whether the metrics provider has completed initialization. Initialization
  // occurs across OnProfileAdded and OnKeyReady. No incoming
  // events are recorded until initialization has succeeded.
  //
  // Execution is:
  //  - A profile is added.
  //  - OnProfileAdded is called, which constructs |storage_| and
  //    asynchronously reads events and keys are loaded.
  //
  // The metrics provider does not handle multiprofile: initialization happens
  // only once, for the first-logged-in account aka. primary user.
  //
  // After a profile is added, two files need to be read from disk:
  // per-profile keys and per-device keys. |init_count_| tracks
  // how many of these have been read and, when it reaches 2, we set
  // |init_state_| to kInitialized.
  InitState init_state_;

 private:
  // Tracks the recording state signalled to the metrics provider by
  // OnRecordingEnabled and OnRecordingDisabled. This is false until
  // OnRecordingEnabled is called, which sets it true if structured metrics'
  // feature flag is enabled.
  bool recording_enabled_ = false;

  // Store for events that were recorded before keys are loaded.
  std::deque<Event> unhashed_events_;

  // Store for events that were recorded before profile keys are loaded.
  std::deque<Event> unhashed_profile_events_;

  // A set of projects that are not allowed to be recorded. This is a cache of
  // GetDisabledProjects().
  base::flat_set<uint64_t> disallowed_projects_;

  base::ObserverList<Observer> watchers_;

  // Callbacks for tests whenever an event is recorded.
  base::RepeatingClosure test_callback_on_record_ = base::DoNothing();

  // Callback to be made once recorder is ready to persist events to disk.
  base::OnceClosure on_ready_callback_ = base::DoNothing();

  base::WeakPtrFactory<StructuredMetricsRecorder> weak_factory_{this};
};
}  // namespace metrics::structured

#endif  // COMPONENTS_METRICS_STRUCTURED_STRUCTURED_METRICS_RECORDER_H_
