// 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.

#include "components/metrics/debug/structured/structured_metrics_debug_provider.h"

#include <optional>
#include <utility>

#include "base/i18n/number_formatting.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "components/metrics/structured/event_validator.h"
#include "components/metrics/structured/project_validator.h"
#include "components/metrics/structured/proto/event_storage.pb.h"
#include "components/metrics/structured/recorder.h"
#include "components/metrics/structured/structured_metrics_service.h"
#include "components/metrics/structured/structured_metrics_validator.h"
#include "third_party/metrics_proto/structured_data.pb.h"

namespace metrics::structured {
namespace {

struct EventInfo {
  base::StringPiece project_name;
  base::StringPiece event_name;
  raw_ptr<const EventValidator> event_validator;

  // Normalizes the name into an easier to read format as defined in the
  // structured.xml.
  std::string NormalizeProjectName() const;
  std::string NormalizeEventName() const;
};

std::string Normalize(base::StringPiece value) {
  std::string result;
  base::ReplaceChars(value, "_", ".", &result);
  return result;
}

std::string EventInfo::NormalizeProjectName() const {
  return Normalize(project_name);
}

std::string EventInfo::NormalizeEventName() const {
  return Normalize(event_name);
}

// Retrieves information about an event that is needed for rendering.
std::optional<EventInfo> GetEventInfo(const StructuredEventProto& proto) {
  validator::Validators* validators = validator::Validators::Get();
  auto project_name = validators->GetProjectName(proto.project_name_hash());
  if (!project_name.has_value()) {
    return std::nullopt;
  }

  // This will not fail.
  const auto* project_validator =
      validators->GetProjectValidator(*project_name);
  CHECK(project_validator);

  const auto event_name =
      project_validator->GetEventName(proto.event_name_hash());
  if (!event_name.has_value()) {
    return std::nullopt;
  }

  // This will not fail.
  const auto* event_validator =
      project_validator->GetEventValidator(*event_name);
  CHECK(event_validator);

  return EventInfo{.project_name = *project_name,
                   .event_name = *event_name,
                   .event_validator = event_validator};
}

// Creates a dictionary that represents a key-value pair.
base::Value::Dict CreateKeyValue(base::StringPiece key, base::Value value) {
  base::Value::Dict result;
  result.Set("key", key);
  result.Set("value", std::move(value));
  return result;
}

std::optional<base::Value> MetricToValue(
    const StructuredEventProto::Metric& metric) {
  using Metric = StructuredEventProto::Metric;
  switch (metric.value_case()) {
    case Metric::kValueHmac:
      return base::Value(base::NumberToString(metric.value_hmac()));
    case Metric::kValueInt64:
      return base::Value(base::NumberToString(metric.value_int64()));
    case Metric::kValueString:
      return base::Value(metric.value_string());
    case Metric::kValueDouble:
      return base::Value(metric.value_double());
    case Metric::kValueRepeatedInt64: {
      base::Value::List list;
      for (int value : metric.value_repeated_int64().values()) {
        list.Append(value);
      }
      return base::Value(std::move(list));
    }
    case Metric::VALUE_NOT_SET:
      return std::nullopt;
  }
}

// Creates a list of metrics represented by a key-value pair from the metrics of
// an event.
base::Value::List CreateMetricsList(const google::protobuf::RepeatedPtrField<
                                        StructuredEventProto::Metric>& metrics,
                                    const EventValidator* event_validator) {
  base::Value::List result;
  for (const auto& metric : metrics) {
    std::string metric_name =
        event_validator
            ? std::string(event_validator->GetMetricName(metric.name_hash())
                              .value_or("unknown"))
            : base::NumberToString(metric.name_hash());
    auto value = MetricToValue(metric);
    if (!value.has_value()) {
      continue;
    }
    result.Append(CreateKeyValue(metric_name, std::move(*value)));
  }
  return result;
}

// Creates an event metadata dictionary from an event.
base::Value::Dict CreateEventMetadataDict(
    const StructuredEventProto::EventSequenceMetadata& sequence_metadata) {
  base::Value::Dict metadata;
  metadata.Set("systemUptimeMs",
               base::FormatNumber(sequence_metadata.system_uptime()));
  metadata.Set("id", base::NumberToString(sequence_metadata.event_unique_id()));
  metadata.Set("resetCounter",
               base::NumberToString(sequence_metadata.reset_counter()));
  return metadata;
}

// Creates a dictionary from an event.
base::Value::Dict CreateEventDict(const StructuredEventProto& proto) {
  base::Value::Dict result;

  auto event_info = GetEventInfo(proto);
  const EventValidator* event_validator = nullptr;

  if (event_info.has_value()) {
    event_validator = event_info->event_validator;
    result.Set("project", event_info->NormalizeProjectName());
    result.Set("event", event_info->NormalizeEventName());
  } else {
    result.Set("project", base::NumberToString(proto.project_name_hash()));
    result.Set("event", base::NumberToString(proto.event_name_hash()));
  }

  result.Set("metrics", CreateMetricsList(proto.metrics(), event_validator));

  if (proto.event_type() == StructuredEventProto::SEQUENCE) {
    result.Set("type", "sequence");
    result.Set("sequenceMetadata",
               CreateEventMetadataDict(proto.event_sequence_metadata()));
  } else {
    result.Set("type", "metric");
  }

  return result;
}

}  // namespace

StructuredMetricsDebugProvider::StructuredMetricsDebugProvider(
    StructuredMetricsService* service)
    : service_(service) {
  CHECK(service);
  LoadRecordedEvents();
  service_->recorder()->AddEventsObserver(this);
}

StructuredMetricsDebugProvider::~StructuredMetricsDebugProvider() {
  service_->recorder()->RemoveEventsObserver(this);
}

void StructuredMetricsDebugProvider::OnEventRecorded(
    const StructuredEventProto& event) {
  events_.Append(CreateEventDict(event));
}

void StructuredMetricsDebugProvider::LoadRecordedEvents() {
  EventsProto proto;
  service_->recorder()->event_storage()->CopyEvents(&proto);
  for (const auto& event : proto.events()) {
    events_.Append(CreateEventDict(event));
  }
}

}  // namespace metrics::structured
