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

#include "components/metrics/dwa/dwa_recorder.h"

#include <algorithm>
#include <numeric>

#include "base/feature_list.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/metrics_hashes.h"
#include "base/no_destructor.h"

namespace metrics::dwa {

BASE_FEATURE(kDwaFeature, "DwaFeature", base::FEATURE_DISABLED_BY_DEFAULT);

BASE_FEATURE(kPrivateMetricsFeature,
             "PrivateMetricsFeature",
             base::FEATURE_DISABLED_BY_DEFAULT);

namespace {

// Populates |dwa_event|.field_trials with the field trial/group name hashes
// for the field_trials we are interested in, |studies_of_interest|.
// |active_field_trial_groups| contains a mapping of field trial names to
// group names that we are currently part of.
void PopulateFieldTrialsForDwaEvent(
    const base::flat_map<std::string, bool>& studies_of_interest,
    const std::unordered_map<std::string, std::string>&
        active_field_trial_groups,
    ::dwa::DeidentifiedWebAnalyticsEvent& dwa_event) {
  // Determine the study groups that is part of |studies_of_interest| in the
  // current session. If we are in a study part of |study_of_interest| in the
  // current session, Hash the study and group names and populate a new repeated
  // field_trials in |dwa_event|.
  for (const auto& [trial_name, _] : studies_of_interest) {
    auto it = active_field_trial_groups.find(trial_name);
    if (it != active_field_trial_groups.end()) {
      const auto& group_name = it->second;
      ::metrics::SystemProfileProto::FieldTrial* field_trial =
          dwa_event.add_field_trials();
      field_trial->set_name_id(base::HashFieldTrialName(trial_name));
      field_trial->set_group_id(base::HashFieldTrialName(group_name));
    }
  }
}

// Takes |raw_entries_metrics|, a vector of metric_hash to metric_value maps,
// and returns it as a vector of EntryMetrics as defined in
// deidentified_web_analytics.proto.
std::vector<::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics>
TransformEntriesMetrics(
    const std::vector<base::flat_map<uint64_t, int64_t>>& raw_entries_metrics) {
  std::vector<::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics>
      entries_metrics;
  entries_metrics.reserve(raw_entries_metrics.size());

  for (const auto& raw_entry_metrics : raw_entries_metrics) {
    ::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics
        entry_metric;
    for (const auto& [metric_hash, metric_value] : raw_entry_metrics) {
      ::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics::Metric*
          metric = entry_metric.add_metric();
      metric->set_name_hash(metric_hash);
      metric->set_value(metric_value);
    }
    entries_metrics.push_back(std::move(entry_metric));
  }

  return entries_metrics;
}

// Takes a vector of entries, aggregates them, and then returns a vector of
// dwa events. The contents of |entries| are moved in this function and
// should not be used after.
std::vector<::dwa::DeidentifiedWebAnalyticsEvent> BuildDwaEvents(
    const std::vector<::metrics::dwa::mojom::DwaEntryPtr>& entries) {
  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
  std::unordered_map<std::string, std::string> active_field_trial_groups;

  for (const auto& active_group : active_groups) {
    active_field_trial_groups.insert(
        std::make_pair(active_group.trial_name, active_group.group_name));
  }

  // Maps {event_hash: {content_hash: vector<metrics>}}.
  std::unordered_map<
      uint64_t, std::unordered_map<
                    uint64_t, std::vector<base::flat_map<uint64_t, int64_t>>>>
      dwa_events_aggregation;
  // Maps {event_hash: vector<field_trials>}.
  std::unordered_map<uint64_t, base::flat_map<std::string, bool>>
      dwa_events_field_trials;

  for (const auto& entry : entries) {
    dwa_events_aggregation[entry->event_hash][entry->content_hash].push_back(
        std::move(entry->metrics));
    dwa_events_field_trials.try_emplace(entry->event_hash,
                                        std::move(entry->studies_of_interest));
  }

  std::vector<::dwa::DeidentifiedWebAnalyticsEvent> dwa_events;

  for (const auto& [event_hash, content_and_metrics] : dwa_events_aggregation) {
    ::dwa::DeidentifiedWebAnalyticsEvent event;

    event.set_event_hash(event_hash);
    for (const auto& [content_hash, raw_entries_metrics] :
         content_and_metrics) {
      ::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric* content_metric =
          event.add_content_metrics();
      content_metric->set_content_type(::dwa::DeidentifiedWebAnalyticsEvent::
                                           ContentMetric::CONTENT_TYPE_URL);
      content_metric->set_content_hash(content_hash);

      std::vector<
          ::dwa::DeidentifiedWebAnalyticsEvent::ContentMetric::EntryMetrics>
          entries_metrics = TransformEntriesMetrics(raw_entries_metrics);
      content_metric->mutable_metrics()->Add(
          std::make_move_iterator(entries_metrics.begin()),
          std::make_move_iterator(entries_metrics.end()));
    }

    PopulateFieldTrialsForDwaEvent(dwa_events_field_trials[event_hash],
                                   active_field_trial_groups, event);

    dwa_events.push_back(std::move(event));
  }

  return dwa_events;
}

}  // namespace

DwaRecorder::DwaRecorder() = default;

DwaRecorder::~DwaRecorder() = default;

void DwaRecorder::EnableRecording() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  recorder_enabled_ = IsDwaOrPrivateMetricsFeatureEnabled();
}

void DwaRecorder::DisableRecording() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  recorder_enabled_ = false;
}

void DwaRecorder::Purge() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  entries_.clear();
}

bool DwaRecorder::IsEnabled() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return recorder_enabled_;
}

// static
DwaRecorder* DwaRecorder::Get() {
  static base::NoDestructor<DwaRecorder> recorder;
  return recorder.get();
}

void DwaRecorder::AddEntry(metrics::dwa::mojom::DwaEntryPtr entry) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!recorder_enabled_) {
    return;
  }

  entries_.push_back(std::move(entry));
}

bool DwaRecorder::HasEntries() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return !entries_.empty();
}

std::vector<::dwa::DeidentifiedWebAnalyticsEvent> DwaRecorder::TakeDwaEvents() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // No entries, so there's nothing to do.
  if (entries_.empty()) {
    return std::vector<::dwa::DeidentifiedWebAnalyticsEvent>();
  }

  std::vector<::dwa::DeidentifiedWebAnalyticsEvent> dwa_events =
      BuildDwaEvents(entries_);
  entries_.clear();

  return dwa_events;
}

const std::vector<metrics::dwa::mojom::DwaEntryPtr>&
DwaRecorder::GetEntriesForTesting() const {
  return entries_;
}

// static
bool DwaRecorder::IsDwaOrPrivateMetricsFeatureEnabled() {
  return base::FeatureList::IsEnabled(kDwaFeature) ||
         base::FeatureList::IsEnabled(kPrivateMetricsFeature);
}

}  // namespace metrics::dwa
