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

#include "chrome/browser/predictors/autocomplete_action_predictor.h"

#include <stddef.h>

#include <algorithm>
#include <array>
#include <string>
#include <vector>

#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/memory/ref_counted.h"
#include "base/no_destructor.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/uuid.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/preloading/chrome_preloading.h"
#include "chrome/browser/preloading/preloading_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/testing_profile.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/in_memory_database.h"
#include "components/history/core/browser/url_database.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_match_type.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/omnibox/common/omnibox_features.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/preloading_test_util.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::ASCIIToUTF16;
using content::WebContents;
using predictors::AutocompleteActionPredictor;

using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
using ukm::builders::Preloading_Prediction;

namespace {

struct TestUrlInfo {
  GURL url;
  std::u16string title;
  int days_from_now;
  std::u16string user_text;
  int number_of_hits;
  int number_of_misses;
  AutocompleteActionPredictor::Action expected_action;
};

AutocompleteActionPredictor::Action ExpectedActionBasedOnConfidenceOnly(
    int number_of_hits,
    int number_of_misses) {
  int total = number_of_hits + number_of_misses;
  EXPECT_GT(total, 0);
  double confidence = number_of_hits / (double)total;
  return AutocompleteActionPredictor::DecideActionByConfidence(confidence);
}

const std::vector<TestUrlInfo>& TestUrlDb() {
  static base::NoDestructor<std::vector<TestUrlInfo>> db{
      {{GURL("http://www.testsite.com/a.html"), u"Test - site - just a test", 1,
        u"j", 5, 0, ExpectedActionBasedOnConfidenceOnly(5, 0)},
       {GURL("http://www.testsite.com/b.html"), u"Test - site - just a test", 1,
        u"ju", 3, 0, ExpectedActionBasedOnConfidenceOnly(3, 0)},
       {GURL("http://www.testsite.com/c.html"), u"Test - site - just a test", 5,
        u"just", 3, 1, ExpectedActionBasedOnConfidenceOnly(3, 1)},
       {GURL("http://www.testsite.com/d.html"), u"Test - site - just a test", 5,
        u"just", 3, 0, ExpectedActionBasedOnConfidenceOnly(3, 0)},
       {GURL("http://www.testsite.com/e.html"), u"Test - site - just a test", 8,
        u"just", 3, 1, ExpectedActionBasedOnConfidenceOnly(3, 1)},
       {GURL("http://www.testsite.com/f.html"), u"Test - site - just a test", 8,
        u"just", 3, 0, ExpectedActionBasedOnConfidenceOnly(3, 0)},
       {GURL("http://www.testsite.com/g.html"), u"Test - site - just a test", 8,
        u"just", 3, 4, ExpectedActionBasedOnConfidenceOnly(3, 4)},
       {GURL("http://www.testsite.com/h.html"), u"Test - site - just a test",
        12, std::u16string(), 5, 0, AutocompleteActionPredictor::ACTION_NONE},
       {GURL("http://www.testsite.com/i.html"), u"Test - site - just a test",
        21, u"just a test", 2, 0, AutocompleteActionPredictor::ACTION_NONE},
       {GURL("http://www.testsite.com/j.html"), u"Test - site - just a test",
        28, u"just a test", 2, 0, AutocompleteActionPredictor::ACTION_NONE}}};
  return *db;
}

// List of urls sorted by the confidence score in ascending order.
const std::vector<TestUrlInfo>& TestUrlConfidenceDb() {
  static base::NoDestructor<std::vector<TestUrlInfo>> db{{
      {GURL("http://www.testsite.com/g.html"), u"Test", 1, u"test", 0, 2,
       AutocompleteActionPredictor::ACTION_NONE},
      {GURL("http://www.testsite.com/f.html"), u"Test", 1, u"test", 1, 2,
       AutocompleteActionPredictor::ACTION_NONE},
      {GURL("http://www.testsite.com/e.html"), u"Test", 1, u"test", 2, 2,
       AutocompleteActionPredictor::ACTION_NONE},
      {GURL("http://www.testsite.com/d.html"), u"Test", 1, u"test", 3, 3,
       AutocompleteActionPredictor::ACTION_NONE},
      {GURL("http://www.testsite.com/c.html"), u"Test", 1, u"test", 3, 2,
       AutocompleteActionPredictor::ACTION_NONE},
      {GURL("http://www.testsite.com/b.html"), u"Test", 1, u"test", 3, 0,
       AutocompleteActionPredictor::ACTION_NONE},
      {GURL("http://www.testsite.com/a.html"), u"Test", 1, u"test", 5, 0,
       AutocompleteActionPredictor::ACTION_NONE},
  }};
  return *db;
}

}  // end namespace

namespace predictors {

class AutocompleteActionPredictorTest
    : public testing::Test,
      public ::testing::WithParamInterface<bool> {
 public:
  AutocompleteActionPredictorTest() : predictor_(nullptr) {
    if (GetParam()) {
      feature_list_.InitAndEnableFeature(
          omnibox::kPreconnectNonSearchOmniboxSuggestions);
    } else {
      feature_list_.InitAndDisableFeature(
          omnibox::kPreconnectNonSearchOmniboxSuggestions);
    }

    TestingProfile::Builder profile_builder;
    profile_builder.AddTestingFactory(
        HistoryServiceFactory::GetInstance(),
        HistoryServiceFactory::GetDefaultFactory());
    profile_ = profile_builder.Build();

    web_contents_ = content::WebContentsTester::CreateTestWebContents(
        profile_.get(), nullptr);
    ukm_entry_builder_ =
        std::make_unique<content::test::PreloadingPredictionUkmEntryBuilder>(
            chrome_preloading_predictor::kOmniboxDirectURLInput);
    test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();

    predictor_ = std::make_unique<AutocompleteActionPredictor>(profile_.get());
    profile_->BlockUntilHistoryProcessesPendingRequests();
    content::RunAllTasksUntilIdle();

    CHECK(predictor_->initialized_);
    CHECK(db_cache()->empty());
    CHECK(db_id_cache()->empty());
  }

  ~AutocompleteActionPredictorTest() override {
    web_contents_.reset();
    // Wait for all pending tasks on the DB sequence.
    content::RunAllTasksUntilIdle();
    // Since we instantiated the predictor instead of going through a factory
    // and dependencies, no one else is going to call Shutdown(), which is
    // supposed to be called as part of being a KeyedService. The behavior of
    // this method is not explicitly verified.
    predictor_->Shutdown();
  }

 protected:
  typedef AutocompleteActionPredictor::DBCacheKey DBCacheKey;
  typedef AutocompleteActionPredictor::DBCacheValue DBCacheValue;
  typedef AutocompleteActionPredictor::DBCacheMap DBCacheMap;
  typedef AutocompleteActionPredictor::DBIdCacheMap DBIdCacheMap;

  history::URLID AddRowToHistory(const TestUrlInfo& test_row) {
    history::HistoryService* history = HistoryServiceFactory::GetForProfile(
        profile_.get(), ServiceAccessType::EXPLICIT_ACCESS);
    CHECK(history);
    history::URLDatabase* url_db = history->InMemoryDatabase();
    CHECK(url_db);

    const base::Time visit_time =
        base::Time::Now() - base::Days(test_row.days_from_now);

    history::URLRow row(test_row.url);
    row.set_title(test_row.title);
    row.set_last_visit(visit_time);

    return url_db->AddURL(row);
  }

  AutocompleteActionPredictorTable::Row CreateRowFromTestUrlInfo(
      const TestUrlInfo& test_row) const {
    AutocompleteActionPredictorTable::Row row;
    row.id = base::Uuid::GenerateRandomV4().AsLowercaseString();
    row.user_text = test_row.user_text;
    row.url = test_row.url;
    row.number_of_hits = test_row.number_of_hits;
    row.number_of_misses = test_row.number_of_misses;
    return row;
  }

  void AddAllRows() {
    for (size_t i = 0; i < std::size(TestUrlDb()); ++i)
      AddRow(TestUrlDb()[i]);
  }

  std::string AddRow(const TestUrlInfo& test_row) {
    AutocompleteActionPredictorTable::Row row =
        CreateRowFromTestUrlInfo(test_row);
    predictor_->AddAndUpdateRows(
        AutocompleteActionPredictorTable::Rows(1, row),
        AutocompleteActionPredictorTable::Rows());

    return row.id;
  }

  WebContents* web_contents() { return web_contents_.get(); }

  void UpdateRow(const AutocompleteActionPredictorTable::Row& row) {
    AutocompleteActionPredictor::DBCacheKey key = { row.user_text, row.url };
    ASSERT_TRUE(db_cache()->find(key) != db_cache()->end());
    predictor_->AddAndUpdateRows(
        AutocompleteActionPredictorTable::Rows(),
        AutocompleteActionPredictorTable::Rows(1, row));
  }

  void OnURLsDeletedTest(bool expired) {
    ASSERT_NO_FATAL_FAILURE(AddAllRows());

    EXPECT_EQ(std::size(TestUrlDb()), db_cache()->size());
    EXPECT_EQ(std::size(TestUrlDb()), db_id_cache()->size());

    std::vector<size_t> expected;
    history::URLRows rows;
    for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
      bool expect_deleted = false;

      if (i < 2) {
        rows.push_back(history::URLRow(TestUrlDb()[i].url));
        expect_deleted = true;
      }

      if (!expired &&
          TestUrlDb()[i].days_from_now > maximum_days_to_keep_entry()) {
        expect_deleted = true;
      }

      if (i != 3 && i != 4)
        ASSERT_TRUE(AddRowToHistory(TestUrlDb()[i]));
      else if (!expired)
        expect_deleted = true;

      if (!expect_deleted)
        expected.push_back(i);
    }

    history::HistoryService* history_service =
        HistoryServiceFactory::GetForProfile(
            profile_.get(), ServiceAccessType::EXPLICIT_ACCESS);
    ASSERT_TRUE(history_service);

    predictor_->OnHistoryDeletions(
        history_service,
        history::DeletionInfo(history::DeletionTimeRange::Invalid(), expired,
                              rows, std::set<GURL>(), std::nullopt));

    EXPECT_EQ(expected.size(), db_cache()->size());
    EXPECT_EQ(expected.size(), db_id_cache()->size());

    for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
      DBCacheKey key = {TestUrlDb()[i].user_text, TestUrlDb()[i].url};

      bool deleted = !std::ranges::contains(expected, i);
      EXPECT_EQ(deleted, db_cache()->find(key) == db_cache()->end());
      EXPECT_EQ(deleted, db_id_cache()->find(key) == db_id_cache()->end());
    }
  }

  void DeleteAllRows() {
    predictor_->DeleteAllRows();
  }

  void DeleteRowsFromCaches(
      const history::URLRows& rows,
      std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) {
    predictor_->DeleteRowsFromCaches(rows, id_list);
  }

  void DeleteOldIdsFromCaches(
      std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) {
    history::HistoryService* history_service =
        HistoryServiceFactory::GetForProfile(
            profile_.get(), ServiceAccessType::EXPLICIT_ACCESS);
    ASSERT_TRUE(history_service);

    history::URLDatabase* url_db = history_service->InMemoryDatabase();
    ASSERT_TRUE(url_db);

    predictor_->DeleteOldIdsFromCaches(url_db, id_list);
  }

  void DeleteLowestConfidenceRowsFromCaches(
      size_t count,
      std::vector<AutocompleteActionPredictorTable::Row::Id>* id_list) {
    predictor_->DeleteLowestConfidenceRowsFromCaches(count, id_list);
  }

  AutocompleteActionPredictor* predictor() { return predictor_.get(); }

  DBCacheMap* db_cache() { return &predictor_->db_cache_; }
  DBIdCacheMap* db_id_cache() { return &predictor_->db_id_cache_; }
  std::vector<AutocompleteActionPredictor::TransitionalMatch>*
  transitional_matches() {
    return &predictor_->transitional_matches_;
  }

  static int maximum_days_to_keep_entry() {
    return AutocompleteActionPredictor::kMaximumDaysToKeepEntry;
  }

  static size_t minimum_user_text_length() {
    return AutocompleteActionPredictor::kMinimumUserTextLength;
  }

  static size_t maximum_string_length() {
    return AutocompleteActionPredictor::kMaximumStringLength;
  }

  ukm::TestAutoSetUkmRecorder* test_ukm_recorder() {
    return test_ukm_recorder_.get();
  }

  const content::test::PreloadingPredictionUkmEntryBuilder&
  ukm_entry_builder() {
    return *ukm_entry_builder_;
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<AutocompleteActionPredictor> predictor_;
  std::unique_ptr<content::test::PreloadingPredictionUkmEntryBuilder>
      ukm_entry_builder_;
  std::unique_ptr<WebContents> web_contents_;
  content::RenderViewHostTestEnabler rvh_test_enabler_;
  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
  base::ScopedMockElapsedTimersForTest test_timer_;
  base::test::ScopedFeatureList feature_list_;
};

INSTANTIATE_TEST_SUITE_P(AutocompleteActionPredictorTest,
                         AutocompleteActionPredictorTest,
                         testing::Bool());

TEST_P(AutocompleteActionPredictorTest, AddRow) {
  // Add a test entry to the predictor.
  std::string guid = AddRow(TestUrlDb()[0]);

  // Get the data back out of the cache.
  const DBCacheKey key = {TestUrlDb()[0].user_text, TestUrlDb()[0].url};
  DBCacheMap::const_iterator it = db_cache()->find(key);
  EXPECT_TRUE(it != db_cache()->end());

  const DBCacheValue value = {TestUrlDb()[0].number_of_hits,
                              TestUrlDb()[0].number_of_misses};
  EXPECT_EQ(value.number_of_hits, it->second.number_of_hits);
  EXPECT_EQ(value.number_of_misses, it->second.number_of_misses);

  DBIdCacheMap::const_iterator id_it = db_id_cache()->find(key);
  EXPECT_TRUE(id_it != db_id_cache()->end());
  EXPECT_EQ(guid, id_it->second);
}

TEST_P(AutocompleteActionPredictorTest, UpdateRow) {
  ASSERT_NO_FATAL_FAILURE(AddAllRows());

  EXPECT_EQ(std::size(TestUrlDb()), db_cache()->size());
  EXPECT_EQ(std::size(TestUrlDb()), db_id_cache()->size());

  // Get the data back out of the cache.
  const DBCacheKey key = {TestUrlDb()[0].user_text, TestUrlDb()[0].url};
  DBCacheMap::const_iterator it = db_cache()->find(key);
  EXPECT_TRUE(it != db_cache()->end());

  DBIdCacheMap::const_iterator id_it = db_id_cache()->find(key);
  EXPECT_TRUE(id_it != db_id_cache()->end());

  AutocompleteActionPredictorTable::Row update_row;
  update_row.id = id_it->second;
  update_row.user_text = key.user_text;
  update_row.url = key.url;
  update_row.number_of_hits = it->second.number_of_hits + 1;
  update_row.number_of_misses = it->second.number_of_misses + 2;

  UpdateRow(update_row);

  // Get the updated version.
  DBCacheMap::const_iterator update_it = db_cache()->find(key);
  EXPECT_TRUE(update_it != db_cache()->end());

  EXPECT_EQ(update_row.number_of_hits, update_it->second.number_of_hits);
  EXPECT_EQ(update_row.number_of_misses, update_it->second.number_of_misses);

  DBIdCacheMap::const_iterator update_id_it = db_id_cache()->find(key);
  EXPECT_TRUE(update_id_it != db_id_cache()->end());

  EXPECT_EQ(id_it->second, update_id_it->second);
}

TEST_P(AutocompleteActionPredictorTest, DeleteAllRows) {
  ASSERT_NO_FATAL_FAILURE(AddAllRows());

  EXPECT_EQ(std::size(TestUrlDb()), db_cache()->size());
  EXPECT_EQ(std::size(TestUrlDb()), db_id_cache()->size());

  DeleteAllRows();

  EXPECT_TRUE(db_cache()->empty());
  EXPECT_TRUE(db_id_cache()->empty());
}

TEST_P(AutocompleteActionPredictorTest, DeleteRowsFromCaches) {
  std::vector<AutocompleteActionPredictorTable::Row::Id> all_ids;
  history::URLRows rows;
  for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
    std::string row_id = AddRow(TestUrlDb()[i]);
    all_ids.push_back(row_id);

    if (i < 2)
      rows.push_back(history::URLRow(TestUrlDb()[i].url));
  }

  EXPECT_EQ(std::size(TestUrlDb()), all_ids.size());
  EXPECT_EQ(std::size(TestUrlDb()), db_cache()->size());
  EXPECT_EQ(std::size(TestUrlDb()), db_id_cache()->size());

  std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
  DeleteRowsFromCaches(rows, &id_list);

  EXPECT_EQ(std::size(TestUrlDb()) - 2, db_cache()->size());
  EXPECT_EQ(std::size(TestUrlDb()) - 2, db_id_cache()->size());

  for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
    DBCacheKey key = {TestUrlDb()[i].user_text, TestUrlDb()[i].url};

    bool deleted = (i < 2);
    EXPECT_EQ(deleted, db_cache()->find(key) == db_cache()->end());
    EXPECT_EQ(deleted, db_id_cache()->find(key) == db_id_cache()->end());
    EXPECT_EQ(deleted, std::ranges::contains(id_list, all_ids[i]));
  }
}

TEST_P(AutocompleteActionPredictorTest, DeleteOldIdsFromCaches) {
  std::vector<AutocompleteActionPredictorTable::Row::Id> expected;
  std::vector<AutocompleteActionPredictorTable::Row::Id> all_ids;

  for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
    std::string row_id = AddRow(TestUrlDb()[i]);
    all_ids.push_back(row_id);

    bool exclude_url =
        base::StartsWith(TestUrlDb()[i].url.GetPath(), "/d",
                         base::CompareCase::SENSITIVE) ||
        (TestUrlDb()[i].days_from_now > maximum_days_to_keep_entry());

    if (exclude_url)
      expected.push_back(row_id);
    else
      ASSERT_TRUE(AddRowToHistory(TestUrlDb()[i]));
  }

  std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
  DeleteOldIdsFromCaches(&id_list);
  EXPECT_EQ(expected.size(), id_list.size());
  EXPECT_EQ(all_ids.size() - expected.size(), db_cache()->size());
  EXPECT_EQ(all_ids.size() - expected.size(), db_id_cache()->size());

  for (auto it = all_ids.begin(); it != all_ids.end(); ++it) {
    bool in_expected = std::ranges::contains(expected, *it);
    bool in_list = std::ranges::contains(id_list, *it);
    EXPECT_EQ(in_expected, in_list);
  }
}

TEST_P(AutocompleteActionPredictorTest,
       DeleteLowestConfidenceRowsFromCaches_OneByOne) {
  std::vector<AutocompleteActionPredictorTable::Row::Id> test_url_ids;
  for (const auto& info : TestUrlConfidenceDb())
    test_url_ids.push_back(AddRow(info));

  std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
  std::vector<AutocompleteActionPredictorTable::Row::Id> expected;

  for (size_t i = 0; i < std::size(TestUrlConfidenceDb()); ++i) {
    DeleteLowestConfidenceRowsFromCaches(1, &id_list);
    expected.push_back(test_url_ids[i]);
    EXPECT_THAT(id_list, ::testing::UnorderedElementsAreArray(expected));

    DBCacheKey deleted_key = {TestUrlConfidenceDb()[i].user_text,
                              TestUrlConfidenceDb()[i].url};
    EXPECT_FALSE(db_cache()->contains(deleted_key));
    EXPECT_FALSE(db_id_cache()->contains(deleted_key));
  }
}

TEST_P(AutocompleteActionPredictorTest,
       DeleteLowestConfidenceRowsFromCaches_Bulk) {
  std::vector<AutocompleteActionPredictorTable::Row::Id> test_url_ids;
  for (const auto& info : TestUrlConfidenceDb())
    test_url_ids.push_back(AddRow(info));

  std::vector<AutocompleteActionPredictorTable::Row::Id> id_list;
  std::vector<AutocompleteActionPredictorTable::Row::Id> expected;

  const size_t count_to_remove = 4;
  CHECK_LT(count_to_remove, std::size(TestUrlConfidenceDb()));

  for (size_t i = 0; i < count_to_remove; ++i)
    expected.push_back(test_url_ids[i]);

  DeleteLowestConfidenceRowsFromCaches(count_to_remove, &id_list);
  ASSERT_THAT(id_list, ::testing::UnorderedElementsAreArray(expected));

  for (size_t i = 0; i < count_to_remove; ++i) {
    DBCacheKey deleted_key = {TestUrlConfidenceDb()[i].user_text,
                              TestUrlConfidenceDb()[i].url};
    EXPECT_FALSE(db_cache()->contains(deleted_key));
    EXPECT_FALSE(db_id_cache()->contains(deleted_key));
  }
}

TEST_P(AutocompleteActionPredictorTest, OnURLsDeletedExpired) {
  OnURLsDeletedTest(true);
}

TEST_P(AutocompleteActionPredictorTest, OnURLsDeletedNonExpired) {
  OnURLsDeletedTest(false);
}

TEST_P(AutocompleteActionPredictorTest, RecommendActionURL) {
  ASSERT_NO_FATAL_FAILURE(AddAllRows());

  // Navigate to kInitial URL.
  GURL kInitialUrl("https://example.com");
  content::WebContentsTester::For(web_contents())
      ->NavigateAndCommit(kInitialUrl);
  content::RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame())
      ->InitializeRenderFrameIfNeeded();

  AutocompleteMatch match;
  match.type = AutocompleteMatchType::HISTORY_URL;

  for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
    match.destination_url = GURL(TestUrlDb()[i].url);
    EXPECT_EQ(TestUrlDb()[i].expected_action,
              predictor()->RecommendAction(TestUrlDb()[i].user_text, match,
                                           web_contents()))
        << "Unexpected action for " << match.destination_url;
  }

  // Calculate confidence_interval for the first entry to cross-check with
  // metrics.
  match.destination_url = GURL(TestUrlDb()[0].url);
  double confidence =
      predictor()->CalculateConfidence(TestUrlDb()[0].user_text, match);

  // Set the first url in the database as the destination url to cross-check the
  // metrics for the first Preloading.Prediction UKM.
  GURL kDestinationUrl(TestUrlDb()[0].url);
  content::WebContentsTester::For(web_contents())
      ->NavigateAndCommit(kDestinationUrl);
  ukm::SourceId ukm_source_id =
      web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId();

  // Check that we have recorded Preloading.Prediction for all entries in the
  // TestUrlDb.
  auto ukm_entries = test_ukm_recorder()->GetEntries(
      Preloading_Prediction::kEntryName,
      content::test::kPreloadingPredictionUkmMetrics);
  EXPECT_EQ(ukm_entries.size(), std::size(TestUrlDb()));
  // Cross-check that we have logged the correct metrics for Prediction,
  // confidence, accurate_prediction on successful activation.
  UkmEntry expected_entry = ukm_entry_builder().BuildEntry(
      ukm_source_id, /*confidence=*/confidence * 100,
      /*accurate_prediction=*/true);
  EXPECT_EQ(ukm_entries[0], expected_entry)
      << content::test::ActualVsExpectedUkmEntryToString(ukm_entries[0],
                                                         expected_entry);
}

TEST_P(AutocompleteActionPredictorTest, RecommendActionSearch) {
  ASSERT_NO_FATAL_FAILURE(AddAllRows());

  AutocompleteMatch match;
  match.type = AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED;

  for (size_t i = 0; i < std::size(TestUrlDb()); ++i) {
    match.destination_url = GURL(TestUrlDb()[i].url);
    AutocompleteActionPredictor::Action expected_action =
        (TestUrlDb()[i].expected_action ==
         AutocompleteActionPredictor::ACTION_PRERENDER)
            ? AutocompleteActionPredictor::ACTION_PRECONNECT
            : TestUrlDb()[i].expected_action;
    EXPECT_EQ(expected_action, predictor()->RecommendAction(
                                   TestUrlDb()[i].user_text, match, nullptr))
        << "Unexpected action for " << match.destination_url;
  }
}

TEST_P(AutocompleteActionPredictorTest, RecommendActionNonSearch) {
  ASSERT_NO_FATAL_FAILURE(AddAllRows());

  AutocompleteMatch match;
  match.type = AutocompleteMatchType::URL_WHAT_YOU_TYPED;

  for (const auto& i : TestUrlDb()) {
    match.destination_url = GURL(i.url);
    EXPECT_EQ(i.expected_action,
              predictor()->RecommendAction(i.user_text, match, nullptr))
        << "Unexpected action for " << match.destination_url;
  }
}

TEST_P(AutocompleteActionPredictorTest, PreconnectableTypes) {
  AutocompleteMatch match;
  for (int i = 0; i < AutocompleteMatchType::NUM_TYPES; i++) {
    EXPECT_TRUE(AutocompleteMatchType::FromInteger(i, &match.type));
    EXPECT_EQ(AutocompleteActionPredictor::IsPreconnectable(match),
              GetParam() ? AutocompleteMatch::IsPreconnectableType(match.type)
                         : AutocompleteMatch::IsSearchType(match.type));
  }
}

TEST_P(AutocompleteActionPredictorTest,
       RegisterTransitionalMatchesUserTextSizeLimits) {
  auto test = [this](const std::u16string& user_text,
                     bool should_be_registered) {
    predictor()->RegisterTransitionalMatches(user_text, AutocompleteResult());
    bool registered = std::ranges::contains(
        *transitional_matches(), user_text,
        &AutocompleteActionPredictor::TransitionalMatch::user_text);
    EXPECT_EQ(registered, should_be_registered);
  };

  std::u16string short_text =
      ASCIIToUTF16(std::string(minimum_user_text_length(), 'g'));
  test(short_text, true);

  std::u16string too_short_text =
      ASCIIToUTF16(std::string(minimum_user_text_length() - 1, 'g'));
  test(too_short_text, false);

  std::u16string long_text =
      ASCIIToUTF16(std::string(maximum_string_length(), 'g'));
  test(long_text, true);

  std::u16string too_long_text =
      ASCIIToUTF16(std::string(maximum_string_length() + 1, 'g'));
  test(too_long_text, false);
}

TEST_P(AutocompleteActionPredictorTest,
       RegisterTransitionalMatchesURLSizeLimits) {
  const auto test_url = [](size_t size) {
    const std::string kPrefix = "http://b/";
    return GURL(kPrefix + std::string(size - kPrefix.size(), 'c'));
  };
  auto urls = std::to_array<GURL>({
      test_url(10),
      test_url(maximum_string_length()),
      test_url(maximum_string_length() + 1),
      test_url(maximum_string_length() * 10),
  });
  ACMatches matches;
  for (const auto& url : urls) {
    AutocompleteMatch match;
    match.destination_url = url;
    matches.push_back(match);
  }
  AutocompleteResult result;
  result.AppendMatches(matches);
  std::u16string user_text = u"google";
  predictor()->RegisterTransitionalMatches(user_text, result);
  auto it = std::ranges::find(
      *transitional_matches(), user_text,
      &AutocompleteActionPredictor::TransitionalMatch::user_text);
  ASSERT_NE(it, transitional_matches()->end());
  EXPECT_THAT(it->urls, ::testing::ElementsAre(urls[0], urls[1]));
}

TEST_P(AutocompleteActionPredictorTest, UpdateDatabaseFromTransitionalMatches) {
  ACMatches matches;
  AutocompleteMatch match;
  GURL clicked_url = GURL("https://foo-clicked.com");
  GURL not_clicked_url = GURL("https://foo-not-clicked.com");
  match.destination_url = clicked_url;
  matches.push_back(match);
  match.destination_url = not_clicked_url;
  matches.push_back(match);
  AutocompleteResult result;
  result.AppendMatches(matches);
  std::u16string user_text = u"foo";
  predictor()->RegisterTransitionalMatches(user_text, result);
  ASSERT_EQ(transitional_matches()->size(), 1ul);

  predictor()->UpdateDatabaseFromTransitionalMatches(clicked_url);
  ASSERT_TRUE(transitional_matches()->empty());

  // Make sure the clicked URL has one hit.
  DBCacheKey key = {user_text, clicked_url};
  DBCacheMap::const_iterator it = db_cache()->find(key);
  EXPECT_TRUE(it != db_cache()->end());
  ASSERT_EQ(it->second.number_of_hits, 1);
  ASSERT_EQ(it->second.number_of_misses, 0);

  // Make sure the not clicked URL has one miss.
  key.url = not_clicked_url;
  it = db_cache()->find(key);
  EXPECT_TRUE(it != db_cache()->end());
  ASSERT_EQ(it->second.number_of_hits, 0);
  ASSERT_EQ(it->second.number_of_misses, 1);
}

TEST_P(AutocompleteActionPredictorTest,
       DecideActionByConfidenceWithDuiPrerenderingFeatureFlag) {
  // When enabled (default), confidence above threshold returns
  // ACTION_PRERENDER.
  {
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndEnableFeature(features::kOmniboxDuiPrerendering);
    EXPECT_EQ(AutocompleteActionPredictor::ACTION_PRERENDER,
              AutocompleteActionPredictor::DecideActionByConfidence(0.8));
  }

  // When disabled, confidence above threshold does not return ACTION_PRERENDER.
  {
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndDisableFeature(
        features::kOmniboxDuiPrerendering);
    EXPECT_NE(AutocompleteActionPredictor::ACTION_PRERENDER,
              AutocompleteActionPredictor::DecideActionByConfidence(0.8));
  }
}

}  // namespace predictors
