// Copyright 2013 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/themes/theme_service.h"

#include "base/test/scoped_feature_list.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/browser_widget.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/theme_resources.h"
#include "components/prefs/pref_service.h"
#include "components/tab_groups/tab_group_id.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "ui/base/buildflags.h"
#include "ui/color/color_provider.h"
#include "ui/native_theme/mock_os_settings_provider.h"

#if BUILDFLAG(IS_LINUX)
#include "ui/linux/linux_ui.h"
#include "ui/linux/linux_ui_getter.h"
#endif

namespace {

// The ntp link color specified in the theme.
constexpr SkColor kThemeNtpLinkColor = SkColorSetRGB(36, 70, 0);

bool UsingCustomTheme(const ThemeService& theme_service) {
  return !theme_service.UsingSystemTheme() &&
         !theme_service.UsingDefaultTheme();
}

const ui::ColorProvider* GetColorProviderFor(Browser* browser) {
  return BrowserWindow::FromBrowser(browser)->GetColorProvider();
}

class ThemeServiceBrowserTest : public extensions::ExtensionBrowserTest {
 public:
  void SetUp() override {
    extensions::ComponentLoader::EnableBackgroundExtensionsForTesting();
    extensions::ExtensionBrowserTest::SetUp();
  }

 private:
  ui::MockOsSettingsProvider os_settings_provider_;  // Forces light mode.
};

// Test that the theme is recreated from the extension when the data pack is
// unavailable or invalid (such as when the theme pack version is incremented).
// The PRE_ part of the test installs the theme and changes where Chrome looks
// for the theme data pack to make sure that Chrome does not find it.
IN_PROC_BROWSER_TEST_F(ThemeServiceBrowserTest, PRE_ThemeDataPackInvalid) {
  Profile* profile = browser()->GetProfile();
  ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile);

  // Test initial state.
  EXPECT_FALSE(UsingCustomTheme(*theme_service));
  EXPECT_NE(kThemeNtpLinkColor,
            GetColorProviderFor(browser())->GetColor(kColorNewTabPageLink));
  EXPECT_EQ(base::FilePath(),
            profile->GetPrefs()->GetFilePath(prefs::kCurrentThemePackFilename));

  test::ThemeServiceChangedWaiter waiter(theme_service);
  InstallExtension(test_data_dir_.AppendASCII("theme"), 1);
  waiter.WaitForThemeChanged();

  // Check that the theme was installed.
  EXPECT_TRUE(UsingCustomTheme(*theme_service));
  EXPECT_EQ(kThemeNtpLinkColor,
            GetColorProviderFor(browser())->GetColor(kColorNewTabPageLink));
  EXPECT_NE(base::FilePath(),
            profile->GetPrefs()->GetFilePath(prefs::kCurrentThemePackFilename));

  // Change the theme data pack path to an invalid location such that second
  // part of the test is forced to recreate the theme pack when the theme
  // service is initialized.
  profile->GetPrefs()->SetFilePath(prefs::kCurrentThemePackFilename,
                                   base::FilePath());
}

IN_PROC_BROWSER_TEST_F(ThemeServiceBrowserTest, ThemeDataPackInvalid) {
  ThemeService* theme_service =
      ThemeServiceFactory::GetForProfile(browser()->GetProfile());
  EXPECT_TRUE(UsingCustomTheme(*theme_service));
  EXPECT_EQ(kThemeNtpLinkColor,
            GetColorProviderFor(browser())->GetColor(kColorNewTabPageLink));
}

IN_PROC_BROWSER_TEST_F(ThemeServiceBrowserTest, IncognitoTest) {
  // Should get a different ColorProvider for incognito and original windows.
  Browser* incognito_browser = CreateIncognitoBrowser();
  const auto* color_provider = GetColorProviderFor(browser());
  const auto* otr_color_provider = GetColorProviderFor(incognito_browser);

  EXPECT_NE(color_provider, otr_color_provider);
  // And (some) colors should be different.
  EXPECT_NE(color_provider->GetColor(kColorToolbar),
            otr_color_provider->GetColor(kColorToolbar));
}

IN_PROC_BROWSER_TEST_F(ThemeServiceBrowserTest,
                       IncognitoCustomColor_WithAutoGeneratedTheme) {
  Browser* incognito_browser = CreateIncognitoBrowser();

  const SkColor original_color =
      GetColorProviderFor(browser())->GetColor(kColorToolbar);
  const SkColor original_otr_color =
      GetColorProviderFor(incognito_browser)->GetColor(kColorToolbar);

  ThemeServiceFactory::GetForProfile(profile())
      ->BuildAutogeneratedThemeFromColor(SkColorSetRGB(100, 100, 100));

  // The custom theme should affect the normal window, but not the incognito
  // window.
  EXPECT_NE(original_color,
            GetColorProviderFor(browser())->GetColor(kColorToolbar));
  EXPECT_EQ(original_otr_color,
            GetColorProviderFor(incognito_browser)->GetColor(kColorToolbar));
}

IN_PROC_BROWSER_TEST_F(ThemeServiceBrowserTest,
                       IncognitoCustomColor_WithExtensionOverride) {
  Browser* incognito_browser = CreateIncognitoBrowser();

  const SkColor original_color =
      GetColorProviderFor(browser())->GetColor(kColorToolbar);
  const SkColor original_otr_color =
      GetColorProviderFor(incognito_browser)->GetColor(kColorToolbar);

  ThemeService* theme_service =
      ThemeServiceFactory::GetForProfile(browser()->GetProfile());
  test::ThemeServiceChangedWaiter waiter(theme_service);
  InstallExtension(test_data_dir_.AppendASCII("theme_minimal/"), 1);
  waiter.WaitForThemeChanged();
  EXPECT_TRUE(theme_service->UsingExtensionTheme());

  // The custom theme should affect the normal window, but not the incognito
  // window.
  EXPECT_NE(original_color,
            GetColorProviderFor(browser())->GetColor(kColorToolbar));
  EXPECT_EQ(original_otr_color,
            GetColorProviderFor(incognito_browser)->GetColor(kColorToolbar));
}

IN_PROC_BROWSER_TEST_F(ThemeServiceBrowserTest, GetColorForToolbarButton) {
#if BUILDFLAG(IS_LINUX)
  // This test relies on toolbar buttons having no tint, which is not currently
  // true when using the system theme.
  ui::LinuxUiGetter::set_instance(nullptr);
  ui::NativeTheme::GetInstanceForNativeUi()->NotifyOnNativeThemeUpdated();
#endif

  SkColor default_toolbar_button_color =
      GetColorProviderFor(browser())->GetColor(kColorToolbarButtonIcon);

  ThemeService* theme_service =
      ThemeServiceFactory::GetForProfile(browser()->GetProfile());
  {
    test::ThemeServiceChangedWaiter waiter(theme_service);
    InstallExtension(
        test_data_dir_.AppendASCII("theme_test_toolbar_button_color/"), 1);
    waiter.WaitForThemeChanged();
  }

  // Should get a new color after installing a theme.
  SkColor toolbar_button_explicit_color =
      GetColorProviderFor(browser())->GetColor(kColorToolbarButtonIcon);
  EXPECT_NE(toolbar_button_explicit_color, default_toolbar_button_color);

  {
    test::ThemeServiceChangedWaiter waiter(theme_service);
    // ThemeService::OnThemeBuiltFromExtension will disable the previous theme
    // but does so with tasks posted to the thread pool which will execute
    // non-deterministically; thus, don't check the `expected_change` value.
    InstallExtension(
        test_data_dir_.AppendASCII("theme_test_toolbar_button_tint/"),
        std::nullopt);
    waiter.WaitForThemeChanged();
  }

  // Should get the color based on a tint.
  SkColor toolbar_button_tinted_color =
      GetColorProviderFor(browser())->GetColor(kColorToolbarButtonIcon);
  EXPECT_NE(toolbar_button_tinted_color, default_toolbar_button_color);
  EXPECT_NE(toolbar_button_tinted_color, toolbar_button_explicit_color);
}

// Test methods that involve resetting and updating multiple theme prefs. Ensure
// the final state is represented after only a single theme change event.
IN_PROC_BROWSER_TEST_F(ThemeServiceBrowserTest,
                       ThemeTransitionsEmitSingleNotification) {
  ThemeService* theme_service =
      ThemeServiceFactory::GetForProfile(browser()->GetProfile());

  // User color.
  {
    EXPECT_NE(SK_ColorGREEN, theme_service->GetUserColor());
    test::ThemeServiceChangedWaiter waiter(theme_service);
    theme_service->SetUserColor(SK_ColorGREEN);
    waiter.WaitForThemeChanged();
    EXPECT_EQ(SK_ColorGREEN, theme_service->GetUserColor());
  }

  // User color + color variant.
  {
    EXPECT_NE(SK_ColorRED, theme_service->GetUserColor());
    EXPECT_NE(ui::mojom::BrowserColorVariant::kTonalSpot,
              theme_service->GetBrowserColorVariant());
    test::ThemeServiceChangedWaiter waiter(theme_service);
    theme_service->SetUserColorAndBrowserColorVariant(
        SK_ColorRED, ui::mojom::BrowserColorVariant::kTonalSpot);
    waiter.WaitForThemeChanged();
    EXPECT_EQ(SK_ColorRED, theme_service->GetUserColor());
    EXPECT_EQ(ui::mojom::BrowserColorVariant::kTonalSpot,
              theme_service->GetBrowserColorVariant());
  }

  // Grayscale.
  {
    EXPECT_FALSE(theme_service->GetIsGrayscale());
    test::ThemeServiceChangedWaiter waiter(theme_service);
    theme_service->SetIsGrayscale(true);
    waiter.WaitForThemeChanged();
    EXPECT_TRUE(theme_service->GetIsGrayscale());
  }
}

class FocusModeThemeServiceBrowserTest : public ThemeServiceBrowserTest {
 public:
  FocusModeThemeServiceBrowserTest() {
    scoped_feature_list_.InitAndEnableFeature(features::kTabGroupsFocusing);
  }

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

IN_PROC_BROWSER_TEST_F(FocusModeThemeServiceBrowserTest,
                       FocusModePerWindowIsolation) {
  // Install a custom theme with a distinct background image and color in the
  // test Profile.
  ThemeService* theme_service =
      ThemeServiceFactory::GetForProfile(browser()->GetProfile());
  test::ThemeServiceChangedWaiter waiter(theme_service);
  InstallExtension(test_data_dir_.AppendASCII("theme"), 1);
  waiter.WaitForThemeChanged();
  EXPECT_TRUE(theme_service->UsingExtensionTheme());

  // Open Window A.
  Browser* browser_a = browser();
  BrowserView* browser_view_a =
      BrowserView::GetBrowserViewForBrowser(browser_a);
  ASSERT_TRUE(browser_view_a);

  // Open Window B (same profile).
  Browser* browser_b = CreateBrowser(browser()->GetProfile());
  BrowserView* browser_view_b =
      BrowserView::GetBrowserViewForBrowser(browser_b);
  ASSERT_TRUE(browser_view_b);

  // Verify both Window A and Window B render with the custom theme.
  EXPECT_NE(nullptr, browser_view_a->GetWidget()
                         ->GetColorProviderKeyForTesting()
                         .custom_theme);
  EXPECT_TRUE(
      browser_view_a->GetThemeProvider()->HasCustomImage(IDR_THEME_FRAME));
  EXPECT_NE(nullptr, browser_view_b->GetWidget()
                         ->GetColorProviderKeyForTesting()
                         .custom_theme);
  EXPECT_TRUE(
      browser_view_b->GetThemeProvider()->HasCustomImage(IDR_THEME_FRAME));

  // In Window A, create a Tab Group and trigger Focus Mode (which sets the
  // user_color_override).
  tab_groups::TabGroupId group_a =
      browser_a->tab_strip_model()->AddToNewGroup({0});
  browser_a->tab_strip_model()->SetFocusedGroup(group_a);

  // Verify Window A does not have the custom theme.
  EXPECT_EQ(nullptr, browser_view_a->GetWidget()
                         ->GetColorProviderKeyForTesting()
                         .custom_theme);
  EXPECT_FALSE(
      browser_view_a->GetThemeProvider()->HasCustomImage(IDR_THEME_FRAME));

  // Verify Window B still has the custom theme.
  EXPECT_NE(nullptr, browser_view_b->GetWidget()
                         ->GetColorProviderKeyForTesting()
                         .custom_theme);
  EXPECT_TRUE(
      browser_view_b->GetThemeProvider()->HasCustomImage(IDR_THEME_FRAME));

  // Unfocus Window A and affirm that its custom theme is restored.
  browser_a->tab_strip_model()->SetFocusedGroup(std::nullopt);

  EXPECT_NE(nullptr, browser_view_a->GetWidget()
                         ->GetColorProviderKeyForTesting()
                         .custom_theme);
  EXPECT_TRUE(
      browser_view_a->GetThemeProvider()->HasCustomImage(IDR_THEME_FRAME));
  EXPECT_NE(nullptr, browser_view_b->GetWidget()
                         ->GetColorProviderKeyForTesting()
                         .custom_theme);
  EXPECT_TRUE(
      browser_view_b->GetThemeProvider()->HasCustomImage(IDR_THEME_FRAME));
}

}  // namespace
