// Copyright 2018 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/ukm/debug/ukm_debug_data_extractor.h"

#include <inttypes.h>

#include <map>
#include <string>
#include <utility>
#include <vector>

#include "base/format_macros.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "build/blink_buildflags.h"
#include "build/build_config.h"
#include "services/metrics/public/cpp/ukm_decode.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/metrics/public/mojom/ukm_interface.mojom.h"
#include "url/gurl.h"

#if BUILDFLAG(USE_BLINK)
#include "third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom-shared.h"
#include "third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom.h"
#endif

namespace ukm {
namespace debug {

namespace {

static const uint64_t BIT_FILTER_LAST32 = 0xffffffffULL;

struct SourceData {
  raw_ptr<UkmSource> source;
  std::vector<raw_ptr<mojom::UkmEntry, VectorExperimental>> entries;
#if BUILDFLAG(USE_BLINK)
  raw_ptr<const BitSet> webdx_features;
#endif
};

std::string GetName(const ukm::builders::EntryDecoder& decoder, uint64_t hash) {
  const auto it = decoder.metric_map.find(hash);
  if (it == decoder.metric_map.end())
    return base::StringPrintf("Unknown %" PRIu64, hash);
  return it->second;
}

base::DictValue ConvertEntryToDict(const ukm::builders::DecodeMap& decode_map,
                                   const mojom::UkmEntry& entry) {
  base::DictValue entry_dict;

  const auto it = decode_map.find(entry.event_hash);
  if (it == decode_map.end()) {
    entry_dict.Set("name",
                   UkmDebugDataExtractor::UInt64AsPairOfInt(entry.event_hash));
  } else {
    entry_dict.Set("name", it->second.name);

    base::ListValue metrics_list;
    for (const auto& metric : entry.metrics) {
      base::DictValue metric_dict;
      metric_dict.Set("name", GetName(it->second, metric.first));
      metric_dict.Set("value",
                      UkmDebugDataExtractor::UInt64AsPairOfInt(metric.second));
      metrics_list.Append(std::move(metric_dict));
    }
    entry_dict.Set("metrics", std::move(metrics_list));
  }
  return entry_dict;
}

}  // namespace

UkmDebugDataExtractor::UkmDebugDataExtractor() = default;

UkmDebugDataExtractor::~UkmDebugDataExtractor() = default;

// static
base::Value UkmDebugDataExtractor::UInt64AsPairOfInt(uint64_t v) {
  // Convert int64_t to pair of int. Passing int64_t in base::Value is not
  // supported. The pair of int will be passed as a List.
  base::ListValue int_pair;
  int_pair.Append(static_cast<int>((v >> 32) & BIT_FILTER_LAST32));
  int_pair.Append(static_cast<int>(v & BIT_FILTER_LAST32));
  return base::Value(std::move(int_pair));
}

// static
base::Value UkmDebugDataExtractor::GetStructuredData(
    const UkmService* ukm_service) {
  if (!ukm_service)
    return {};

  base::DictValue ukm_data;

  ukm_data.Set("state", ukm_service->recording_enabled_);
  ukm_data.Set("msbb_state", ukm_service->recording_enabled(MSBB));
  ukm_data.Set("extension_state", ukm_service->recording_enabled(EXTENSIONS));
  ukm_data.Set("app_state", ukm_service->recording_enabled(APPS));
  ukm_data.Set("client_id",
               base::StringPrintf("%016" PRIx64, ukm_service->client_id_));
  ukm_data.Set("session_id", static_cast<int>(ukm_service->session_id_));

  ukm_data.Set("is_sampling_enabled",
               static_cast<bool>(ukm_service->IsSamplingConfigured()));

  std::map<SourceId, SourceData> source_data;
  for (const auto& kv : ukm_service->recordings_.sources) {
    source_data[kv.first].source = kv.second.get();
  }

  for (const auto& v : ukm_service->recordings_.entries) {
    source_data[v->source_id].entries.push_back(v.get());
  }

#if BUILDFLAG(USE_BLINK)
  for (const auto& kv : ukm_service->recordings_.webdx_features) {
    source_data[kv.first].webdx_features = &kv.second;
  }
#endif

  base::ListValue sources_list;
  for (const auto& kv : source_data) {
    const auto* src = kv.second.source.get();

    base::DictValue source_dict;
    if (src) {
      source_dict.Set("id",
                      UkmDebugDataExtractor::UInt64AsPairOfInt(src->id()));
      source_dict.Set("type", GetSourceIdTypeDebugString(src->id()));
      source_dict.Set("url", base::Value(src->url().spec()));
      if (src->urls().size() > 1) {
        auto redirects_list =
            base::ListValue::with_capacity(src->urls().size());
        for (const auto& url : src->urls()) {
          redirects_list.Append(url.spec());
        }
        source_dict.Set("redirects", std::move(redirects_list));
      }
    } else {
      source_dict.Set("id", UkmDebugDataExtractor::UInt64AsPairOfInt(kv.first));
      source_dict.Set("type", GetSourceIdTypeDebugString(kv.first));
    }

    base::ListValue entries_list;
    for (ukm::mojom::UkmEntry* entry : kv.second.entries) {
      entries_list.Append(
          ConvertEntryToDict(ukm_service->GetDecodeMap(), *entry));
    }

    source_dict.Set("events", std::move(entries_list));

#if BUILDFLAG(USE_BLINK)
    if (kv.second.webdx_features) {
      base::ListValue webdx_features_list;
      for (size_t i = 0; i < kv.second.webdx_features->set_size(); ++i) {
        if (kv.second.webdx_features->Contains(i)) {
          // WebDX feature enum names are prefixed with "k" -- omit them.
          std::string feature_name =
              base::ToString(static_cast<blink::mojom::WebDXFeature>(i))
                  .substr(1);
          webdx_features_list.Append(base::Value(std::move(feature_name)));
        }
      }
      source_dict.Set("webdx_features", std::move(webdx_features_list));
    }
#endif

    sources_list.Append(std::move(source_dict));
  }
  ukm_data.Set("sources", std::move(sources_list));
  return base::Value(std::move(ukm_data));
}

}  // namespace debug
}  // namespace ukm
