// Copyright 2020 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_VARIATIONS_VARIATIONS_TEST_UTILS_H_
#define COMPONENTS_VARIATIONS_VARIATIONS_TEST_UTILS_H_

#include <set>
#include <string>

#include "base/memory/raw_span.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/field_trial.h"
#include "base/test/mock_entropy_provider.h"
#include "components/variations/active_field_trials.h"
#include "components/variations/client_filterable_state.h"
#include "components/variations/entropy_provider.h"
#include "components/variations/field_trial_config/fieldtrial_testing_config.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "components/variations/seed_reader_writer.h"
#include "components/variations/synthetic_trial_registry.h"
#include "components/variations/variations_associated_data.h"

namespace base {
class FilePath;
}  // namespace base

class PrefService;

namespace variations {

struct ClientFilterableState;

// Packages signed variations seed data into a tuple for use with
// WriteSignedSeedData(). This allows for encapsulated seed information to be
// created below for generic test seeds as well as seeds which cause crashes.
//
// Note: To manually get the raw data, you can use the following command:
// echo -n base64_compressed_data | base64 -d | hexdump -e '8 1 ", 0x%x"'
struct SignedSeedData {
  // Names of all studies in the seed.
  base::raw_span<const char*> study_names;
  const char* base64_uncompressed_data;
  const char* base64_compressed_data;
  const char* base64_signature;

  // Out-of-line ctor/dtor/copy/move required for 'complex' classes.
  SignedSeedData(base::raw_span<const char*> in_study_names,
                 const char* in_base64_uncompressed_data,
                 const char* in_base64_compressed_data,
                 const char* in_base64_signature);
  ~SignedSeedData();
  SignedSeedData(const SignedSeedData&);
  SignedSeedData(SignedSeedData&&);
  SignedSeedData& operator=(const SignedSeedData&);
  SignedSeedData& operator=(SignedSeedData&&);
};

// Packages variations seed pref keys into a tuple for use with StoreSeedInfo().
// This allow easily writing signed seed data into either the safe seed or
// regular seed locations in Local State.
struct SignedSeedPrefKeys {
  const char* base64_compressed_data_key;
  const char* base64_signature_key;
};

// The test seed data is associated with a VariationsSeed with one study,
// "UMA-Uniformity-Trial-10-Percent", and ten equally weighted groups: "default"
// and "group_01" through "group_09". The study is not associated with channels,
// platforms, or features.
const SignedSeedData& TestSeedData();

// The crashing seed data contains a CrashingStudy that enables the
// variations::kForceFieldTrialSetupCrashForTesting feature at 100% on all
// platforms and on all channels except Unknown.
const SignedSeedData& CrashingSeedData();

// The pref keys used to store safe signed variations seed data.
extern const SignedSeedPrefKeys kSafeSeedPrefKeys;

// The pref keys used to store regular signed variations seed data.
extern const SignedSeedPrefKeys kRegularSeedPrefKeys;

// Mock field trial testing config.
extern const FieldTrialTestingConfig kTestingConfig;

// Disables the use of the field trial testing config to exercise
// VariationsFieldTrialCreator::CreateTrialsFromSeed().
void DisableTestingConfig();

// Enables the use of the field trial testing config.
void EnableTestingConfig();

// Decodes the variations header and extracts the variation ids.
bool ExtractVariationIds(const std::string& variations,
                         std::set<VariationID>* variation_ids,
                         std::set<VariationID>* trigger_ids);

// Creates an inactive FieldTrial, `trial_name`, where the client is assigned to
// `group_name`, and associates a VariationID for the trial using the given
// `key`, `id` and optional `time_window`.
scoped_refptr<base::FieldTrial> CreateInactiveTrialAndAssociateId(
    const std::string& trial_name,
    const std::string& group_name,
    IDCollectionKey key,
    VariationID id,
    TimeWindow time_window = TimeWindow());

// Creates an active FieldTrial, `trial_name`, where the client is assigned to
// `group_name`, and associates a VariationID for the trial using the given
// `key`, `id` and optional `time_window`.
scoped_refptr<base::FieldTrial> CreateTrialAndAssociateId(
    const std::string& trial_name,
    const std::string& group_name,
    IDCollectionKey key,
    VariationID id,
    TimeWindow time_window = TimeWindow());

// Simulates a crash by setting the clean exit pref to false and disabling
// the steps to update the pref on clean shutdown.
void SimulateCrash(PrefService* local_state);

// Writes |seed_info| into |local_state| using the given seed |pref_keys|.
void WriteSignedSeedData(PrefService* local_state,
                         const SignedSeedData& seed_data,
                         const SignedSeedPrefKeys& pref_keys);

// Writes the seed to both Local State and a seed file.
void WriteSeedData(const base::FilePath& user_data_dir,
                   const VariationsSeed& seed);

// Returns true if all of the study_names listed in |seed_data| exist in the
// (global) field trial list.
bool FieldTrialListHasAllStudiesFrom(const SignedSeedData& seed_data);

// Resets variations. Ensures that maps can be cleared between tests since they
// are stored as process singleton.
void ResetVariations();

// Create a ClientFilterableState with valid, but unimportant values.
// Tests that actually expect specific values should set them on the result.
std::unique_ptr<ClientFilterableState> CreateDummyClientFilterableState();

// An mock entropy result that will always pick the first non-zero weight group.
constexpr double kAlwaysUseFirstGroup = 0;
// An mock entropy result that will always pick the last non-zero weight group.
constexpr double kAlwaysUseLastGroup = 1.0 - 1e-8;

// EntropyProviders that return known values.
class MockEntropyProviders : public EntropyProviders {
 public:
  struct Results {
    double low_entropy = kAlwaysUseLastGroup;
    std::optional<double> high_entropy = std::nullopt;
    std::optional<double> limited_entropy = std::nullopt;
  };
  explicit MockEntropyProviders(Results results,
                                uint32_t low_entropy_domain = 8000);
  ~MockEntropyProviders() override;

  const base::FieldTrial::EntropyProvider& low_entropy() const override;
  const base::FieldTrial::EntropyProvider& default_entropy() const override;
  const base::FieldTrial::EntropyProvider& limited_entropy() const override;

 private:
  base::MockEntropyProvider low_provider_;
  base::MockEntropyProvider high_provider_;
  base::MockEntropyProvider limited_provider_;
};

// Returns a hex string of the GZipped, base64 encoded, and serialized seed.
std::string GZipAndB64EncodeToHexString(const VariationsSeed& seed);

// Returns whether the active group ids includes the given trial name.
bool ContainsTrialName(const std::vector<ActiveGroupId>& active_group_ids,
                       std::string_view trial_name);

// Returns whether the active group ids includes the given trial name with the
// given group name.
bool ContainsTrialAndGroupName(
    const std::vector<ActiveGroupId>& active_group_ids,
    std::string_view trial_name,
    std::string_view group_name);

// Sets up the seed file experiment where `group_name` is the active group.
void SetUpSeedFileTrial(std::string_view group_name);

// Returns true if there are no adjacent elements (a, b) when iterating over
// `container` such that a >= b.
template <typename Container>
bool IsSortedAndUnique(const Container& container) {
  return std::adjacent_find(container.begin(), container.end(),
                            [](const typename Container::value_type& a,
                               const typename Container::value_type& b) {
                              return a >= b;
                            }) == container.end();
}

}  // namespace variations

#endif  // COMPONENTS_VARIATIONS_VARIATIONS_TEST_UTILS_H_
