// Copyright 2014 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/variations/active_field_trials.h"

#include <stddef.h>

#include <algorithm>
#include <string_view>
#include <vector>

#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/no_destructor.h"
#include "base/process/launch.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "components/variations/hashing.h"
#include "components/variations/synthetic_trials_active_group_id_provider.h"
#include "components/variations/variations_crash_keys.h"
#include "components/variations/variations_switches.h"

namespace variations {

namespace {

std::string& GetSeedVersionInternal() {
  static base::NoDestructor<std::string> seed_version;
  return *seed_version;
}

void AppendActiveGroupIdsAsStrings(
    const std::vector<ActiveGroupId> name_group_ids,
    std::vector<std::string>* output) {
  for (const auto& active_group_id : name_group_ids) {
    output->push_back(base::StringPrintf("%x-%x", active_group_id.name,
                                         active_group_id.group));
  }
}

uint32_t HashNameAndSuffix(std::string_view base_name,
                           std::string_view optional_suffix) {
  // Note that most of the time, suffixes are empty, so this avoids creating new
  // strings if not necessary.
  if (optional_suffix.empty()) {
    return HashName(base_name);
  }
  return HashName(base::StrCat({base_name, optional_suffix}));
}

uint32_t HashNameAndSuffix(std::string_view base_name,
                           std::string_view optional_suffix,
                           std::string_view optional_suffix2) {
  if (optional_suffix.empty() && optional_suffix2.empty()) {
    return HashName(base_name);
  }
  return HashName(base::StrCat({base_name, optional_suffix, optional_suffix2}));
}

ActiveGroupId MakeActiveGroupIdWithSuffix(std::string_view trial_name,
                                          std::string_view group_name,
                                          std::string_view optional_suffix,
                                          bool is_overridden) {
  ActiveGroupId id;
  id.name = HashNameAndSuffix(trial_name, optional_suffix);
  id.group =
      HashNameAndSuffix(group_name, optional_suffix,
                        is_overridden ? kOverrideSuffix : std::string_view());
  return id;
}

}  // namespace

ActiveGroupId MakeActiveGroupId(std::string_view trial_name,
                                std::string_view group_name,
                                bool is_overridden) {
  ActiveGroupId id;
  id.name = HashName(trial_name);
  id.group = !is_overridden ? HashName(group_name)
                            : HashNameAndSuffix(group_name, kOverrideSuffix);

  return id;
}

void GetFieldTrialActiveGroupIdsForActiveGroups(
    std::string_view suffix,
    const base::FieldTrial::ActiveGroups& active_groups,
    std::vector<ActiveGroupId>* name_group_ids) {
  DCHECK(name_group_ids->empty());
  for (const auto& active_group : active_groups) {
    ActiveGroupId group_id = MakeActiveGroupIdWithSuffix(
        active_group.trial_name, active_group.group_name, suffix,
        active_group.is_overridden);
    name_group_ids->push_back(std::move(group_id));
  }
}

void GetFieldTrialActiveGroupIds(std::string_view suffix,
                                 std::vector<ActiveGroupId>* name_group_ids,
                                 bool include_runtime_overrides) {
  DCHECK(name_group_ids->empty());
  // A note on thread safety: Since GetActiveFieldTrialGroups() is thread
  // safe, and we operate on a separate list of that data, this function is
  // technically thread safe as well, with respect to the FieldTrialList data.
  base::FieldTrial::ActiveGroups active_groups;
  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups,
                                                  include_runtime_overrides);
  GetFieldTrialActiveGroupIdsForActiveGroups(suffix, active_groups,
                                             name_group_ids);
}

void GetFieldTrialActiveGroupIds(
    std::string_view suffix,
    const base::FieldTrial::ActiveGroups& active_groups,
    std::vector<ActiveGroupId>* name_group_ids) {
  DCHECK(name_group_ids->empty());
  GetFieldTrialActiveGroupIdsForActiveGroups(suffix, active_groups,
                                             name_group_ids);
}

void GetFieldTrialActiveGroupIdsAsStrings(std::string_view suffix,
                                          std::vector<std::string>* output) {
  DCHECK(output->empty());
  std::vector<ActiveGroupId> name_group_ids;
  GetFieldTrialActiveGroupIds(suffix, &name_group_ids);
  AppendActiveGroupIdsAsStrings(name_group_ids, output);
}

void GetFieldTrialActiveGroupIdsAsStrings(
    std::string_view suffix,
    const base::FieldTrial::ActiveGroups& active_groups,
    std::vector<std::string>* output) {
  DCHECK(output->empty());
  std::vector<ActiveGroupId> name_group_ids;
  GetFieldTrialActiveGroupIds(suffix, active_groups, &name_group_ids);
  AppendActiveGroupIdsAsStrings(name_group_ids, output);
}

void GetSyntheticTrialGroupIdsAsString(std::vector<std::string>* output) {
  std::vector<ActiveGroupId> name_group_ids =
      SyntheticTrialsActiveGroupIdProvider::GetInstance()->GetActiveGroupIds();
  AppendActiveGroupIdsAsStrings(name_group_ids, output);
}

bool HasSyntheticTrial(std::string_view trial_name) {
  std::vector<std::string> synthetic_trials;
  variations::GetSyntheticTrialGroupIdsAsString(&synthetic_trials);
  std::string trial_hash = variations::HashNameAsHexString(trial_name);
  return std::ranges::any_of(synthetic_trials, [&trial_hash](
                                                   const auto& trial) {
    return base::StartsWith(trial, trial_hash, base::CompareCase::SENSITIVE);
  });
}

bool IsInSyntheticTrialGroup(std::string_view trial_name,
                             std::string_view trial_group) {
  std::vector<std::string> synthetic_trials;
  GetSyntheticTrialGroupIdsAsString(&synthetic_trials);
  return std::ranges::contains(
      synthetic_trials,
      base::StringPrintf("%x-%x", HashName(trial_name), HashName(trial_group)));
}

void SetSeedVersion(std::string_view seed_version) {
  GetSeedVersionInternal() = seed_version;
  SetVariationsSeedVersionCrashKey(seed_version);
}

const std::string& GetSeedVersion() {
  return GetSeedVersionInternal();
}

#if BUILDFLAG(USE_BLINK)
void PopulateLaunchOptionsWithVariationsInfo(
    base::shared_memory::SharedMemorySwitch* shared_memory_switch,
    base::CommandLine* command_line,
    base::LaunchOptions* launch_options) {
  base::FieldTrialList::PopulateLaunchOptionsWithFieldTrialState(
      shared_memory_switch, command_line, launch_options);
  command_line->AppendSwitchASCII(switches::kVariationsSeedVersion,
                                  GetSeedVersion());
}
#endif  // !BUILDFLAG(USE_BLINK)

namespace test {

void TestGetFieldTrialActiveGroupIds(
    std::string_view suffix,
    const base::FieldTrial::ActiveGroups& active_groups,
    std::vector<ActiveGroupId>* name_group_ids) {
  GetFieldTrialActiveGroupIdsForActiveGroups(suffix, active_groups,
                                             name_group_ids);
}

}  // namespace test
}  // namespace variations
