// 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/omnibox/browser/omnibox_field_trial.h"

#include <memory>
#include <string>
#include <utility>

#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "components/history/core/browser/url_database.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search/search.h"
#include "components/variations/variations_associated_data.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"

using metrics::OmniboxEventProto;

class OmniboxFieldTrialTest : public testing::Test {
 public:
  OmniboxFieldTrialTest() {
    ResetFieldTrialList();
  }
  OmniboxFieldTrialTest(const OmniboxFieldTrialTest&) = delete;
  OmniboxFieldTrialTest& operator=(const OmniboxFieldTrialTest&) = delete;

  void ResetFieldTrialList() {
    scoped_feature_list_.Reset();
    variations::test::ClearAllVariationParams();
    scoped_feature_list_.Init();
  }

  void ResetAndEnableFeatureWithParameters(
      const base::Feature& feature,
      const base::FieldTrialParams& feature_parameters) {
    scoped_feature_list_.Reset();
    variations::test::ClearAllVariationParams();
    scoped_feature_list_.InitAndEnableFeatureWithParameters(feature,
                                                            feature_parameters);
  }

  // Creates and activates a field trial.
  static base::FieldTrial* CreateTestTrial(const std::string& name,
                                           const std::string& group_name) {
    base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial(
        name, group_name);
    trial->Activate();
    return trial;
  }

  // EXPECT()s that OmniboxFieldTrial::GetSuggestPollingStrategy returns
  // |expected_from_last_keystroke| and |expected_delay_ms| for the given
  // experiment params. If one the rule values is NULL, the corresponding
  // variation parameter won't be set thus allowing to test the default
  // behavior.
  void VerifySuggestPollingStrategy(
      const char* from_last_keystroke_rule_value,
      const char* polling_delay_ms_rule_value,
      bool expected_from_last_keystroke,
      int expected_delay_ms);

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

void OmniboxFieldTrialTest::VerifySuggestPollingStrategy(
    const char* from_last_keystroke_rule_value,
    const char* polling_delay_ms_rule_value,
    bool expected_from_last_keystroke,
    int expected_delay_ms) {
  ResetFieldTrialList();
  std::map<std::string, std::string> params;
  if (from_last_keystroke_rule_value != nullptr) {
    params[std::string(
        OmniboxFieldTrial::kMeasureSuggestPollingDelayFromLastKeystrokeRule)] =
        from_last_keystroke_rule_value;
  }
  if (polling_delay_ms_rule_value != nullptr) {
    params[std::string(
        OmniboxFieldTrial::kSuggestPollingDelayMsRule)] =
        polling_delay_ms_rule_value;
  }
  ASSERT_TRUE(base::AssociateFieldTrialParams(
      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
  base::FieldTrialList::CreateFieldTrial(
      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");

  bool from_last_keystroke;
  int delay_ms;
  OmniboxFieldTrial::GetSuggestPollingStrategy(&from_last_keystroke, &delay_ms);
  EXPECT_EQ(expected_from_last_keystroke, from_last_keystroke);
  EXPECT_EQ(expected_delay_ms, delay_ms);
}

// Test if GetDisabledProviderTypes() properly parses various field trial
// group names.
TEST_F(OmniboxFieldTrialTest, GetDisabledProviderTypes) {
  EXPECT_EQ(0, OmniboxFieldTrial::GetDisabledProviderTypes());

  {
    SCOPED_TRACE("Valid field trial, missing param.");
    ResetFieldTrialList();
    std::map<std::string, std::string> params;
    ASSERT_TRUE(base::AssociateFieldTrialParams(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
    base::FieldTrialList::CreateFieldTrial(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
    EXPECT_EQ(0, OmniboxFieldTrial::GetDisabledProviderTypes());
  }

  {
    SCOPED_TRACE("Valid field trial, empty param value.");
    ResetFieldTrialList();
    std::map<std::string, std::string> params;
    params[std::string(OmniboxFieldTrial::kDisableProvidersRule)] = "";
    ASSERT_TRUE(base::AssociateFieldTrialParams(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
    base::FieldTrialList::CreateFieldTrial(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
    EXPECT_EQ(0, OmniboxFieldTrial::GetDisabledProviderTypes());
  }

  {
    SCOPED_TRACE("Valid field trial, invalid param value.");
    ResetFieldTrialList();
    std::map<std::string, std::string> params;
    params[std::string(OmniboxFieldTrial::kDisableProvidersRule)] = "aaa";
    ASSERT_TRUE(base::AssociateFieldTrialParams(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
    base::FieldTrialList::CreateFieldTrial(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
    EXPECT_EQ(0, OmniboxFieldTrial::GetDisabledProviderTypes());
  }

  {
    SCOPED_TRACE("Valid field trial and param.");
    ResetFieldTrialList();
    std::map<std::string, std::string> params;
    params[std::string(OmniboxFieldTrial::kDisableProvidersRule)] = "12321";
    ASSERT_TRUE(base::AssociateFieldTrialParams(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
    base::FieldTrialList::CreateFieldTrial(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");
    EXPECT_EQ(12321, OmniboxFieldTrial::GetDisabledProviderTypes());
  }
}

TEST_F(OmniboxFieldTrialTest, GetProviderMaxMatches) {
  OmniboxFieldTrial::ScopedMLConfigForTesting scoped_ml_config;
  scoped_ml_config.GetMLConfig().ml_url_scoring = false;
  {
    ResetAndEnableFeatureWithParameters(
        omnibox::kUIExperimentMaxAutocompleteMatches,
        {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesByProviderParam,
          "1:50,2:0"}});
    ASSERT_EQ(50ul, OmniboxFieldTrial::GetProviderMaxMatches(
                        AutocompleteProvider::Type::TYPE_BOOKMARK));
    ASSERT_EQ(0ul, OmniboxFieldTrial::GetProviderMaxMatches(
                       AutocompleteProvider::Type::TYPE_BUILTIN));
    ASSERT_EQ(3ul, OmniboxFieldTrial::GetProviderMaxMatches(
                       AutocompleteProvider::Type::TYPE_HISTORY_QUICK));
  }
  {
    ResetAndEnableFeatureWithParameters(
        omnibox::kUIExperimentMaxAutocompleteMatches,
        {{OmniboxFieldTrial::kUIMaxAutocompleteMatchesByProviderParam,
          "1:60,*:61,2:62"}});
    ASSERT_EQ(60ul, OmniboxFieldTrial::GetProviderMaxMatches(
                        AutocompleteProvider::Type::TYPE_BOOKMARK));
    ASSERT_EQ(62ul, OmniboxFieldTrial::GetProviderMaxMatches(
                        AutocompleteProvider::Type::TYPE_BUILTIN));
    ASSERT_EQ(61ul, OmniboxFieldTrial::GetProviderMaxMatches(
                        AutocompleteProvider::Type::TYPE_HISTORY_QUICK));
  }
  {
    ResetFieldTrialList();
    ASSERT_EQ(3ul, OmniboxFieldTrial::GetProviderMaxMatches(
                       AutocompleteProvider::Type::TYPE_BOOKMARK));
    ASSERT_EQ(3ul, OmniboxFieldTrial::GetProviderMaxMatches(
                       AutocompleteProvider::Type::TYPE_BUILTIN));
    ASSERT_EQ(3ul, OmniboxFieldTrial::GetProviderMaxMatches(
                       AutocompleteProvider::Type::TYPE_HISTORY_QUICK));
  }
  {
    scoped_ml_config.GetMLConfig().ml_url_scoring = true;
    scoped_ml_config.GetMLConfig().url_scoring_model = true;
    scoped_ml_config.GetMLConfig().ml_url_scoring_max_matches_by_provider =
        "1:10,4:10,8:10,64:10,65536:10";

    ASSERT_EQ(10ul, OmniboxFieldTrial::GetProviderMaxMatches(
                        AutocompleteProvider::Type::TYPE_BOOKMARK));
    ASSERT_EQ(10ul, OmniboxFieldTrial::GetProviderMaxMatches(
                        AutocompleteProvider::Type::TYPE_HISTORY_QUICK));
    ASSERT_EQ(10ul, OmniboxFieldTrial::GetProviderMaxMatches(
                        AutocompleteProvider::Type::TYPE_HISTORY_URL));
    ASSERT_EQ(10ul, OmniboxFieldTrial::GetProviderMaxMatches(
                        AutocompleteProvider::Type::TYPE_HISTORY_FUZZY));
  }
}

TEST_F(OmniboxFieldTrialTest, HUPNewScoringFieldTrial) {
  {
    std::map<std::string, std::string> params;
    params[std::string(
        OmniboxFieldTrial::kHUPNewScoringTypedCountRelevanceCapParam)] = "56";
    params[std::string(
        OmniboxFieldTrial::kHUPNewScoringTypedCountHalfLifeTimeParam)] = "77";
    params[std::string(
        OmniboxFieldTrial::kHUPNewScoringTypedCountScoreBucketsParam)] =
        "0.2:25,0.1:1001,2.3:777";
    params[std::string(
        OmniboxFieldTrial::kHUPNewScoringVisitedCountRelevanceCapParam)] = "11";
    params[std::string(
        OmniboxFieldTrial::kHUPNewScoringVisitedCountHalfLifeTimeParam)] = "31";
    params[std::string(
        OmniboxFieldTrial::kHUPNewScoringVisitedCountScoreBucketsParam)] =
        "5:300,0:200";
    ASSERT_TRUE(base::AssociateFieldTrialParams(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
  }
  base::FieldTrialList::CreateFieldTrial(
      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");

  HUPScoringParams scoring_params;
  OmniboxFieldTrial::GetExperimentalHUPScoringParams(&scoring_params);
  EXPECT_EQ(56, scoring_params.typed_count_buckets.relevance_cap());
  EXPECT_EQ(77, scoring_params.typed_count_buckets.half_life_days());
  ASSERT_EQ(3u, scoring_params.typed_count_buckets.buckets().size());
  EXPECT_EQ(std::make_pair(2.3, 777),
            scoring_params.typed_count_buckets.buckets()[0]);
  EXPECT_EQ(std::make_pair(0.2, 25),
            scoring_params.typed_count_buckets.buckets()[1]);
  EXPECT_EQ(std::make_pair(0.1, 1001),
            scoring_params.typed_count_buckets.buckets()[2]);
  EXPECT_EQ(11, scoring_params.visited_count_buckets.relevance_cap());
  EXPECT_EQ(31, scoring_params.visited_count_buckets.half_life_days());
  ASSERT_EQ(2u, scoring_params.visited_count_buckets.buckets().size());
  EXPECT_EQ(std::make_pair(5.0, 300),
            scoring_params.visited_count_buckets.buckets()[0]);
  EXPECT_EQ(std::make_pair(0.0, 200),
            scoring_params.visited_count_buckets.buckets()[1]);
}

TEST_F(OmniboxFieldTrialTest, HUPNewScoringFieldTrialWithDecayFactor) {
  {
    std::map<std::string, std::string> params;
    params[OmniboxFieldTrial::kHUPNewScoringTypedCountHalfLifeTimeParam] = "10";
    params[OmniboxFieldTrial::kHUPNewScoringTypedCountUseDecayFactorParam] =
        "1";
    params[OmniboxFieldTrial::kHUPNewScoringTypedCountScoreBucketsParam] =
        "0.1:100,0.5:500,1.0:1000";
    ASSERT_TRUE(base::AssociateFieldTrialParams(
        OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A", params));
  }
  base::FieldTrialList::CreateFieldTrial(
      OmniboxFieldTrial::kBundledExperimentFieldTrialName, "A");

  HUPScoringParams scoring_params;
  OmniboxFieldTrial::GetExperimentalHUPScoringParams(&scoring_params);
  EXPECT_EQ(10, scoring_params.typed_count_buckets.half_life_days());
  ASSERT_EQ(3u, scoring_params.typed_count_buckets.buckets().size());
  ASSERT_TRUE(scoring_params.typed_count_buckets.use_decay_factor());
}

TEST_F(OmniboxFieldTrialTest, HalfLifeTimeDecay) {
  HUPScoringParams::ScoreBuckets buckets;

  // No decay by default.
  EXPECT_EQ(1.0, buckets.HalfLifeTimeDecay(base::Days(7)));

  buckets.set_half_life_days(7);
  EXPECT_EQ(0.5, buckets.HalfLifeTimeDecay(base::Days(7)));
  EXPECT_EQ(0.25, buckets.HalfLifeTimeDecay(base::Days(14)));
  EXPECT_EQ(1.0, buckets.HalfLifeTimeDecay(base::Days(0)));
  EXPECT_EQ(1.0, buckets.HalfLifeTimeDecay(base::Days(-1)));
}

TEST_F(OmniboxFieldTrialTest, GetSuggestPollingStrategy) {
  // Invalid params.
  VerifySuggestPollingStrategy(
      "", "", false,
      OmniboxFieldTrial::kDefaultMinimumTimeBetweenSuggestQueriesMs);
  VerifySuggestPollingStrategy(
      "foo", "-1", false,
      OmniboxFieldTrial::kDefaultMinimumTimeBetweenSuggestQueriesMs);
  VerifySuggestPollingStrategy(
      "TRUE", "xyz", false,
      OmniboxFieldTrial::kDefaultMinimumTimeBetweenSuggestQueriesMs);

  // Default values.
  VerifySuggestPollingStrategy(
      nullptr, nullptr, false,
      OmniboxFieldTrial::kDefaultMinimumTimeBetweenSuggestQueriesMs);

  // Valid params.
  VerifySuggestPollingStrategy("true", "50", true, 50);
  VerifySuggestPollingStrategy(nullptr, "35", false, 35);
  VerifySuggestPollingStrategy(
      "true", nullptr, true,
      OmniboxFieldTrial::kDefaultMinimumTimeBetweenSuggestQueriesMs);
}
