// 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.

#include "chrome/browser/ui/extensions/settings_overridden_params_providers.h"

#include <algorithm>
#include <cstddef>

#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/settings_api_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/test/base/search_test_utils.h"
#include "components/search_engines/search_engines_test_util.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/ui_util.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/test/test_extension_dir.h"

namespace {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
constexpr char16_t kSearchOverrideExtensionName[] =
    u"Search Override Extension";
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
}  // namespace

class SettingsOverriddenParamsProvidersBrowserTest
    : public extensions::ExtensionBrowserTest {
 public:
  void SetUpOnMainThread() override {
    extensions::ExtensionBrowserTest::SetUpOnMainThread();
    search_test_utils::WaitForTemplateURLServiceToLoad(
        TemplateURLServiceFactory::GetForProfile(browser()->GetProfile()));
  }

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
  // Makes asynchronous parameter collection synchronous fortesting, by waiting
  // in a run loop for icon resource fetching to complete.
  std::optional<ExtensionSettingsOverriddenDialog::Params>
  GetSearchOverriddenParamsSync(content::WebContents* web_contents) {
    std::optional<ExtensionSettingsOverriddenDialog::Params> result;
    base::RunLoop run_loop;
    settings_overridden_params::GetSearchOverriddenParamsThenRun(
        web_contents,
        base::BindLambdaForTesting(
            [&](std::unique_ptr<ExtensionSettingsOverriddenDialog::Params>
                    params) {
              if (params) {
                result.emplace(std::move(*params));
              }
              run_loop.Quit();
            }));
    run_loop.Run();
    return result;
  }
#endif

  content::WebContents* web_contents() {
    return browser()->GetTabStripModel()->GetActiveWebContents();
  }

  // Installs a new extension that controls the default search engine.
  const extensions::Extension* AddExtensionControllingSearch(
      const char* path = "search_provider_override") {
    const extensions::Extension* extension =
        InstallExtensionWithPermissionsGranted(test_data_dir_.AppendASCII(path),
                                               1);
    EXPECT_EQ(extension,
              extensions::GetExtensionOverridingSearchEngine(profile()));
    return extension;
  }

  // Installs a new extension that controls the new tab page.
  const extensions::Extension* AddExtensionControllingNewTab() {
    const extensions::Extension* extension =
        InstallExtensionWithPermissionsGranted(
            test_data_dir_.AppendASCII("api_test/override/newtab"), 1);
    EXPECT_EQ(extension,
              extensions::GetExtensionOverridingNewTabPage(profile()));
    return extension;
  }

  // Sets a new default search provider. The new search provider will be one
  // shows in the default search provider list iff
  // |new_search_shows_in_default_list| is true.  If non-null,
  // |new_search_name_out| will be populated with the new search provider's
  // name.
  void SetNewDefaultSearch(bool new_search_shows_in_default_list,
                           const TemplateURL** new_turl_out) {
    // Find a search provider that isn't Google, and set it as the default.
    TemplateURLService* const template_url_service = GetTemplateURLService();
    TemplateURLService::TemplateURLVector template_urls =
        template_url_service->GetTemplateURLs();
    auto iter = std::ranges::find_if(
        template_urls, [template_url_service, new_search_shows_in_default_list](
                           const TemplateURL* turl) {
          return !turl->HasGoogleBaseURLs(
                     template_url_service->search_terms_data()) &&
                 template_url_service->ShowInDefaultList(turl) ==
                     new_search_shows_in_default_list;
        });
    ASSERT_NE(template_urls.end(), iter);
    // iter != template_urls.end());
    template_url_service->SetUserSelectedDefaultSearchProvider(*iter);
    if (new_turl_out) {
      *new_turl_out = *iter;
    }
  }

  TemplateURLService* GetTemplateURLService() {
    return TemplateURLServiceFactory::GetForProfile(profile());
  }
};

// The chrome_settings_overrides API that allows extensions to override the
// default search provider is only available on Windows and Mac.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

class SearchOverriddenParamsProvidersBrowserTest
    : public SettingsOverriddenParamsProvidersBrowserTest,
      public testing::WithParamInterface<bool> {
 protected:
  SearchOverriddenParamsProvidersBrowserTest() {
    feature_list_.InitWithFeatureState(
        extensions_features::kSearchEngineExplicitChoiceDialog, GetParam());
  }

  bool IsExplicitChoiceDialog() const { return GetParam(); }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// NOTE: It's very unfortunate that this has to be a browsertest. Unfortunately,
// a few bits here - the TemplateURLService in particular - don't play nicely
// with a unittest environment.
IN_PROC_BROWSER_TEST_P(SearchOverriddenParamsProvidersBrowserTest,
                       GetExtensionControllingSearch) {
  // With no extensions installed, there should be no controlling extension.
  EXPECT_EQ(std::nullopt, GetSearchOverriddenParamsSync(web_contents()));

  // Install an extension, but not one that overrides the default search engine.
  // There should still be no controlling extension.
  InstallExtensionWithPermissionsGranted(
      test_data_dir_.AppendASCII("simple_with_icon"), 1);
  EXPECT_EQ(std::nullopt, GetSearchOverriddenParamsSync(web_contents()));

  // Finally, install an extension that overrides the default search engine.
  // It should be the controlling extension.
  const extensions::Extension* search_extension =
      AddExtensionControllingSearch();
  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  ASSERT_TRUE(params);
  EXPECT_EQ(search_extension->id(), params->controlling_extension_id);

  if (IsExplicitChoiceDialog()) {
    EXPECT_TRUE(params->content.dialog_title.contains(u"Confirm"));
    EXPECT_TRUE(params->content.message.contains(u"Search Override Extension"));

    ASSERT_TRUE(params->content.previous_setting);
    EXPECT_EQ(u"Google", params->content.previous_setting->text);
    EXPECT_EQ(u"Your previous choice",
              params->content.previous_setting->description);

    ASSERT_TRUE(params->content.new_setting);
    EXPECT_EQ(u"example.com", params->content.new_setting->text);
    EXPECT_EQ(u"Changed to this by extension",
              params->content.new_setting->description);
  } else {
    EXPECT_EQ(u"Change back to Google Search?", params->content.dialog_title);

    // Validate the body message, since it has a bit of formatting applied.
    EXPECT_EQ(
        u"The \"Search Override Extension\" extension changed search to use "
        "example.com",
        params->content.message);
  }
}

IN_PROC_BROWSER_TEST_P(SearchOverriddenParamsProvidersBrowserTest,
                       LongNameExtensionControllingSearch) {
  // With no extensions installed, there should be no controlling extension.
  ASSERT_EQ(std::nullopt, GetSearchOverriddenParamsSync(web_contents()));

  // Install an extensions which overrides the default search engine and has a
  // long name.
  const extensions::Extension* search_extension =
      AddExtensionControllingSearch("search_provider_override_long_name");
  std::optional<const ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  ASSERT_TRUE(params);
  EXPECT_EQ(search_extension->id(), params->controlling_extension_id);

  const std::u16string extension_name =
      base::UTF8ToUTF16(search_extension->name());
  const std::u16string truncated_name =
      extensions::ui_util::GetFixupExtensionNameForUIDisplay(extension_name);
  ASSERT_LT(truncated_name.size(), extension_name.size());

  EXPECT_TRUE(params->content.message.contains(truncated_name));
  EXPECT_FALSE(params->content.message.contains(extension_name));
}

IN_PROC_BROWSER_TEST_P(SearchOverriddenParamsProvidersBrowserTest,
                       GetExtensionControllingSearch_NonGoogleSearch) {
  constexpr bool kNewSearchShowsInDefaultList = true;
  const TemplateURL* new_turl = nullptr;
  SetNewDefaultSearch(kNewSearchShowsInDefaultList, &new_turl);
  ASSERT_TRUE(new_turl);
  std::string new_search_name = base::UTF16ToUTF8(new_turl->short_name());

  const extensions::Extension* extension = AddExtensionControllingSearch();
  ASSERT_TRUE(extension);

  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  ASSERT_TRUE(params);
  if (IsExplicitChoiceDialog()) {
    ASSERT_TRUE(params->content.previous_setting);
    EXPECT_EQ(base::UTF8ToUTF16(new_search_name),
              params->content.previous_setting->text);
  } else {
    EXPECT_EQ(base::StringPrintf("Change back to %s?", new_search_name.c_str()),
              base::UTF16ToUTF8(params->content.dialog_title));
  }
}

IN_PROC_BROWSER_TEST_P(SearchOverriddenParamsProvidersBrowserTest,
                       GetExtensionControllingSearch_NonDefaultSearch) {
  // Create and set a search provider that isn't one of the built-in default
  // options.
  TemplateURLService* const template_url_service = GetTemplateURLService();
  template_url_service->Add(
      std::make_unique<TemplateURL>(*GenerateDummyTemplateURLData("test")));

  constexpr bool kNewSearchShowsInDefaultList = false;
  SetNewDefaultSearch(kNewSearchShowsInDefaultList, nullptr);

  const extensions::Extension* extension = AddExtensionControllingSearch();
  ASSERT_TRUE(extension);

  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  ASSERT_TRUE(params);
  if (IsExplicitChoiceDialog()) {
    ASSERT_TRUE(params->content.previous_setting);
    EXPECT_EQ(u"Bookmarks", params->content.previous_setting->text);
  } else {
    EXPECT_EQ("Did you mean to change your search provider?",
              base::UTF16ToUTF8(params->content.dialog_title));
  }
}

IN_PROC_BROWSER_TEST_P(
    SearchOverriddenParamsProvidersBrowserTest,
    GetExtensionControllingSearch_MultipleSearchProvidingExtensions) {
  const extensions::Extension* first_extension =
      AddExtensionControllingSearch();
  ASSERT_TRUE(first_extension);

  extensions::TestExtensionDir second_extension_dir;
  second_extension_dir.WriteManifest(
      R"({
             "name": "Simple Search Override",
             "version": "0.1",
             "manifest_version": 3,
             "chrome_settings_overrides": {
               "search_provider": {
                 "search_url": "https://example.com/?q={searchTerms}",
                 "name": "New Search",
                 "keyword": "word",
                 "encoding": "UTF-8",
                 "favicon_url": "https://example.com/favicon.ico",
                 "is_default": true
               }
             }
          })");
  const extensions::Extension* second_extension =
      InstallExtensionWithPermissionsGranted(
          second_extension_dir.UnpackedPath(), 1);
  ASSERT_TRUE(second_extension);

  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  ASSERT_TRUE(params);
  if (IsExplicitChoiceDialog()) {
    ASSERT_TRUE(params->content.previous_setting);
    EXPECT_EQ(u"example.com", params->content.previous_setting->text);
    EXPECT_TRUE(params->content.previous_setting->image.IsVectorIcon());
    ASSERT_TRUE(params->content.new_setting);
    EXPECT_EQ(u"example.com", params->content.new_setting->text);
  } else {
    EXPECT_EQ(u"Did you mean to change your search provider?",
              params->content.dialog_title);
  }
}

// Tests that null params are returned (indicating no dialog should be shown)
// when an extension overrides search to the same domain that was previously
// used using a prepopulated id.
IN_PROC_BROWSER_TEST_P(SearchOverriddenParamsProvidersBrowserTest,
                       SearchOverriddenToSameSearch_PrepopulatedId) {
  constexpr bool kNewSearchShowsInDefaultList = true;
  const TemplateURL* new_turl = nullptr;
  SetNewDefaultSearch(kNewSearchShowsInDefaultList, &new_turl);
  ASSERT_TRUE(new_turl);
  // Google's ID is the lowest valid ID (1); the new engine must be greater.
  constexpr int kGooglePrepopulateId = 1;
  EXPECT_GT(new_turl->prepopulate_id(), kGooglePrepopulateId);

  constexpr char kManifestTemplate[] =
      R"({
           "name": "Search Override Extension",
           "version": "0.1",
           "manifest_version": 2,
           "chrome_settings_overrides": {
             "search_provider": {
               "search_url": "%s/?q={searchTerms}",
               "prepopulated_id": %d,
               "is_default": true
             }
           },
           "permissions": ["storage"]
         })";
  extensions::TestExtensionDir test_dir;

  GURL search_url =
      new_turl->GenerateSearchURL(GetTemplateURLService()->search_terms_data());
  test_dir.WriteManifest(base::StringPrintf(
      kManifestTemplate, search_url.DeprecatedGetOriginAsURL().spec().c_str(),
      new_turl->prepopulate_id()));

  const extensions::Extension* extension =
      InstallExtensionWithPermissionsGranted(test_dir.UnpackedPath(), 1);
  ASSERT_TRUE(extension);
  EXPECT_EQ(extension,
            extensions::GetExtensionOverridingSearchEngine(profile()));

  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  EXPECT_FALSE(params) << "Unexpected params: " << params->content.dialog_title;
}

// Tests that null params are returned (indicating no dialog should be shown)
// when an extension overrides search to the same domain that was previously
// used using a custom search definition.
IN_PROC_BROWSER_TEST_P(SearchOverriddenParamsProvidersBrowserTest,
                       SearchOverriddenToSameSearch_SameDomain) {
  constexpr bool kNewSearchShowsInDefaultList = true;
  const TemplateURL* new_turl = nullptr;
  SetNewDefaultSearch(kNewSearchShowsInDefaultList, &new_turl);
  ASSERT_TRUE(new_turl);
  // Google's ID is the lowest valid ID (1); the new engine must be greater.
  constexpr int kGooglePrepopulateId = 1;
  EXPECT_GT(new_turl->prepopulate_id(), kGooglePrepopulateId);

  constexpr char kManifestTemplate[] =
      R"({
           "name": "Search Override Extension",
           "version": "0.1",
           "manifest_version": 2,
           "chrome_settings_overrides": {
             "search_provider": {
               "search_url": "%s/?q={searchTerms}",
               "name": "New Search",
               "keyword": "word",
               "encoding": "UTF-8",
               "favicon_url": "https://example.com/favicon.ico",
               "is_default": true
             }
           },
           "permissions": ["storage"]
         })";

  extensions::TestExtensionDir test_dir;

  GURL search_url =
      new_turl->GenerateSearchURL(GetTemplateURLService()->search_terms_data());
  test_dir.WriteManifest(base::StringPrintf(
      kManifestTemplate, search_url.DeprecatedGetOriginAsURL().spec().c_str()));

  const extensions::Extension* extension =
      InstallExtensionWithPermissionsGranted(test_dir.UnpackedPath(), 1);
  ASSERT_TRUE(extension);
  EXPECT_EQ(extension,
            extensions::GetExtensionOverridingSearchEngine(profile()));

  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  EXPECT_FALSE(params) << "Unexpected params: " << params->content.dialog_title;
}

// Tests that the settings overridden dialog isn't shown for a simple override
// extension, but would be if the extension is then updated to have more
// capabilities.
IN_PROC_BROWSER_TEST_P(SearchOverriddenParamsProvidersBrowserTest,
                       DialogNotShownForSimpleOverridesAndIsAfterUpdate) {
  extensions::TestExtensionDir dir_v1;
  static constexpr char kManifestV1[] =
      R"({
           "name": "Search Override",
           "version": "0.1",
           "manifest_version": 3,
           "chrome_settings_overrides": {
             "search_provider": {
               "search_url": "https://example.com/?q={searchTerms}",
               "name": "New Search",
               "keyword": "word",
               "encoding": "UTF-8",
               "favicon_url": "https://example.com/favicon.ico",
               "is_default": true
             }
           }
         })";
  dir_v1.WriteManifest(kManifestV1);
  dir_v1.WriteFile(FILE_PATH_LITERAL("page.html"), "hello world!");

  extensions::TestExtensionDir dir_v2;
  static constexpr char kManifestV2[] =
      R"({
           "name": "Search Override",
           "version": "0.2",
           "manifest_version": 3,
           "chrome_settings_overrides": {
             "search_provider": {
               "search_url": "https://example.com/?q={searchTerms}",
               "name": "New Search",
               "keyword": "word",
               "encoding": "UTF-8",
               "favicon_url": "https://example.com/favicon.ico",
               "is_default": true
             }
           },
           "permissions": ["storage"]
         })";
  dir_v2.WriteManifest(kManifestV2);
  dir_v2.WriteFile(FILE_PATH_LITERAL("page.html"), "hello world!");

  // Borrow a .pem file to have consistent IDs in the .crx files.
  base::FilePath pem_path =
      test_data_dir_.AppendASCII("permissions/update.pem");

  base::ScopedAllowBlockingForTesting allow_blocking;
  base::ScopedTempDir scoped_temp_dir;
  EXPECT_TRUE(scoped_temp_dir.CreateUniqueTempDir());

  base::FilePath v1_crx_path = PackExtensionWithOptions(
      dir_v1.UnpackedPath(), scoped_temp_dir.GetPath().AppendASCII("v1.crx"),
      pem_path, base::FilePath());
  base::FilePath v2_crx_path = PackExtensionWithOptions(
      dir_v2.UnpackedPath(), scoped_temp_dir.GetPath().AppendASCII("v2.crx"),
      pem_path, base::FilePath());

  // Install v1 of the extension. Since this is a simple override, the dialog
  // should not display.
  const extensions::Extension* extension =
      InstallExtensionWithPermissionsGranted(v1_crx_path, 1);
  ASSERT_TRUE(extension);

  {
    std::optional<ExtensionSettingsOverriddenDialog::Params> params =
        GetSearchOverriddenParamsSync(web_contents());
    ASSERT_TRUE(params);
    ExtensionSettingsOverriddenDialog controller(std::move(*params),
                                                 *profile());
    EXPECT_FALSE(controller.ShouldShow());
  }

  // Update the extension to v2. Now, the dialog *should* show, since the
  // extension is no longer considered a simple override.
  extension = UpdateExtension(extension->id(), v2_crx_path, 0);
  EXPECT_TRUE(extension);

  {
    std::optional<ExtensionSettingsOverriddenDialog::Params> params =
        GetSearchOverriddenParamsSync(web_contents());
    ASSERT_TRUE(params);
    ExtensionSettingsOverriddenDialog controller(std::move(*params),
                                                 *profile());
    EXPECT_TRUE(controller.ShouldShow());
  }
}

// Tests that null params are returned (indicating no dialog should be shown)
// when an extension overrides search to the same domain that was previously
// set by another extension.
IN_PROC_BROWSER_TEST_P(
    SearchOverriddenParamsProvidersBrowserTest,
    SearchOverriddenToSameSearch_SameDomainExistingExtensionOverride) {
  // With no extensions installed, there should be no controlling extension.
  EXPECT_EQ(std::nullopt, GetSearchOverriddenParamsSync(web_contents()));

  // Install a simple search override extension, that shouldn't trigger the
  // prompt.
  extensions::TestExtensionDir extension_dir_1;

  extension_dir_1.WriteManifest(
      R"({
           "name": "Simple Search Override",
           "version": "0.1",
           "manifest_version": 3,
           "chrome_settings_overrides": {
             "search_provider": {
               "search_url": "https://example.com/?q={searchTerms}",
               "name": "New Search",
               "keyword": "word",
               "encoding": "UTF-8",
               "favicon_url": "https://example.com/favicon.ico",
               "is_default": true
             }
           }
        })");
  const extensions::Extension* extension_1 =
      InstallExtensionWithPermissionsGranted(extension_dir_1.UnpackedPath(), 1);

  ASSERT_TRUE(extension_1);
  EXPECT_EQ(extension_1,
            extensions::GetExtensionOverridingSearchEngine(profile()));
  {
    std::optional<ExtensionSettingsOverriddenDialog::Params> params =
        GetSearchOverriddenParamsSync(web_contents());
    ASSERT_TRUE(params);
    ExtensionSettingsOverriddenDialog controller(std::move(*params),
                                                 *profile());
    EXPECT_FALSE(controller.ShouldShow());
  }

  // Install a second search override extension, that shouldn't trigger prompt
  // since it sets the same search domain as the previous extension.
  extensions::TestExtensionDir extension_dir_2;
  extension_dir_2.WriteManifest(
      R"({
           "name": "Second Search Override",
           "version": "0.1",
           "manifest_version": 3,
           "chrome_settings_overrides": {
             "search_provider": {
               "search_url": "https://example.com/?q={searchTerms}",
               "name": "New Search",
               "keyword": "word",
               "encoding": "UTF-8",
               "favicon_url": "https://example.com/favicon.ico",
               "is_default": true
             }
           },
           "permissions": ["storage"]
        })");
  const extensions::Extension* extension_2 =
      InstallExtensionWithPermissionsGranted(extension_dir_2.UnpackedPath(), 1);

  ASSERT_TRUE(extension_2);
  EXPECT_EQ(extension_2,
            extensions::GetExtensionOverridingSearchEngine(profile()));
  {
    std::optional<ExtensionSettingsOverriddenDialog::Params> params =
        GetSearchOverriddenParamsSync(web_contents());
    EXPECT_FALSE(params) << "Unexpected params: "
                         << params->content.dialog_title;
  }
}

// Param true means Explicit Choice dialog.
INSTANTIATE_TEST_SUITE_P(All,
                         SearchOverriddenParamsProvidersBrowserTest,
                         testing::Bool(),
                         [](const testing::TestParamInfo<bool>& info) {
                           return info.param ? "ExplicitChoice" : "KeepOrBack";
                         });

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

// Tests the dialog display when the default search engine has changed; in this
// case, we should display the generic dialog.
IN_PROC_BROWSER_TEST_F(SettingsOverriddenParamsProvidersBrowserTest,
                       DialogParamsWithNonDefaultSearch) {
  // Find a search provider that isn't Google, and set it as the default.
  TemplateURLService* const template_url_service = GetTemplateURLService();
  TemplateURLService::TemplateURLVector template_urls =
      template_url_service->GetTemplateURLs();
  auto iter = std::ranges::find_if_not(
      template_urls, [template_url_service](const TemplateURL* turl) {
        // For the test, we can be a bit lazier and just use HasGoogleBaseURLs()
        // instead of getting the full search URL.
        return turl->HasGoogleBaseURLs(
            template_url_service->search_terms_data());
      });
  ASSERT_TRUE(iter != template_urls.end());
  template_url_service->SetUserSelectedDefaultSearchProvider(*iter);

  const extensions::Extension* extension = AddExtensionControllingNewTab();

  // The dialog should be the generic version, rather than prompting to go back
  // to the default.
  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      settings_overridden_params::GetNtpOverriddenParams(profile());
  ASSERT_TRUE(params);
  EXPECT_EQ(extension->id(), params->controlling_extension_id);
  EXPECT_EQ(u"Did you mean to change this page?", params->content.dialog_title);
}

// Test class specific to the explicit-choice dialog variation.
class ExtensionControllingSearchExplicitChoiceParamsBrowserTest
    : public SettingsOverriddenParamsProvidersBrowserTest {
 public:
  ExtensionControllingSearchExplicitChoiceParamsBrowserTest() {
    feature_list_.InitAndEnableFeature(
        extensions_features::kSearchEngineExplicitChoiceDialog);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// The chrome_settings_overrides API that allows extensions to override the
// default search provider is only available on Windows and Mac.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

IN_PROC_BROWSER_TEST_F(
    ExtensionControllingSearchExplicitChoiceParamsBrowserTest,
    NewExtensionOrPreviousGoogleSearch) {
  base::HistogramTester histogram_tester;
  const extensions::Extension* extension = AddExtensionControllingSearch();
  ASSERT_TRUE(extension);

  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  ASSERT_TRUE(params);

  EXPECT_TRUE(params->content.dialog_title.contains(u"Confirm"));
  EXPECT_TRUE(params->content.message.contains(
      base::StrCat({u"The extension \"", kSearchOverrideExtensionName,
                    u"\" changed your settings"})));

  auto previous_setting = params->content.previous_setting;
  ASSERT_TRUE(previous_setting.has_value());
  EXPECT_FALSE(previous_setting.value().image.IsEmpty());
  EXPECT_EQ(u"Google", previous_setting.value().text);
  EXPECT_EQ(u"Your previous choice", previous_setting.value().description);

  auto new_setting = params->content.new_setting;
  ASSERT_TRUE(new_setting.has_value());
  EXPECT_TRUE(new_setting.value().image.IsVectorIcon());
  EXPECT_EQ(new_setting.value().text, u"example.com");
  EXPECT_EQ(new_setting.value().description, u"Changed to this by extension");

  // Ensure that a fully-populated dialog was logged.
  histogram_tester.ExpectUniqueSample(
      "Extensions.SettingsOverridden.MissingParams",
      settings_overridden_params::MissingParams::kNone, 1);
}

IN_PROC_BROWSER_TEST_F(
    ExtensionControllingSearchExplicitChoiceParamsBrowserTest,
    NewExtensionOrPreviousNonPrepopulatedSearch) {
  // Create and set a custom (non-prepopulated) search provider.
  TemplateURLService* const template_url_service = GetTemplateURLService();
  std::unique_ptr<TemplateURLData> data =
      GenerateDummyTemplateURLData("custom");
  data->SetURL("https://customsearch.com/search?q={searchTerms}");
  TemplateURL* custom_turl =
      template_url_service->Add(std::make_unique<TemplateURL>(*data));
  template_url_service->SetUserSelectedDefaultSearchProvider(custom_turl);
  const extensions::Extension* extension = AddExtensionControllingSearch();
  ASSERT_TRUE(extension);

  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  ASSERT_TRUE(params);

  auto previous_setting = params->content.previous_setting;
  ASSERT_TRUE(previous_setting.has_value());
  // For a non-prepopulated search engine, the vector icon should be used,
  // and the text should be the formatted URL rather than the short name.
  EXPECT_TRUE(previous_setting.value().image.IsVectorIcon());
  EXPECT_EQ(u"customsearch.com", previous_setting.value().text);

  auto new_setting = params->content.new_setting;
  ASSERT_TRUE(new_setting.has_value());
  // The new setting (from the extension) is also non-prepopulated.
  EXPECT_TRUE(new_setting.value().image.IsVectorIcon());
  EXPECT_EQ(u"example.com", new_setting.value().text);
}

IN_PROC_BROWSER_TEST_F(
    ExtensionControllingSearchExplicitChoiceParamsBrowserTest,
    PrepopulatedSearches) {
  // The default search engine initially is Google (prepopulated).
  // Find another prepopulated search engine for the extension to use.
  TemplateURLService* const template_url_service = GetTemplateURLService();
  TemplateURLService::TemplateURLVector template_urls =
      template_url_service->GetTemplateURLs();
  auto iter = std::ranges::find_if(
      template_urls, [template_url_service](const TemplateURL* turl) {
        return !turl->HasGoogleBaseURLs(
                   template_url_service->search_terms_data()) &&
               template_url_service->ShowInDefaultList(turl) &&
               turl->prepopulate_id() > 0;
      });
  ASSERT_NE(template_urls.end(), iter);
  const TemplateURL* extension_turl = *iter;

  constexpr char kManifestTemplate[] =
      R"({
           "name": "Search Override Extension",
           "version": "0.1",
           "manifest_version": 2,
           "chrome_settings_overrides": {
             "search_provider": {
               "name": "%s",
               "keyword": "%s",
               "search_url": "%s",
               "prepopulated_id": %d,
               "is_default": true
             }
           },
           "permissions": ["storage"]
         })";

  extensions::TestExtensionDir test_dir;
  test_dir.WriteManifest(base::StringPrintf(
      kManifestTemplate,
      base::UTF16ToUTF8(extension_turl->short_name()).c_str(),
      base::UTF16ToUTF8(extension_turl->keyword()).c_str(),
      extension_turl->url().c_str(), extension_turl->prepopulate_id()));

  const extensions::Extension* extension =
      InstallExtensionWithPermissionsGranted(test_dir.UnpackedPath(), 1);
  ASSERT_TRUE(extension);

  std::optional<ExtensionSettingsOverriddenDialog::Params> params =
      GetSearchOverriddenParamsSync(web_contents());
  ASSERT_TRUE(params);

  auto previous_setting = params->content.previous_setting;
  ASSERT_TRUE(previous_setting.has_value());
  // Prepopulated -> Should have an icon (either a fetched favicon or a
  // prepopulated vector icon like the Google logo). We assert it is not empty.
  EXPECT_FALSE(previous_setting.value().image.IsEmpty());
  EXPECT_EQ(u"Google", previous_setting.value().text);

  auto new_setting = params->content.new_setting;
  ASSERT_TRUE(new_setting.has_value());
  // Prepopulated -> Should have an icon.
  EXPECT_FALSE(new_setting.value().image.IsEmpty());
  EXPECT_EQ(extension_turl->short_name(), new_setting.value().text);
}

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
