// Copyright 2023 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/updater/win/ui/progress_wnd.h"

#include <memory>
#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_reg_util_win.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "base/win/registry.h"
#include "base/win/scoped_hdc.h"
#include "chrome/updater/test/test_scope.h"
#include "chrome/updater/test/unit_test_util.h"
#include "chrome/updater/test/unit_test_util_win.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/test/test_executables.h"
#include "chrome/updater/win/test/test_strings.h"
#include "chrome/updater/win/ui/l10n_util.h"
#include "chrome/updater/win/ui/message_loop.h"
#include "chrome/updater/win/ui/resources/updater_installer_strings.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace updater::ui {
namespace {

// Maximum length for strings read from UI controls.
constexpr size_t kMaxStringLen = 256;

class MockProgressWndEvents : public ui::ProgressWndEvents {
 public:
  // Overrides for OmahaWndEvents.
  MOCK_METHOD(void, DoClose, (), (override));
  MOCK_METHOD(void, DoExit, (), (override));

  // Overrides for CompleteWndEvents.
  MOCK_METHOD(bool, DoLaunchBrowser, (const std::string& url), (override));

  // Overrides for ui::ProgressWndEvents.
  MOCK_METHOD(bool,
              DoRestartBrowser,
              (bool restart_all_browsers, const std::vector<GURL>& urls),
              (override));
  MOCK_METHOD(bool, DoReboot, (), (override));
  MOCK_METHOD(void, DoCancel, (), (override));
};

}  // namespace

class ProgressWndTest : public ui::ProgressWndEvents, public ::testing::Test {
 public:
  // Overrides for OmahaWndEvents.
  void DoClose() override { mock_progress_wnd_events_->DoClose(); }
  void DoExit() override { mock_progress_wnd_events_->DoExit(); }

  // Overrides for CompleteWndEvents.
  bool DoLaunchBrowser(const std::string& url) override {
    return mock_progress_wnd_events_->DoLaunchBrowser(url);
  }

  // Overrides for ProgressWndEvents.
  bool DoRestartBrowser(bool restart_all_browsers,
                        const std::vector<GURL>& urls) override {
    return mock_progress_wnd_events_->DoRestartBrowser(restart_all_browsers,
                                                       urls);
  }
  bool DoReboot() override { return mock_progress_wnd_events_->DoReboot(); }
  void DoCancel() override { mock_progress_wnd_events_->DoCancel(); }

  std::unique_ptr<ProgressWnd> MakeProgressWindow(MessageLoop* message_loop) {
    auto progress_wnd =
        std::make_unique<ui::ProgressWnd>(message_loop, nullptr);
    progress_wnd->SetEventSink(this);
    progress_wnd->Initialize();
    progress_wnd->Show();
    return progress_wnd;
  }

 protected:
  std::unique_ptr<MockProgressWndEvents> mock_progress_wnd_events_ =
      std::make_unique<MockProgressWndEvents>();
};

TEST_F(ProgressWndTest, ClickedButton) {
  // Calls ProgressWnd::OnComplete then simulates a button push on the dialog.
  auto button_tester = [&](CompletionCodes code, int button_to_push) {
    AppCompletionInfo app_completion_info;
    app_completion_info.post_install_url = GURL("http://some-test-url");
    app_completion_info.completion_code = code;
    ObserverCompletionInfo observer_completion_info;
    observer_completion_info.completion_text = u"some text";
    observer_completion_info.apps_info.push_back(app_completion_info);
    MessageLoop ui_message_loop;
    std::unique_ptr<ProgressWnd> progress_wnd =
        MakeProgressWindow(&ui_message_loop);
    progress_wnd->OnComplete(observer_completion_info);
    const HWND button = ::GetDlgItem(progress_wnd->hwnd(), button_to_push);
    ::SendMessageW(progress_wnd->hwnd(), WM_COMMAND,
                   MAKEWPARAM(button_to_push, BN_CLICKED),
                   reinterpret_cast<LPARAM>(button));
  };
  {
    mock_progress_wnd_events_ = std::make_unique<MockProgressWndEvents>();
    ::testing::InSequence seq;
    EXPECT_CALL(*mock_progress_wnd_events_,
                DoRestartBrowser(
                    false, std::vector<GURL>{GURL("http://some-test-url")}))

        .WillOnce(::testing::Return(true));
    EXPECT_CALL(*mock_progress_wnd_events_, DoExit());
    EXPECT_CALL(*mock_progress_wnd_events_, DoClose());
    button_tester(CompletionCodes::COMPLETION_CODE_RESTART_BROWSER,
                  IDC_BUTTON1);
  }
  {
    mock_progress_wnd_events_ = std::make_unique<MockProgressWndEvents>();
    ::testing::InSequence seq;
    EXPECT_CALL(
        *mock_progress_wnd_events_,
        DoRestartBrowser(true, std::vector<GURL>{GURL("http://some-test-url")}))

        .WillOnce(::testing::Return(true));
    EXPECT_CALL(*mock_progress_wnd_events_, DoExit());
    EXPECT_CALL(*mock_progress_wnd_events_, DoClose());
    button_tester(CompletionCodes::COMPLETION_CODE_RESTART_ALL_BROWSERS,
                  IDC_BUTTON1);
  }
  {
    mock_progress_wnd_events_ = std::make_unique<MockProgressWndEvents>();
    ::testing::InSequence seq;
    EXPECT_CALL(*mock_progress_wnd_events_, DoReboot())

        .WillOnce(::testing::Return(true));
    EXPECT_CALL(*mock_progress_wnd_events_, DoExit());
    EXPECT_CALL(*mock_progress_wnd_events_, DoClose());
    button_tester(CompletionCodes::COMPLETION_CODE_REBOOT, IDC_BUTTON1);
  }

  for (auto completion_code :
       {CompletionCodes::COMPLETION_CODE_RESTART_BROWSER,
        CompletionCodes::COMPLETION_CODE_RESTART_ALL_BROWSERS,
        CompletionCodes::COMPLETION_CODE_REBOOT}) {
    mock_progress_wnd_events_ = std::make_unique<MockProgressWndEvents>();
    ::testing::InSequence seq;
    EXPECT_CALL(*mock_progress_wnd_events_,
                DoRestartBrowser(::testing::_, ::testing::_))
        .Times(0);
    EXPECT_CALL(*mock_progress_wnd_events_, DoReboot()).Times(0);
    EXPECT_CALL(*mock_progress_wnd_events_, DoExit());
    EXPECT_CALL(*mock_progress_wnd_events_, DoClose());
    button_tester(completion_code, IDC_BUTTON2);
  }

  for (auto completion_code : {CompletionCodes::COMPLETION_CODE_SUCCESS,
                               CompletionCodes::COMPLETION_CODE_ERROR}) {
    mock_progress_wnd_events_ = std::make_unique<MockProgressWndEvents>();
    ::testing::InSequence seq;
    EXPECT_CALL(*mock_progress_wnd_events_,
                DoRestartBrowser(::testing::_, ::testing::_))
        .Times(0);
    EXPECT_CALL(*mock_progress_wnd_events_, DoExit());
    EXPECT_CALL(*mock_progress_wnd_events_, DoClose());
    button_tester(completion_code, IDC_CLOSE);
  }
}

TEST_F(ProgressWndTest, OnInstallStopped) {
    mock_progress_wnd_events_ = std::make_unique<MockProgressWndEvents>();
    MessageLoop ui_message_loop;
    std::unique_ptr<ProgressWnd> progress_wnd =
        MakeProgressWindow(&ui_message_loop);
    progress_wnd->OnCheckingForUpdate();
    EXPECT_EQ(progress_wnd->cur_state_,
              ProgressWnd::States::STATE_CHECKING_FOR_UPDATE);
    EXPECT_CALL(*mock_progress_wnd_events_, DoCancel());
    progress_wnd->OnClose(WM_CLOSE, 0, 0);
    EXPECT_TRUE(progress_wnd->is_canceled_);
    progress_wnd->DestroyWindow();
}

TEST_F(ProgressWndTest, MaybeCloseWindow) {
  mock_progress_wnd_events_ = std::make_unique<MockProgressWndEvents>();
  EXPECT_CALL(*mock_progress_wnd_events_, DoCancel()).WillOnce([] {
    ::PostThreadMessage(::GetCurrentThreadId(), WM_QUIT, 0, 0);
  });
  MessageLoop message_loop;
  std::unique_ptr<ProgressWnd> progress_wnd = MakeProgressWindow(&message_loop);
  progress_wnd->MaybeCloseWindow();
  message_loop.Run();
  progress_wnd->DestroyWindow();
}

TEST_F(ProgressWndTest, GetBundleCompletionCode) {
  {
    for (CompletionCodes completion_code :
         {CompletionCodes::COMPLETION_CODE_ERROR,
          CompletionCodes::COMPLETION_CODE_INSTALL_FINISHED_BEFORE_CANCEL}) {
      ObserverCompletionInfo info;
      info.completion_code = completion_code;
      EXPECT_EQ(ProgressWnd::GetBundleCompletionCode(info), completion_code);
    }
  }
  {
    ObserverCompletionInfo info;
    EXPECT_EQ(ProgressWnd::GetBundleCompletionCode(info),
              CompletionCodes::COMPLETION_CODE_EXIT_SILENTLY);
  }
  {
    for (CompletionCodes completion_code :
         {CompletionCodes::COMPLETION_CODE_SUCCESS,
          CompletionCodes::COMPLETION_CODE_EXIT_SILENTLY,
          CompletionCodes::COMPLETION_CODE_RESTART_ALL_BROWSERS,
          CompletionCodes::COMPLETION_CODE_REBOOT,
          CompletionCodes::COMPLETION_CODE_RESTART_BROWSER,
          CompletionCodes::COMPLETION_CODE_RESTART_ALL_BROWSERS_NOTICE_ONLY,
          CompletionCodes::COMPLETION_CODE_REBOOT_NOTICE_ONLY,
          CompletionCodes::COMPLETION_CODE_RESTART_BROWSER_NOTICE_ONLY,
          CompletionCodes::COMPLETION_CODE_LAUNCH_COMMAND,
          CompletionCodes::COMPLETION_CODE_INSTALL_FINISHED_BEFORE_CANCEL}) {
      ObserverCompletionInfo info;
      AppCompletionInfo app_info;
      app_info.completion_code = completion_code;
      info.apps_info.push_back(app_info);
      EXPECT_EQ(ProgressWnd::GetBundleCompletionCode(info), completion_code);
    }
  }
  {
    ObserverCompletionInfo info;

    for (CompletionCodes code : {CompletionCodes::COMPLETION_CODE_SUCCESS,
                                 CompletionCodes::COMPLETION_CODE_EXIT_SILENTLY,
                                 CompletionCodes::COMPLETION_CODE_REBOOT}) {
      AppCompletionInfo app_info;
      app_info.completion_code = code;
      info.apps_info.push_back(app_info);
    }
    EXPECT_EQ(ProgressWnd::GetBundleCompletionCode(info),
              CompletionCodes::COMPLETION_CODE_REBOOT);
  }
}

TEST_F(ProgressWndTest, DeterminePostInstallUrls) {
  for (CompletionCodes code :
       {CompletionCodes::COMPLETION_CODE_RESTART_ALL_BROWSERS,
        CompletionCodes::COMPLETION_CODE_RESTART_BROWSER}) {
    MessageLoop message_loop;
    std::unique_ptr<ProgressWnd> progress_wnd =
        MakeProgressWindow(&message_loop);
    ObserverCompletionInfo observer_completion_info;
    AppCompletionInfo app_completion_info;
    app_completion_info.completion_code = code;
    app_completion_info.post_install_url = GURL("http://some-test-url");
    observer_completion_info.apps_info.push_back(app_completion_info);
    progress_wnd->DeterminePostInstallUrls(observer_completion_info);
    EXPECT_EQ(progress_wnd->post_install_urls_,
              std::vector<GURL>{GURL("http://some-test-url")});
    progress_wnd->DestroyWindow();
  }
}

TEST_F(ProgressWndTest, OnCheckingForUpdate) {
  MessageLoop ui_message_loop;
  std::unique_ptr<ProgressWnd> progress_wnd =
      MakeProgressWindow(&ui_message_loop);
  progress_wnd->OnCheckingForUpdate();
  EXPECT_EQ(progress_wnd->cur_state_,
            ProgressWnd::States::STATE_CHECKING_FOR_UPDATE);
  EXPECT_FALSE(
      ::IsWindowEnabled(::GetDlgItem(progress_wnd->hwnd(), IDC_CLOSE)));
  progress_wnd->DestroyWindow();
}

TEST_F(ProgressWndTest, OnWaitingToDownload) {
  for (const int is_retry : {false, true}) {
    MessageLoop ui_message_loop;
    std::unique_ptr<ProgressWnd> progress_wnd =
        MakeProgressWindow(&ui_message_loop);
    if (is_retry) {
      progress_wnd->OnWaitingRetryDownload(
          "app-id", u"app-name",
          base::Time::NowFromSystemTime() + base::Minutes(5));
    } else {
      progress_wnd->OnWaitingToDownload("app-id", u"app-name");
    }
    EXPECT_EQ(progress_wnd->cur_state_,
              ProgressWnd::States::STATE_WAITING_TO_DOWNLOAD);
    EXPECT_FALSE(
        ::IsWindowEnabled(::GetDlgItem(progress_wnd->hwnd(), IDC_CLOSE)));
    wchar_t state_text[kMaxStringLen] = {};
    ::GetDlgItemTextW(progress_wnd->hwnd(), IDC_INSTALLER_STATE_TEXT,
                      state_text, std::size(state_text));
    EXPECT_STREQ(state_text, L"");
    progress_wnd->DestroyWindow();
  }
}

TEST_F(ProgressWndTest, OnDownloading) {
  struct TestCase {
    const std::optional<base::TimeDelta> time_remaining;
    const bool is_canceled;
    const unsigned int expected_string_id;
  } cases[] = {
      {base::Seconds(20), false, IDS_DOWNLOADING_BASE},
      {base::Minutes(5), false, IDS_DOWNLOADING_BASE},
      {base::Hours(2), false, IDS_DOWNLOADING_BASE},
      {std::nullopt, false, IDS_DOWNLOADING_BASE},
      {base::Seconds(0), false, IDS_DOWNLOADING_COMPLETED_BASE},
      {base::Seconds(20), true, IDS_CANCELING_BASE},
  };

  MessageLoop ui_message_loop;
  std::unique_ptr<ProgressWnd> progress_wnd =
      MakeProgressWindow(&ui_message_loop);

  for (const auto& test_case : cases) {
    progress_wnd->is_canceled_ = test_case.is_canceled;
    progress_wnd->OnDownloading("app-id", u"app-name", test_case.time_remaining,
                                50);
    EXPECT_EQ(progress_wnd->cur_state_, ProgressWnd::States::STATE_DOWNLOADING);
    EXPECT_FALSE(
        ::IsWindowEnabled(::GetDlgItem(progress_wnd->hwnd(), IDC_CLOSE)));
    wchar_t state_text[kMaxStringLen] = {};
    ::GetDlgItemTextW(progress_wnd->hwnd(), IDC_INSTALLER_STATE_TEXT,
                      state_text, std::size(state_text));
    EXPECT_STREQ(state_text,
                 GetLocalizedString(test_case.expected_string_id).c_str());
  }

  progress_wnd->DestroyWindow();
}

TEST_F(ProgressWndTest, OnPause) {
  MessageLoop ui_message_loop;
  std::unique_ptr<ProgressWnd> progress_wnd =
      MakeProgressWindow(&ui_message_loop);
  progress_wnd->OnPause();
  EXPECT_EQ(progress_wnd->cur_state_, ProgressWnd::States::STATE_PAUSED);
  progress_wnd->DestroyWindow();
}

TEST_F(ProgressWndTest, OnComplete) {
  using ::testing::AnyNumber;
  EXPECT_CALL(*mock_progress_wnd_events_, DoExit()).Times(AnyNumber());
  EXPECT_CALL(*mock_progress_wnd_events_, DoClose()).Times(AnyNumber());

  MessageLoop ui_message_loop;
  {
    std::unique_ptr<ProgressWnd> progress_wnd =
        MakeProgressWindow(&ui_message_loop);
    ObserverCompletionInfo observer_completion_info;
    progress_wnd->OnComplete(observer_completion_info);
    EXPECT_EQ(progress_wnd->cur_state_,
              ProgressWnd::States::STATE_COMPLETE_SUCCESS);
  }
  {
    std::unique_ptr<ProgressWnd> progress_wnd =
        MakeProgressWindow(&ui_message_loop);
    AppCompletionInfo app_completion_info;
    app_completion_info.completion_code =
        CompletionCodes::COMPLETION_CODE_SUCCESS;
    ObserverCompletionInfo observer_completion_info;
    observer_completion_info.completion_text = u"text";
    observer_completion_info.apps_info.push_back(app_completion_info);
    progress_wnd->OnComplete(observer_completion_info);
    wchar_t completion_text[kMaxStringLen] = {};
    ::GetDlgItemTextW(progress_wnd->hwnd(), IDC_COMPLETE_TEXT, completion_text,
                      std::size(completion_text));
    EXPECT_STREQ(completion_text, L"text");
    EXPECT_TRUE(
        ::IsWindowEnabled(::GetDlgItem(progress_wnd->hwnd(), IDC_CLOSE)));
    progress_wnd->DestroyWindow();
  }
}

TEST_F(ProgressWndTest, LaunchCmdLine) {
  using ::testing::AnyNumber;
  EXPECT_CALL(*mock_progress_wnd_events_, DoExit()).Times(AnyNumber());
  EXPECT_CALL(*mock_progress_wnd_events_, DoClose()).Times(AnyNumber());

  // Create a shared event to be waited for in this process and signaled in the
  // test process. If the test is running elevated with UAC on, the test will
  // also confirm that the test process is launched at medium integrity, by
  // creating an event with a security descriptor that allows the medium
  // integrity process to signal it.
  test::EventHolder event_holder(
      IsElevatedWithUACOn() ? test::CreateEveryoneWaitableEventForTest()
                            : test::CreateWaitableEventForTest());
  ASSERT_NE(event_holder.event.handle(), nullptr);

  base::CommandLine test_process_cmd_line = GetTestProcessCommandLine(
      GetUpdaterScopeForTesting(), test::GetTestName());
  test_process_cmd_line.AppendSwitchNative(
      IsElevatedWithUACOn() ? kTestEventToSignalIfMediumIntegrity
                            : kTestEventToSignal,
      event_holder.name);
  MessageLoop ui_message_loop;
  std::unique_ptr<ProgressWnd> progress_wnd =
      MakeProgressWindow(&ui_message_loop);
  AppCompletionInfo app_completion_info;
  app_completion_info.completion_code =
      CompletionCodes::COMPLETION_CODE_EXIT_SILENTLY_ON_LAUNCH_COMMAND;
  app_completion_info.post_install_launch_command_line =
      base::WideToUTF8(test_process_cmd_line.GetCommandLineString());
  ObserverCompletionInfo observer_completion_info;
  observer_completion_info.completion_text = u"text";
  observer_completion_info.apps_info.push_back(app_completion_info);
  progress_wnd->OnComplete(observer_completion_info);

  EXPECT_TRUE(event_holder.event.TimedWait(TestTimeouts::action_max_timeout()));
  EXPECT_TRUE(test::WaitFor(
      [] { return test::FindProcesses(kTestProcessExecutableName).empty(); }));
}

TEST_F(ProgressWndTest, FlatButtonSubclass) {
  MessageLoop ui_message_loop;
  std::unique_ptr<ProgressWnd> progress_wnd =
      MakeProgressWindow(&ui_message_loop);

  EXPECT_EQ(progress_wnd->btn1_.hwnd(),
            ::GetDlgItem(progress_wnd->hwnd(), IDC_BUTTON1));
  EXPECT_TRUE(progress_wnd->btn1_.IsWindow());

  EXPECT_EQ(progress_wnd->btn2_.hwnd(),
            ::GetDlgItem(progress_wnd->hwnd(), IDC_BUTTON2));
  EXPECT_TRUE(progress_wnd->btn2_.IsWindow());

  EXPECT_EQ(progress_wnd->close_btn_.hwnd(),
            ::GetDlgItem(progress_wnd->hwnd(), IDC_CLOSE));
  EXPECT_TRUE(progress_wnd->close_btn_.IsWindow());

  EXPECT_EQ(progress_wnd->get_help_btn_.hwnd(),
            ::GetDlgItem(progress_wnd->hwnd(), IDC_GET_HELP));
  EXPECT_TRUE(progress_wnd->get_help_btn_.IsWindow());

  progress_wnd->DestroyWindow();
}

// Verifies that the app logo control dynamically resizes to match both
// theme-specific square logos (48x48) and legacy rectangular logos (92x24),
// scaling correctly under the window's effective DPI while keeping the bottom
// edge aligned with the layout baseline.
TEST_F(ProgressWndTest, SetAppLogoDynamicSizing) {
  MessageLoop ui_message_loop;
  std::unique_ptr<ProgressWnd> progress_wnd =
      MakeProgressWindow(&ui_message_loop);

  const HWND app_bitmap_ctl =
      ::GetDlgItem(progress_wnd->hwnd(), IDC_APP_BITMAP);
  base::win::ScopedGetDC dc(nullptr);

  // Test with a 48x48 square logo.
  base::win::ScopedGDIObject<HBITMAP> square_bitmap(
      ::CreateCompatibleBitmap(dc, 48, 48));
  EXPECT_TRUE(square_bitmap.is_valid());

  ::SendMessage(progress_wnd->hwnd(), WM_SET_APP_LOGO,
                reinterpret_cast<WPARAM>(square_bitmap.release()), 0);

  RECT ctl_rect = progress_wnd->GetControlClientRect(app_bitmap_ctl);
  const int initial_bottom = ctl_rect.bottom;
  const int dpi = ::GetDpiForWindow(progress_wnd->hwnd());
  const int effective_dpi = dpi ? dpi : USER_DEFAULT_SCREEN_DPI;
  EXPECT_EQ(ctl_rect.right - ctl_rect.left,
            ::MulDiv(48, effective_dpi, USER_DEFAULT_SCREEN_DPI));
  EXPECT_EQ(ctl_rect.bottom - ctl_rect.top,
            ::MulDiv(48, effective_dpi, USER_DEFAULT_SCREEN_DPI));

  // Test with a 92x24 rectangular logo.
  base::win::ScopedGDIObject<HBITMAP> rect_bitmap(
      ::CreateCompatibleBitmap(dc, 92, 24));
  EXPECT_TRUE(rect_bitmap.is_valid());

  ::SendMessage(progress_wnd->hwnd(), WM_SET_APP_LOGO,
                reinterpret_cast<WPARAM>(rect_bitmap.release()), 0);

  // Verify that the rectangular logo matches expected scaled dimensions and
  // the bottom coordinate remains locked to the original baseline.
  ctl_rect = progress_wnd->GetControlClientRect(app_bitmap_ctl);
  EXPECT_EQ(ctl_rect.right - ctl_rect.left,
            ::MulDiv(92, effective_dpi, USER_DEFAULT_SCREEN_DPI));
  EXPECT_EQ(ctl_rect.bottom - ctl_rect.top,
            ::MulDiv(24, effective_dpi, USER_DEFAULT_SCREEN_DPI));
  EXPECT_EQ(ctl_rect.bottom, initial_bottom);

  progress_wnd->DestroyWindow();
}

// Verifies that caching both light and dark logos allows switching the
// displayed logo when a theme change (WM_SETTINGCHANGE or WM_SYSCOLORCHANGE)
// occurs without requiring redownloading or resetting the cache.
TEST_F(ProgressWndTest, SetAppLogoThemeSwitching) {
  registry_util::RegistryOverrideManager registry_override;
  ASSERT_NO_FATAL_FAILURE(
      registry_override.OverrideRegistry(HKEY_CURRENT_USER));

  auto set_dark_mode = [](bool dark) {
    base::win::RegKey key;
    EXPECT_EQ(key.Create(HKEY_CURRENT_USER,
                         L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes"
                         L"\\Personalize",
                         KEY_SET_VALUE),
              ERROR_SUCCESS);
    EXPECT_EQ(
        key.WriteValue(L"AppsUseLightTheme", static_cast<DWORD>(dark ? 0 : 1)),
        ERROR_SUCCESS);
  };

  // Start with light mode.
  set_dark_mode(false);

  MessageLoop ui_message_loop;
  std::unique_ptr<ProgressWnd> progress_wnd =
      MakeProgressWindow(&ui_message_loop);

  const HWND app_bitmap_ctl =
      ::GetDlgItem(progress_wnd->hwnd(), IDC_APP_BITMAP);
  base::win::ScopedGetDC dc(nullptr);

  // Light logo: 32x32, Dark logo: 48x48.
  base::win::ScopedGDIObject<HBITMAP> light_bitmap(
      ::CreateCompatibleBitmap(dc, 32, 32));
  base::win::ScopedGDIObject<HBITMAP> dark_bitmap(
      ::CreateCompatibleBitmap(dc, 48, 48));
  EXPECT_TRUE(light_bitmap.is_valid());
  EXPECT_TRUE(dark_bitmap.is_valid());

  const HBITMAP light_hbitmap = light_bitmap.get();
  const HBITMAP dark_hbitmap = dark_bitmap.get();

  ::SendMessage(progress_wnd->hwnd(), WM_SET_APP_LOGO,
                reinterpret_cast<WPARAM>(light_bitmap.release()),
                reinterpret_cast<LPARAM>(dark_bitmap.release()));

  EXPECT_EQ(progress_wnd->light_app_logo_bmp_.get(), light_hbitmap);
  EXPECT_EQ(progress_wnd->dark_app_logo_bmp_.get(), dark_hbitmap);
  EXPECT_EQ(progress_wnd->GetCurrentAppLogoBitmap(), light_hbitmap);

  const int dpi = ::GetDpiForWindow(progress_wnd->hwnd());
  const int effective_dpi = dpi ? dpi : USER_DEFAULT_SCREEN_DPI;

  RECT ctl_rect = progress_wnd->GetControlClientRect(app_bitmap_ctl);
  EXPECT_EQ(ctl_rect.right - ctl_rect.left,
            ::MulDiv(32, effective_dpi, USER_DEFAULT_SCREEN_DPI));
  EXPECT_EQ(ctl_rect.bottom - ctl_rect.top,
            ::MulDiv(32, effective_dpi, USER_DEFAULT_SCREEN_DPI));

  // Switch to dark mode and notify the window via WM_SETTINGCHANGE.
  set_dark_mode(true);
  ::SendMessage(progress_wnd->hwnd(), WM_SETTINGCHANGE, 0,
                reinterpret_cast<LPARAM>(L"ImmersiveColorSet"));

  EXPECT_EQ(progress_wnd->GetCurrentAppLogoBitmap(), dark_hbitmap);
  ctl_rect = progress_wnd->GetControlClientRect(app_bitmap_ctl);
  EXPECT_EQ(ctl_rect.right - ctl_rect.left,
            ::MulDiv(48, effective_dpi, USER_DEFAULT_SCREEN_DPI));
  EXPECT_EQ(ctl_rect.bottom - ctl_rect.top,
            ::MulDiv(48, effective_dpi, USER_DEFAULT_SCREEN_DPI));

  // Switch back to light mode and notify via WM_SYSCOLORCHANGE.
  set_dark_mode(false);
  ::SendMessage(progress_wnd->hwnd(), WM_SYSCOLORCHANGE, 0, 0);

  EXPECT_EQ(progress_wnd->GetCurrentAppLogoBitmap(), light_hbitmap);
  ctl_rect = progress_wnd->GetControlClientRect(app_bitmap_ctl);
  EXPECT_EQ(ctl_rect.right - ctl_rect.left,
            ::MulDiv(32, effective_dpi, USER_DEFAULT_SCREEN_DPI));
  EXPECT_EQ(ctl_rect.bottom - ctl_rect.top,
            ::MulDiv(32, effective_dpi, USER_DEFAULT_SCREEN_DPI));

  // Test with only a single fallback logo (light provided, dark is null).
  base::win::ScopedGDIObject<HBITMAP> fallback_bitmap(
      ::CreateCompatibleBitmap(dc, 64, 64));
  EXPECT_TRUE(fallback_bitmap.is_valid());
  const HBITMAP fallback_hbitmap = fallback_bitmap.get();

  ::SendMessage(progress_wnd->hwnd(), WM_SET_APP_LOGO,
                reinterpret_cast<WPARAM>(fallback_bitmap.release()), 0);

  EXPECT_EQ(progress_wnd->light_app_logo_bmp_.get(), fallback_hbitmap);
  EXPECT_EQ(progress_wnd->dark_app_logo_bmp_.get(), nullptr);

  // In light mode, uses fallback logo.
  EXPECT_EQ(progress_wnd->GetCurrentAppLogoBitmap(), fallback_hbitmap);
  ctl_rect = progress_wnd->GetControlClientRect(app_bitmap_ctl);
  EXPECT_EQ(ctl_rect.right - ctl_rect.left,
            ::MulDiv(64, effective_dpi, USER_DEFAULT_SCREEN_DPI));
  EXPECT_EQ(ctl_rect.bottom - ctl_rect.top,
            ::MulDiv(64, effective_dpi, USER_DEFAULT_SCREEN_DPI));

  // In dark mode with no dark logo provided, falls back to light logo.
  set_dark_mode(true);
  ::SendMessage(progress_wnd->hwnd(), WM_SETTINGCHANGE, 0,
                reinterpret_cast<LPARAM>(L"ImmersiveColorSet"));
  EXPECT_EQ(progress_wnd->GetCurrentAppLogoBitmap(), fallback_hbitmap);
  ctl_rect = progress_wnd->GetControlClientRect(app_bitmap_ctl);
  EXPECT_EQ(ctl_rect.right - ctl_rect.left,
            ::MulDiv(64, effective_dpi, USER_DEFAULT_SCREEN_DPI));
  EXPECT_EQ(ctl_rect.bottom - ctl_rect.top,
            ::MulDiv(64, effective_dpi, USER_DEFAULT_SCREEN_DPI));

  // Switch back to light mode and verify fallback logo persists.
  set_dark_mode(false);
  ::SendMessage(progress_wnd->hwnd(), WM_SYSCOLORCHANGE, 0, 0);
  EXPECT_EQ(progress_wnd->GetCurrentAppLogoBitmap(), fallback_hbitmap);
  ctl_rect = progress_wnd->GetControlClientRect(app_bitmap_ctl);
  EXPECT_EQ(ctl_rect.right - ctl_rect.left,
            ::MulDiv(64, effective_dpi, USER_DEFAULT_SCREEN_DPI));
  EXPECT_EQ(ctl_rect.bottom - ctl_rect.top,
            ::MulDiv(64, effective_dpi, USER_DEFAULT_SCREEN_DPI));

  progress_wnd->DestroyWindow();
}

}  // namespace updater::ui
