// Copyright 2025 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/default_browser/default_browser_manager.h"

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

#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/default_browser/default_browser_controller.h"
#include "chrome/browser/default_browser/default_browser_features.h"
#include "chrome/browser/default_browser/default_browser_notification_observer.h"
#include "chrome/browser/default_browser/test_support/fake_default_browser_setter.h"
#include "chrome/browser/default_browser/test_support/fake_shell_delegate.h"
#include "chrome/browser/global_features.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/omnibox/omnibox_next_features.h"
#include "chrome/browser/ui/toasts/api/toast_id.h"
#include "chrome/browser/ui/toasts/toast_controller.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/unowned_user_data/user_data_factory.h"

#if BUILDFLAG(IS_WIN)
#include "base/test/test_reg_util_win.h"
#include "base/win/registry.h"
#endif  // BUILDFLAG(IS_WIN)

namespace default_browser {

namespace {

#if BUILDFLAG(IS_WIN)
constexpr wchar_t kProgIdValue[] = L"ProgId";

constexpr wchar_t kRegistryPath[] =
    L"Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\h"
    L"ttp\\UserChoice";

#endif  // BUILDFLAG(IS_WIN)

}  // namespace

#if BUILDFLAG(IS_WIN)
class DefaultBrowserManagerWinBrowserTest : public InProcessBrowserTest {
 protected:
  DefaultBrowserManagerWinBrowserTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enable_features*/ {kDefaultBrowserFramework,
                             kDefaultBrowserChangedOsNotification},
        /*disabled_features=*/{omnibox::internal::kWebUIOmniboxPopup,
                               omnibox::internal::kWebUIOmniboxAimPopup});
  }
  ~DefaultBrowserManagerWinBrowserTest() override = default;

  void SetUp() override {
    ASSERT_NO_FATAL_FAILURE(
        registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
    base::win::RegKey key;
    ASSERT_EQ(ERROR_SUCCESS,
              key.Create(HKEY_CURRENT_USER, kRegistryPath, KEY_WRITE));
    InProcessBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    display_service_tester_ =
        std::make_unique<NotificationDisplayServiceTester>(
            browser()->GetProfile());
  }

  void TearDownOnMainThread() override {
    display_service_tester_.reset();
    fake_shell_delegate_ptr_ = nullptr;
    InProcessBrowserTest::TearDownOnMainThread();
  }

  void SetUpInProcessBrowserTestFixture() override {
    global_feature_override_ =
        GlobalFeatures::GetUserDataFactoryForTesting().AddOverrideForTesting(
            base::BindLambdaForTesting([&](BrowserProcess& browser_process) {
              auto fake_shell_delegate = std::make_unique<FakeShellDelegate>();
              fake_shell_delegate_ptr_ = fake_shell_delegate.get();
              return std::make_unique<DefaultBrowserManager>(
                  &browser_process, std::move(fake_shell_delegate),
                  base::BindLambdaForTesting(
                      [&]() { return browser()->GetProfile(); }));
            }));
  }

  // Helper to simulate the environment change that triggers the notification.
  void TriggerNotification() {
    // 1. Ensure Chrome believes it is NOT default.
    fake_shell_delegate_ptr_->set_default_state(shell_integration::IS_DEFAULT);

    // 2. Set up the registry to look like Chrome was default initially (so the
    //    change to something else is significant).
    CreateDefaultBrowserKey(L"ChromeHTML");
    content::RunAllTasksUntilIdle();

    // 3. Prepare to listen for the async monitor event.
    base::test::TestFuture<DefaultBrowserState> monitor_future;
    auto* manager = DefaultBrowserManager::From(g_browser_process);
    base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
        monitor_future.GetRepeatingCallback());

    // 4. Simulate external registry change to a different browser.
    ChangeDefaultBrowserProgId(L"VanadiumHTML");

    // 5. Wait for the manager to detect the change and update state.
    fake_shell_delegate_ptr_->set_default_state(shell_integration::NOT_DEFAULT);
    EXPECT_EQ(monitor_future.Take(), shell_integration::NOT_DEFAULT);

    // 6. Allow the notification tasks to post to the UI thread.
    content::RunAllTasksUntilIdle();
  }

  void CreateDefaultBrowserKey(const std::wstring& prog_id) {
    base::win::RegKey key;
    ASSERT_EQ(ERROR_SUCCESS,
              key.Create(HKEY_CURRENT_USER, kRegistryPath, KEY_WRITE));
    ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kProgIdValue, prog_id.c_str()));
  }

  void ChangeDefaultBrowserProgId(const std::wstring& prog_id) {
    base::win::RegKey key;
    ASSERT_EQ(ERROR_SUCCESS,
              key.Open(HKEY_CURRENT_USER, kRegistryPath, KEY_WRITE));
    ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kProgIdValue, prog_id.c_str()));
  }

  raw_ptr<FakeShellDelegate> fake_shell_delegate_ptr_;
  std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
  base::HistogramTester histogram_tester_;

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  ui::UserDataFactory::ScopedOverride global_feature_override_;
  registry_util::RegistryOverrideManager registry_override_manager_;
};

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       ChangeIsDetectedAndObserverIsNotified) {
  CreateDefaultBrowserKey(L"ChromeHTML");
  DefaultBrowserManager* manager =
      DefaultBrowserManager::From(g_browser_process);
  ASSERT_TRUE(manager);

  base::test::TestFuture<DefaultBrowserState> future;
  base::CallbackListSubscription subscription =
      manager->RegisterDefaultBrowserChanged(future.GetRepeatingCallback());
  ChangeDefaultBrowserProgId(L"VanadiumHTML");
  EXPECT_TRUE(future.Wait());
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       SubscriptionDestroyedPreventsCallback) {
  CreateDefaultBrowserKey(L"ChromeHTML");
  DefaultBrowserManager* manager =
      DefaultBrowserManager::From(g_browser_process);
  ASSERT_TRUE(manager);

  base::test::TestFuture<DefaultBrowserState> future;
  {
    base::CallbackListSubscription subscription =
        manager->RegisterDefaultBrowserChanged(future.GetRepeatingCallback());
  }

  ChangeDefaultBrowserProgId(L"VanadiumHTML");
  content::RunAllTasksUntilIdle();
  EXPECT_FALSE(future.IsReady());
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       AllRegisteredObserversAreNotified) {
  CreateDefaultBrowserKey(L"ChromeHTML");
  DefaultBrowserManager* manager =
      DefaultBrowserManager::From(g_browser_process);
  ASSERT_TRUE(manager);

  base::test::TestFuture<DefaultBrowserState> future1;
  base::test::TestFuture<DefaultBrowserState> future2;
  base::CallbackListSubscription subscription1 =
      manager->RegisterDefaultBrowserChanged(future1.GetRepeatingCallback());
  base::CallbackListSubscription subscription2 =
      manager->RegisterDefaultBrowserChanged(future2.GetRepeatingCallback());

  ChangeDefaultBrowserProgId(L"VanadiumHTML");
  EXPECT_TRUE(future1.Wait());
  EXPECT_TRUE(future2.Wait());
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       SubsequentChangesAreAlsoDetected) {
  CreateDefaultBrowserKey(L"ChromeHTML");
  DefaultBrowserManager* manager =
      DefaultBrowserManager::From(g_browser_process);
  ASSERT_TRUE(manager);

  {
    base::test::TestFuture<DefaultBrowserState> future_change1;
    base::CallbackListSubscription subscription =
        manager->RegisterDefaultBrowserChanged(
            future_change1.GetRepeatingCallback());
    ChangeDefaultBrowserProgId(L"VanadiumHTML");
    ASSERT_TRUE(future_change1.Wait()) << "First change was not detected.";
  }

  {
    base::test::TestFuture<DefaultBrowserState> future_change2;
    base::CallbackListSubscription subscription =
        manager->RegisterDefaultBrowserChanged(
            future_change2.GetRepeatingCallback());
    ChangeDefaultBrowserProgId(L"ManganeseHTML");
    EXPECT_TRUE(future_change2.Wait()) << "Second change was not detected.";
  }
}

IN_PROC_BROWSER_TEST_F(
    DefaultBrowserManagerWinBrowserTest,
    SubsequentChangesAreAlsoDetectedWithTheSameSubscription) {
  CreateDefaultBrowserKey(L"ChromeHTML");
  DefaultBrowserManager* manager =
      DefaultBrowserManager::From(g_browser_process);
  ASSERT_TRUE(manager);

  int call_count = 0;
  base::RunLoop run_loop1;
  base::RunLoop run_loop2;

  // This callback will be invoked for each default browser change.
  // On the first invocation, it will quit the first `run_loop`.
  // On the second, it will quit the second `run_loop`.
  auto subscription_callback = base::BindRepeating(
      [](int* count, base::RunLoop* loop1, base::RunLoop* loop2,
         DefaultBrowserState state) {
        (*count)++;
        if (*count == 1) {
          loop1->Quit();
        } else if (*count == 2) {
          loop2->Quit();
        }
      },
      &call_count, &run_loop1, &run_loop2);

  // Register the single subscription that will be used for the entire test.
  base::CallbackListSubscription subscription =
      manager->RegisterDefaultBrowserChanged(subscription_callback);

  // Trigger the first change.
  ChangeDefaultBrowserProgId(L"VanadiumHTML");
  run_loop1.Run();
  EXPECT_EQ(call_count, 1) << "First change was not detected.";

  // Trigger the second change. The same subscription should be notified again.
  ChangeDefaultBrowserProgId(L"ManganeseHTML");
  run_loop2.Run();
  EXPECT_EQ(call_count, 2) << "Second change was not detected.";
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       RegistryChangeTriggersSystemNotification) {
  TriggerNotification();

  auto notification = display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId);
  ASSERT_TRUE(notification.has_value());
  EXPECT_EQ(notification->title(),
            l10n_util::GetStringUTF16(IDS_DEFAULT_BROWSER_CHANGED_TITLE));
  EXPECT_EQ(notification->message(),
            l10n_util::GetStringUTF16(IDS_DEFAULT_BROWSER_CHANGED_MESSAGE));

  histogram_tester_.ExpectUniqueSample(
      "DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Shown", 1, 1);
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       NoNotificationWhenChromeRemainsDefault) {
  fake_shell_delegate_ptr_->set_default_state(shell_integration::IS_DEFAULT);
  CreateDefaultBrowserKey(L"ChromeHTML");

  base::test::TestFuture<DefaultBrowserState> monitor_future;
  auto* manager = DefaultBrowserManager::From(g_browser_process);
  base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
      monitor_future.GetRepeatingCallback());

  ChangeDefaultBrowserProgId(L"ChromeHTML");
  EXPECT_EQ(monitor_future.Take(), shell_integration::IS_DEFAULT);

  content::RunAllTasksUntilIdle();

  auto notification = display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId);
  EXPECT_FALSE(notification.has_value())
      << "Notification shown even though Chrome is default.";
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       ClickAcceptTriggersSetterAndMetric) {
  TriggerNotification();

  ASSERT_TRUE(display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId));

  display_service_tester_->SimulateClick(
      NotificationHandler::Type::DEFAULT_BROWSER_CHANGED,
      DefaultBrowserManager::kNotificationId,
      /*action_index=*/0,
      /*reply=*/std::nullopt);

  histogram_tester_.ExpectUniqueSample(
      "DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
      DefaultBrowserInteractionType::kAccepted, 1);

  histogram_tester_.ExpectBucketCount(
      "DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
      DefaultBrowserInteractionType::kDismissed, 0);

  EXPECT_FALSE(display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId));
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       ClickNoThanksDismissesAndRecordsMetric) {
  TriggerNotification();

  ASSERT_TRUE(display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId));

  display_service_tester_->SimulateClick(
      NotificationHandler::Type::DEFAULT_BROWSER_CHANGED,
      DefaultBrowserManager::kNotificationId,
      /*action_index=*/1,
      /*reply=*/std::nullopt);

  histogram_tester_.ExpectUniqueSample(
      "DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
      DefaultBrowserInteractionType::kDismissed, 1);

  histogram_tester_.ExpectBucketCount(
      "DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
      DefaultBrowserInteractionType::kAccepted, 0);

  EXPECT_FALSE(display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId));
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       UserCloseDismissesAndRecordsMetric) {
  TriggerNotification();

  ASSERT_TRUE(display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId));

  display_service_tester_->RemoveNotification(
      NotificationHandler::Type::DEFAULT_BROWSER_CHANGED,
      DefaultBrowserManager::kNotificationId,
      /*by_user=*/true);

  histogram_tester_.ExpectUniqueSample(
      "DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
      DefaultBrowserInteractionType::kDismissed, 1);

  histogram_tester_.ExpectBucketCount(
      "DefaultBrowser.ChangeDetectedNotification.ShellIntegration.Interaction",
      DefaultBrowserInteractionType::kAccepted, 0);
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       NoNotificationWhenRemainingNotDefault) {
  fake_shell_delegate_ptr_->set_default_state(shell_integration::NOT_DEFAULT);
  CreateDefaultBrowserKey(L"EdgeHTML");

  content::RunAllTasksUntilIdle();

  base::test::TestFuture<DefaultBrowserState> monitor_future;
  auto* manager = DefaultBrowserManager::From(g_browser_process);
  base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
      monitor_future.GetRepeatingCallback());

  ChangeDefaultBrowserProgId(L"VanadiumHTML");

  EXPECT_EQ(monitor_future.Take(), shell_integration::NOT_DEFAULT);
  content::RunAllTasksUntilIdle();

  auto notification = display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId);
  EXPECT_FALSE(notification.has_value());
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       NoNotificationWhenBecomingDefault) {
  fake_shell_delegate_ptr_->set_default_state(shell_integration::NOT_DEFAULT);
  CreateDefaultBrowserKey(L"EdgeHTML");
  content::RunAllTasksUntilIdle();

  base::test::TestFuture<DefaultBrowserState> monitor_future;
  auto* manager = DefaultBrowserManager::From(g_browser_process);
  base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
      monitor_future.GetRepeatingCallback());

  fake_shell_delegate_ptr_->set_default_state(shell_integration::IS_DEFAULT);
  ChangeDefaultBrowserProgId(L"ChromeHTML");

  EXPECT_EQ(monitor_future.Take(), shell_integration::IS_DEFAULT);
  content::RunAllTasksUntilIdle();

  auto notification = display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId);
  EXPECT_FALSE(notification.has_value());
}

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerWinBrowserTest,
                       NotificationShownOnTransitionFromDefault) {
  fake_shell_delegate_ptr_->set_default_state(shell_integration::IS_DEFAULT);
  CreateDefaultBrowserKey(L"ChromeHTML");
  content::RunAllTasksUntilIdle();

  base::test::TestFuture<DefaultBrowserState> monitor_future;
  auto* manager = DefaultBrowserManager::From(g_browser_process);
  base::CallbackListSubscription sub = manager->RegisterDefaultBrowserChanged(
      monitor_future.GetRepeatingCallback());

  fake_shell_delegate_ptr_->set_default_state(shell_integration::NOT_DEFAULT);
  ChangeDefaultBrowserProgId(L"VanadiumHTML");

  EXPECT_EQ(monitor_future.Take(), shell_integration::NOT_DEFAULT);
  content::RunAllTasksUntilIdle();

  auto notification = display_service_tester_->GetNotification(
      DefaultBrowserManager::kNotificationId);
  EXPECT_TRUE(notification.has_value());
}

#endif  // BUILDFLAG(IS_WIN)

using DefaultBrowserManagerBrowserTest = InProcessBrowserTest;

IN_PROC_BROWSER_TEST_F(DefaultBrowserManagerBrowserTest, OnAcceptedShowsToast) {
  DefaultBrowserController controller(
      std::make_unique<FakeDefaultBrowserSetter>(),
      DefaultBrowserEntrypointType::kSettingsPage);

  base::test::TestFuture<DefaultBrowserState> future;
  controller.OnAccepted(future.GetCallback());
  EXPECT_EQ(future.Get(), DefaultBrowserState::IS_DEFAULT);

  ToastController* toast_controller =
      browser()->GetFeatures().toast_controller();
  ASSERT_TRUE(toast_controller);
  EXPECT_TRUE(toast_controller->IsShowingToast());
  EXPECT_EQ(toast_controller->GetCurrentToastId(),
            ToastId::kDefaultBrowserUpdateSuccess);
}

}  // namespace default_browser
