// Copyright 2016 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/webui/settings/settings_ui.h"

#include <string>

#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/glic/glic_enums.h"
#include "chrome/browser/glic/glic_pref_names.h"
#include "chrome/browser/glic/glic_pref_names_internal.h"
#include "chrome/browser/glic/public/features.h"
#include "chrome/browser/profiles/batch_upload/batch_upload_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/hats/mock_hats_service.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/core/browser/payments/payments_service_url.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/prefs/pref_service.h"
#include "components/sync/base/command_line_switches.h"
#include "components/sync/base/features.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"

typedef InProcessBrowserTest SettingsUITest;

using ::testing::_;
using ui_test_utils::NavigateToURL;

IN_PROC_BROWSER_TEST_F(SettingsUITest, ViewSourceDoesntCrash) {
  ASSERT_TRUE(NavigateToURL(
      browser(),
      GURL(content::kViewSourceScheme + std::string(":") +
           chrome::kChromeUISettingsURL + std::string("strings.js"))));
}

// Catch lifetime issues in message handlers. There was previously a problem
// with PrefMember calling Init again after Destroy.
IN_PROC_BROWSER_TEST_F(SettingsUITest, ToggleJavaScript) {
  ASSERT_TRUE(NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL)));

  const auto& handlers = *browser()
                              ->GetTabStripModel()
                              ->GetActiveWebContents()
                              ->GetWebUI()
                              ->GetHandlersForTesting();

  for (const std::unique_ptr<content::WebUIMessageHandler>& handler :
       handlers) {
    handler->AllowJavascriptForTesting();
    handler->DisallowJavascript();
    handler->AllowJavascriptForTesting();
  }
}

IN_PROC_BROWSER_TEST_F(SettingsUITest, TriggerHappinessTrackingSurveys) {
  MockHatsService* mock_hats_service_ = static_cast<MockHatsService*>(
      HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
          browser()->GetProfile(), base::BindRepeating(&BuildMockHatsService)));
  EXPECT_CALL(*mock_hats_service_,
              LaunchDelayedSurveyForWebContents(kHatsSurveyTriggerSettings, _,
                                                _, _, _, _, _, _, _, _));
  ASSERT_TRUE(NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL)));
  base::RunLoop().RunUntilIdle();
}

// Test fixture for testing the kDisableSync flag.
class SettingsUITestDisableSync : public SettingsUITest {
 public:
  SettingsUITestDisableSync() {
    std::vector<base::test::FeatureRef> enabled_features;
#if !BUILDFLAG(IS_CHROMEOS)
    enabled_features.push_back(syncer::kUnoPhase2FollowUp);
#else
    enabled_features.push_back(syncer::kReplaceSyncPromosWithSignInPromos);
#endif
    scoped_feature_list_.InitWithFeatures(enabled_features,
                                          /*disabled_features=*/{});
  }

  void SetUp() override {
    // Append the switch *before* the profile is built.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(syncer::kDisableSync);
    SettingsUITest::SetUp();
  }

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

// Regression test for crbug.com/484893496.
// This mainly intends to check that `CreateBatchUploadPromoHandler()` does not
// crash when the sync service is null.
IN_PROC_BROWSER_TEST_F(
    SettingsUITestDisableSync,
    CreateBatchUploadPromoHandlerWithoutSyncServiceDoesNotCrash) {
  ASSERT_TRUE(
      NavigateToURL(browser(), GURL(base::StrCat({chrome::kChromeUISettingsURL,
                                                  chrome::kPeopleSubPage}))));

  ASSERT_EQ(nullptr,
            SyncServiceFactory::GetForProfile(browser()->GetProfile()));

  // Wait for sync controls to load which would initialize the batch upload
  // service if the sync service was not null.
  ASSERT_TRUE(
      content::ExecJs(browser()->GetTabStripModel()->GetActiveWebContents(),
                      R"((() => {
                           return customElements.whenDefined(
                              'settings-sync-controls');
                         })())"));

  EXPECT_EQ(nullptr,
            BatchUploadServiceFactory::GetForProfile(browser()->GetProfile(),
                                                     /*create=*/false));
}

IN_PROC_BROWSER_TEST_F(SettingsUITest, GoogleSearchAiModeWorkspaceUrl) {
  ASSERT_TRUE(NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL)));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  // Wait for settings UI to be loaded.
  ASSERT_TRUE(content::ExecJs(web_contents,
                              "customElements.whenDefined('settings-ui');"));

  // Evaluate the loadTimeData string in settings page using dynamic import.
  std::string url_val =
      content::EvalJs(
          web_contents,
          "import('chrome://resources/js/load_time_data.js').then(m => "
          "m.loadTimeData.getString('googleSearchAiModeWorkspaceUrl'))")
          .ExtractString();

  EXPECT_EQ(url_val, "https://myactivity.google.com/search-services/apps");
}

IN_PROC_BROWSER_TEST_F(SettingsUITest, GoogleSearchAiModeRestrictedUrl) {
  ASSERT_TRUE(NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL)));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  // Wait for settings UI to be loaded.
  ASSERT_TRUE(content::ExecJs(web_contents,
                              "customElements.whenDefined('settings-ui');"));

  // Evaluate the loadTimeData string in settings page using dynamic import.
  std::string url_val =
      content::EvalJs(
          web_contents,
          "import('chrome://resources/js/load_time_data.js').then(m => "
          "m.loadTimeData.getString('googleSearchAiModeRestrictedUrl'))")
          .ExtractString();

  EXPECT_EQ(url_val, "https://myactivity.google.com/myactivity");
}

class SettingsUITestGlicDisabledButAnchored : public SettingsUITest {
 public:
  SettingsUITestGlicDisabledButAnchored() {
    scoped_feature_list_.InitWithFeatures(
        {features::kGlicAnchorEntryPointForOnboardedUsers},  // Enabled
        {features::kGlic}                                    // Disabled
    );
  }

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

IN_PROC_BROWSER_TEST_F(SettingsUITestGlicDisabledButAnchored, DoesNotCrash) {
  // Set Glic as completed onboarding.
  browser()->GetProfile()->GetPrefs()->SetInteger(
      glic::prefs::kGlicCompletedFre,
      static_cast<int>(glic::prefs::FreStatus::kCompleted));

  // Navigate to Settings. This should not crash.
  ASSERT_TRUE(NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL)));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  // Wait for settings UI to be loaded.
  ASSERT_TRUE(content::ExecJs(web_contents,
                              "customElements.whenDefined('settings-ui');"));

  // Check that the Glic settings section is hidden because the killswitch
  // (kGlic) is disabled.
  bool show_glic_settings =
      content::EvalJs(
          web_contents,
          "import('chrome://resources/js/load_time_data.js').then(m => "
          "m.loadTimeData.getBoolean('showGlicSettings'))")
          .ExtractBool();

  EXPECT_FALSE(show_glic_settings);
}

class SettingsUIWalletReminderNoticeTest : public SettingsUITest {
 public:
  SettingsUIWalletReminderNoticeTest() {
    scoped_feature_list_.InitAndEnableFeature(
        autofill::features::kAutofillEnableWalletReminderNotice);
  }

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

IN_PROC_BROWSER_TEST_F(SettingsUIWalletReminderNoticeTest,
                       ManageCreditCardsLabel) {
  // Navigate to settings. This should not crash.
  ASSERT_TRUE(NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL)));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  // Wait for settings UI to be loaded.
  ASSERT_TRUE(content::ExecJs(web_contents,
                              "customElements.whenDefined('settings-ui');"));

  // Retrieve the string added to loadTimeData.
  std::string label =
      content::EvalJs(
          web_contents,
          "import('chrome://resources/js/load_time_data.js').then(m => "
          "m.loadTimeData.getString('manageCreditCardsLabel'))")
          .ExtractString();

  std::string expected_label = base::UTF16ToUTF8(l10n_util::GetStringFUTF16(
      IDS_SETTINGS_PAYMENTS_MANAGE_WALLET_DATA,
      base::UTF8ToUTF16(autofill::payments::GetManageSettingsUrl().spec()),
      base::UTF8ToUTF16(autofill::payments::GetManageInstrumentsUrl().spec()),
      base::UTF8ToUTF16(autofill::payments::GetManagePassesUrl().spec())));

  EXPECT_EQ(label, expected_label);
}
