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

#include <memory>

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "components/metrics/entropy_state.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/metrics/test/test_enabled_state_provider.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/client_filterable_state.h"
#include "components/variations/entropy_provider.h"
#include "components/variations/fuzzers/create_trials_from_seed_test_case.pb.h"
#include "components/variations/fuzzers/create_trials_from_seed_test_case_fuzzable.pb.h"
#include "components/variations/proto/study.pb.h"
#include "components/variations/service/limited_entropy_randomization.h"
#include "components/variations/variations_layers.h"
#include "components/variations/variations_seed_processor.h"
#include "components/variations/variations_test_utils.h"
#include "testing/libfuzzer/proto/lpm_interface.h"

namespace variations {
namespace {

struct Environment {
  Environment() : enabled_state_provider(/*consent=*/true, /*enabled=*/true) {
    base::CommandLine::Init(0, nullptr);
    metrics::MetricsStateManager::RegisterPrefs(pref_service.registry());
  }

  base::AtExitManager at_exit_manager;
  TestingPrefServiceSimple pref_service;
  metrics::TestEnabledStateProvider enabled_state_provider;
};

std::unique_ptr<ClientFilterableState> CreateClientFilterableState(
    const CreateTrialsFromSeedTestCase::ClientFilterableState& spec) {
  auto client_state = std::make_unique<ClientFilterableState>(
      base::BindOnce([] { return false; }),
      base::BindLambdaForTesting([spec]() {
        return base::flat_set<uint64_t>(spec.google_groups().begin(),
                                        spec.google_groups().end());
      }),
      base::BindLambdaForTesting([spec]() {
        return base::flat_set<std::string>(spec.enterprise_groups().begin(),
                                           spec.enterprise_groups().end());
      }));

  if (spec.has_locale()) {
    client_state->locale = spec.locale();
  }
  if (spec.has_reference_date_seconds_since_epoch()) {
    client_state->reference_date =
        base::Time::UnixEpoch() +
        base::Seconds(spec.reference_date_seconds_since_epoch());
  }
  if (!spec.version().empty()) {
    client_state->version = base::Version(
        std::vector<uint32_t>(spec.version().begin(), spec.version().end()));
  } else {
    // Default constructed version is invalid as per base::Version so we use
    // placeholder value instead.
    client_state->version = base::Version({0, 0, 0, 0});
  }
  if (!spec.os_version().empty()) {
    client_state->os_version = base::Version(std::vector<uint32_t>(
        spec.os_version().begin(), spec.os_version().end()));
  } else {
    // Default constructed version is invalid as per base::Version so we use
    // placeholder value instead.
    client_state->os_version = base::Version(std::vector<uint32_t>{0, 0});
  }
  if (spec.has_channel()) {
    client_state->channel = spec.channel();
  }
  if (spec.has_form_factor()) {
    client_state->form_factor = spec.form_factor();
  }
  if (spec.has_cpu_architecture()) {
    client_state->cpu_architecture = spec.cpu_architecture();
  }
  if (spec.has_platform()) {
    client_state->platform = spec.platform();
  }
  if (spec.has_hardware_class()) {
    client_state->hardware_class = spec.hardware_class();
  }
  if (spec.has_is_low_end_device()) {
    client_state->is_low_end_device = spec.is_low_end_device();
  }
  if (spec.has_session_consistency_country()) {
    client_state->session_consistency_country =
        spec.session_consistency_country();
  }
  if (spec.has_permanent_consistency_country()) {
    client_state->permanent_consistency_country =
        spec.permanent_consistency_country();
  }
  if (spec.has_policy_restriction()) {
    switch (spec.policy_restriction()) {
      case CreateTrialsFromSeedTestCase_RestrictionPolicy_NO_RESTRICTIONS:
        client_state->policy_restriction = RestrictionPolicy::NO_RESTRICTIONS;
        break;
      case CreateTrialsFromSeedTestCase_RestrictionPolicy_CRITICAL_ONLY:
        client_state->policy_restriction = RestrictionPolicy::CRITICAL_ONLY;
        break;
      case CreateTrialsFromSeedTestCase_RestrictionPolicy_ALL:
        client_state->policy_restriction = RestrictionPolicy::ALL;
        break;
    }
  }
  return client_state;
}

std::unique_ptr<metrics::MetricsStateManager>
CreateMetricsStateManagerForFuzzer(
    const CreateTrialsFromSeedTestCase::EntropyValues& entropy_values,
    TestingPrefServiceSimple* pref_service,
    metrics::EnabledStateProvider* enabled_state_provider) {
  pref_service->SetInteger(metrics::prefs::kMetricsLowEntropySource,
                           entropy_values.low_entropy());
  pref_service->SetString(
      metrics::prefs::kMetricsLimitedEntropyRandomizationSource,
      entropy_values.limited_entropy_randomization_source());
  pref_service->SetString(metrics::prefs::kMetricsClientID,
                          entropy_values.client_id());

  return metrics::MetricsStateManager::Create(
      pref_service, enabled_state_provider,
      /*backup_registry_key=*/std::wstring(),
      /*user_data_dir=*/base::FilePath());
}

void CreateTrialsFromSeedFuzzer(
    const variations::CreateTrialsFromSeedTestCase& test_case,
    TestingPrefServiceSimple* pref_service,
    metrics::EnabledStateProvider* enabled_state_provider) {
  base::FieldTrialList field_trial_list;
  base::FeatureList feature_list;

  auto client_state = CreateClientFilterableState(
      test_case.has_client_filterable_state()
          ? test_case.client_filterable_state()
          : variations::CreateTrialsFromSeedTestCase::ClientFilterableState());

  const auto& entropy_values =
      test_case.has_entropy()
          ? test_case.entropy()
          : variations::CreateTrialsFromSeedTestCase::EntropyValues();
  auto state_manager = CreateMetricsStateManagerForFuzzer(
      entropy_values, pref_service, enabled_state_provider);
  auto entropy_providers = state_manager->CreateEntropyProviders(
      /*enable_limited_entropy_mode=*/true);

  if (!test_case.has_seed()) {
    return;
  }

  base::HistogramTester histogram_tester;
  auto seed = test_case.seed();
  VariationsLayers layers(seed, *entropy_providers);
  StickyActivationManager sticky_activation_manager(/*local_state=*/nullptr);
  VariationsSeedProcessor(sticky_activation_manager)
      .CreateTrialsFromSeed(seed, *client_state,
                            *entropy_providers, layers, &feature_list);

  // There are numerous conditions for which the seed could be rejected. They
  // should all be caught during the initial seed validation. Post validation,
  // when attempting to actually use the seed, there are some components which,
  // for safety, repeat some of the validation and signal a generic "invalid
  // configuration" rejection reason. This state should not be reachable in
  // practice.
  CHECK_EQ(histogram_tester.GetBucketCount(
               kSeedRejectionReasonHistogram,
               SeedRejectionReason::kInvalidLayerConfiguration),
           0);
}

}  // namespace

DEFINE_PROTO_FUZZER(
    const fuzzable::variations::CreateTrialsFromSeedTestCase& test_case) {
  variations::CreateTrialsFromSeedTestCase lite_test_case;
  if (!lite_test_case.ParseFromString(test_case.SerializeAsString())) {
    return;
  }
  static Environment env;
  CreateTrialsFromSeedFuzzer(lite_test_case, &env.pref_service,
                             &env.enabled_state_provider);
}

}  // namespace variations
