// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/test/integration/committed_all_nudged_changes_checker.h"
#include "chrome/browser/sync/test/integration/fake_server_match_status_checker.h"
#include "chrome/browser/sync/test/integration/preferences_helper.h"
#include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/sync/test/integration/themes_helper.h"
#include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
#include "chrome/browser/themes/theme_local_data_batch_uploader.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/themes/theme_service_utils.h"
#include "chrome/browser/themes/theme_syncable_service.h"
#include "chrome/common/pref_names.h"
#include "components/browser_sync/browser_sync_switches.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/engine/loopback_server/persistent_unique_client_entity.h"
#include "components/sync/protocol/theme_specifics.pb.h"
#include "components/sync/test/fake_server.h"
#include "components/sync/test/test_matchers.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace {

using syncer::MatchesLocalDataDescription;
using syncer::MatchesLocalDataItemModel;
using testing::IsEmpty;
using themes_helper::GetCustomTheme;
using themes_helper::IsSystemThemeDistinctFromDefaultTheme;
using themes_helper::UseCustomTheme;
using themes_helper::UseDefaultTheme;
using themes_helper::UseSystemTheme;
using themes_helper::UsingCustomTheme;
using themes_helper::UsingDefaultTheme;
using themes_helper::UsingGrayscaleTheme;
using themes_helper::UsingSystemTheme;

// Note: All of these matchers take a sync_pb::ThemeSpecifics.

MATCHER(HasDefaultTheme, "") {
  return !arg.use_custom_theme() && !arg.use_system_theme_by_default() &&
         !arg.has_custom_theme_id();
}

MATCHER(HasSystemTheme, "") {
  return !arg.use_custom_theme() && arg.use_system_theme_by_default() &&
         !arg.has_custom_theme_id();
}

MATCHER_P(HasCustomThemeWithId, theme_id, "") {
  return arg.use_custom_theme() && arg.custom_theme_id() == theme_id;
}

MATCHER_P(HasBrowserColorScheme, color_scheme, "") {
  return arg.browser_color_scheme() == color_scheme;
}

MATCHER_P(HasAutogeneratedThemeColor, color, "") {
  return arg.has_autogenerated_color_theme() &&
         arg.autogenerated_color_theme().color() == color;
}

MATCHER_P(HasUserColor, color, "") {
  return arg.has_user_color_theme() && arg.user_color_theme().color() == color;
}

MATCHER(HasGrayscaleTheme, "") {
  return arg.has_grayscale_theme_enabled();
}

std::unique_ptr<syncer::LoopbackServerEntity> CreateDefaultThemeEntity() {
  sync_pb::EntitySpecifics specifics;
  // Clients always write `browser_color_scheme` field.
  specifics.mutable_theme()->set_browser_color_scheme(
      sync_pb::ThemeSpecifics_BrowserColorScheme_SYSTEM);
  return syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
      ThemeSyncableService::kSyncEntityClientTag,
      ThemeSyncableService::kSyncEntityClientTag, specifics,
      /*creation_time=*/0, /*last_modified_time=*/0);
}

std::unique_ptr<syncer::LoopbackServerEntity> CreateSystemThemeEntity() {
  sync_pb::EntitySpecifics specifics;
  // Clients always write `browser_color_scheme` field.
  specifics.mutable_theme()->set_browser_color_scheme(
      sync_pb::ThemeSpecifics_BrowserColorScheme_SYSTEM);
  specifics.mutable_theme()->set_use_system_theme_by_default(true);
  return syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
      ThemeSyncableService::kSyncEntityTitle,
      ThemeSyncableService::kSyncEntityClientTag, specifics,
      /*creation_time=*/0, /*last_modified_time=*/0);
}

std::unique_ptr<syncer::LoopbackServerEntity> CreateCustomThemeEntity(
    const std::string& theme_id) {
  sync_pb::EntitySpecifics specifics;
  specifics.mutable_theme()->set_use_custom_theme(true);
  specifics.mutable_theme()->set_custom_theme_id(theme_id);
  specifics.mutable_theme()->set_custom_theme_name("custom theme");
  return syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
      ThemeSyncableService::kSyncEntityTitle,
      ThemeSyncableService::kSyncEntityClientTag, specifics,
      /*creation_time=*/0, /*last_modified_time=*/0);
}

std::unique_ptr<syncer::LoopbackServerEntity> CreateGrayscaleThemeEntity() {
  sync_pb::EntitySpecifics specifics;
  specifics.mutable_theme()->set_use_custom_theme(false);
  specifics.mutable_theme()->mutable_grayscale_theme_enabled();
  return syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
      ThemeSyncableService::kSyncEntityTitle,
      ThemeSyncableService::kSyncEntityClientTag, specifics,
      /*creation_time=*/0, /*last_modified_time=*/0);
}

// A helper class that waits for the (single) THEME entity on the FakeServer to
// match a given matcher.
class ServerThemeMatchChecker
    : public fake_server::FakeServerMatchStatusChecker {
 public:
  using Matcher = testing::Matcher<sync_pb::ThemeSpecifics>;

  explicit ServerThemeMatchChecker(const Matcher& matcher);
  ~ServerThemeMatchChecker() override;
  ServerThemeMatchChecker(const ServerThemeMatchChecker&) = delete;
  ServerThemeMatchChecker& operator=(const ServerThemeMatchChecker&) = delete;

  // FakeServer::Observer overrides.
  void OnCommit(syncer::DataTypeSet committed_data_types) override;

  // StatusChangeChecker overrides.
  bool IsExitConditionSatisfied(std::ostream* os) override;

 private:
  const Matcher matcher_;
};

ServerThemeMatchChecker::ServerThemeMatchChecker(const Matcher& matcher)
    : matcher_(matcher) {}

ServerThemeMatchChecker::~ServerThemeMatchChecker() = default;

void ServerThemeMatchChecker::OnCommit(
    syncer::DataTypeSet committed_data_types) {
  if (committed_data_types.Has(syncer::THEMES)) {
    CheckExitCondition();
  }
}

bool ServerThemeMatchChecker::IsExitConditionSatisfied(std::ostream* os) {
  std::vector<sync_pb::SyncEntity> entities =
      fake_server()->GetSyncEntitiesByDataType(syncer::THEMES);

  if (entities.empty()) {
    return false;
  }
  DCHECK_EQ(entities.size(), 1u);
  DCHECK(entities[0].specifics().has_theme());

  testing::StringMatchResultListener result_listener;
  const bool matches = testing::ExplainMatchResult(
      matcher_, entities[0].specifics().theme(), &result_listener);
  *os << result_listener.str();
  return matches;
}

class SingleClientThemesSyncTest
    : public SyncTest,
      public testing::WithParamInterface<SyncTest::SetupSyncMode> {
 public:
  SingleClientThemesSyncTest() : SyncTest(SINGLE_CLIENT) {
    if (GetSetupSyncMode() == SetupSyncMode::kSyncTransportOnly) {
      scoped_feature_list_.InitWithFeatures(
          /*enabled_features=*/{syncer::kReplaceSyncPromosWithSignInPromos,
                                syncer::kSeparateLocalAndAccountThemes,
                                syncer::kSeparateLocalAndAccountSearchEngines,
                                // `kEnablePreferencesAccountStorage` is used to
                                // enable themes in transport
                                // mode alongside some other data types.
                                switches::kEnablePreferencesAccountStorage},
          /*disabled_features=*/{});
    }
  }
  ~SingleClientThemesSyncTest() override = default;

  bool TestUsesSelfNotifications() override { return false; }

  SyncTest::SetupSyncMode GetSetupSyncMode() const override {
    return GetParam();
  }

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

INSTANTIATE_TEST_SUITE_P(,
                         SingleClientThemesSyncTest,
                         GetSyncTestModes(),
                         testing::PrintToStringParamName());

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTest, UploadsThemesOnInstall) {
  ASSERT_TRUE(SetupSync());

  ASSERT_FALSE(UsingCustomTheme(GetProfile(0)));
  UseCustomTheme(GetProfile(0), 0);
  EXPECT_TRUE(
      ServerThemeMatchChecker(HasCustomThemeWithId(GetCustomTheme(0))).Wait());

  if (IsSystemThemeDistinctFromDefaultTheme(GetProfile(0))) {
    ASSERT_FALSE(UsingSystemTheme(GetProfile(0)));
    UseSystemTheme(GetProfile(0));
    EXPECT_TRUE(ServerThemeMatchChecker(HasSystemTheme()).Wait());
  }

  ASSERT_FALSE(UsingDefaultTheme(GetProfile(0)));
  UseDefaultTheme(GetProfile(0));
  EXPECT_TRUE(ServerThemeMatchChecker(HasDefaultTheme()).Wait());
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTest, DownloadsCustomTheme) {
  ASSERT_TRUE(SetupSync());

  GetFakeServer()->InjectEntity(CreateCustomThemeEntity(GetCustomTheme(0)));
  // Note: The custom theme won't actually get installed; just check that it's
  // pending for installation.
  EXPECT_TRUE(
      ThemePendingInstallChecker(GetProfile(0), GetCustomTheme(0)).Wait());
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTest, DownloadsSystemTheme) {
  // Skip if this platform doesn't distinguish between system and default theme.
  ASSERT_TRUE(SetupClients());
  if (!IsSystemThemeDistinctFromDefaultTheme(GetProfile(0))) {
    return;
  }

  ASSERT_TRUE(SetupSync());

  // Set up a custom theme first, so we can then switch back to system.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(
      ServerThemeMatchChecker(HasCustomThemeWithId(GetCustomTheme(0))).Wait());
  ASSERT_TRUE(UsingCustomTheme(GetProfile(0)));

  ASSERT_FALSE(UsingSystemTheme(GetProfile(0)));
  GetFakeServer()->InjectEntity(CreateSystemThemeEntity());
  EXPECT_TRUE(SystemThemeChecker(GetProfile(0)).Wait());
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTest, DownloadsDefaultTheme) {
  ASSERT_TRUE(SetupSync());

  // Set up a custom theme first, so we can then switch back to default.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(
      ServerThemeMatchChecker(HasCustomThemeWithId(GetCustomTheme(0))).Wait());
  ASSERT_TRUE(UsingCustomTheme(GetProfile(0)));

  ASSERT_FALSE(UsingDefaultTheme(GetProfile(0)));
  GetFakeServer()->InjectEntity(CreateDefaultThemeEntity());
  EXPECT_TRUE(DefaultThemeChecker(GetProfile(0)).Wait());
}

// Verifies that theme from syncing theme prefs get applied if the migration is
// unset. After this, the migration flag should get set to disallow future reads
// from the syncing theme prefs. The incoming theme is committed to the server
// with the new fields in the ThemeSpecifics.
IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTest,
                       ShouldApplyThemeFromSyncingPrefsIfFlagUnmarked) {
  ASSERT_TRUE(SetupClients());
  // Migration flag is unset.
  ASSERT_TRUE(preferences_helper::GetPrefs(/*index=*/0)
                  ->GetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs));

  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::PreferenceSpecifics* preference_specifics =
        specifics.mutable_preference();
    preference_specifics->set_name(
        prefs::kDeprecatedBrowserColorSchemeDoNotUse);
    preference_specifics->set_value(
        preferences_helper::ConvertPrefValueToValueInSpecifics(base::Value(
            std::to_underlying(ThemeService::BrowserColorScheme::kLight))));

    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            /*non_unique_name=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            /*client_tag=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }
  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::PreferenceSpecifics* preference_specifics =
        specifics.mutable_preference();
    preference_specifics->set_name(prefs::kDeprecatedUserColorDoNotUse);
    preference_specifics->set_value(
        preferences_helper::ConvertPrefValueToValueInSpecifics(
            base::Value(static_cast<int>(SK_ColorRED))));

    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            /*non_unique_name=*/prefs::kDeprecatedUserColorDoNotUse,
            /*client_tag=*/prefs::kDeprecatedUserColorDoNotUse, specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }

  ASSERT_TRUE(SetupSync());
  // Themes was applied from the syncing theme prefs.
  EXPECT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))
                ->GetBrowserColorScheme(),
            ThemeService::BrowserColorScheme::kLight);
  EXPECT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))->GetUserColor(),
            SK_ColorRED);
  // Migration flag is set.
  EXPECT_FALSE(preferences_helper::GetPrefs(/*index=*/0)
                   ->GetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs));

  // The theme should get committed to the server with the new fields in
  // ThemeSpecifics.
  EXPECT_TRUE(ServerThemeMatchChecker(HasUserColor(SK_ColorRED)).Wait());
  EXPECT_TRUE(ServerThemeMatchChecker(
                  HasBrowserColorScheme(
                      sync_pb::ThemeSpecifics_BrowserColorScheme_LIGHT))
                  .Wait());
}

// Simulate pref migration being run in the previous browser session by setting
// the migration flag.
IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTest,
                       PRE_ShouldNotApplyThemeFromSyncingPrefsIfFlagMarked) {
  ASSERT_TRUE(SetupClients());

  // Set the flag to not read incoming prefs.
  preferences_helper::GetPrefs(/*index=*/0)
      ->SetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs, false);

  // Wait for data to be committed to disk.
  base::RunLoop loop;
  preferences_helper::GetPrefs(/*index=*/0)
      ->CommitPendingWrite(loop.QuitClosure());
  loop.Run();
}

// Verifies that the syncing theme prefs are not read if the migration is set.
IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTest,
                       ShouldNotApplyThemeFromSyncingPrefsIfFlagMarked) {
  ASSERT_TRUE(SetupClients());

  // Migration flag is already set.
  ASSERT_FALSE(preferences_helper::GetPrefs(/*index=*/0)
                   ->GetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs));

  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::PreferenceSpecifics* preference_specifics =
        specifics.mutable_preference();
    preference_specifics->set_name(
        prefs::kDeprecatedBrowserColorSchemeDoNotUse);
    preference_specifics->set_value(
        preferences_helper::ConvertPrefValueToValueInSpecifics(base::Value(
            std::to_underlying(ThemeService::BrowserColorScheme::kDark))));

    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            /*non_unique_name=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            /*client_tag=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }
  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::PreferenceSpecifics* preference_specifics =
        specifics.mutable_preference();
    preference_specifics->set_name(prefs::kDeprecatedUserColorDoNotUse);
    preference_specifics->set_value(
        preferences_helper::ConvertPrefValueToValueInSpecifics(
            base::Value(static_cast<int>(SK_ColorBLUE))));

    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            /*non_unique_name=*/prefs::kDeprecatedUserColorDoNotUse,
            /*client_tag=*/prefs::kDeprecatedUserColorDoNotUse, specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }

  ASSERT_TRUE(SetupSync());

  // Themes was not applied from the syncing theme prefs.
  EXPECT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))
                ->GetBrowserColorScheme(),
            ThemeService::BrowserColorScheme::kSystem);
  EXPECT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))->GetUserColor(),
            std::nullopt);
}

// Verifies that syncing theme prefs are not read with the incremental updates.
// They can only be applied when the prefs sync starts (which will set the
// migration flag to disallow future reads).
IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTest,
                       ShouldNotApplyThemeFromSyncingPrefsAfterSyncHasStarted) {
  ASSERT_TRUE(SetupClients());
  ASSERT_TRUE(preferences_helper::GetPrefs(/*index=*/0)
                  ->GetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs));

  ASSERT_TRUE(SetupSync());

  // The migration flag gets set upon sync start.
  EXPECT_FALSE(preferences_helper::GetPrefs(/*index=*/0)
                   ->GetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs));

  ASSERT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))
                ->GetBrowserColorScheme(),
            ThemeService::BrowserColorScheme::kSystem);

  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::PreferenceSpecifics* preference_specifics =
        specifics.mutable_preference();
    preference_specifics->set_name(
        prefs::kDeprecatedBrowserColorSchemeDoNotUse);
    preference_specifics->set_value(
        preferences_helper::ConvertPrefValueToValueInSpecifics(base::Value(
            std::to_underlying(ThemeService::BrowserColorScheme::kLight))));

    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            /*non_unique_name=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            /*client_tag=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }

  ASSERT_TRUE(PrefValueChecker(preferences_helper::GetPrefs(/*index=*/0),
                               prefs::kDeprecatedBrowserColorSchemeDoNotUse,
                               base::Value(static_cast<int>(
                                   ThemeService::BrowserColorScheme::kLight)))
                  .Wait());

  // The incoming theme pref is not applied.
  EXPECT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))
                ->GetBrowserColorScheme(),
            ThemeService::BrowserColorScheme::kSystem);
}

// Verifies that the syncing theme prefs are not applied if the incoming
// ThemeSpecifics has the new fields, which implies another client has already
// updated the ThemeSpecifics using the prefs.
IN_PROC_BROWSER_TEST_P(
    SingleClientThemesSyncTest,
    ShouldNotApplyThemeFromSyncingPrefsIfReceivedViaSpecifics) {
  ASSERT_TRUE(SetupClients());
  ASSERT_TRUE(preferences_helper::GetPrefs(/*index=*/0)
                  ->GetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs));
  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::PreferenceSpecifics* preference_specifics =
        specifics.mutable_preference();
    preference_specifics->set_name(
        prefs::kDeprecatedBrowserColorSchemeDoNotUse);
    preference_specifics->set_value(
        preferences_helper::ConvertPrefValueToValueInSpecifics(base::Value(
            std::to_underlying(ThemeService::BrowserColorScheme::kLight))));
    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            /*non_unique_name=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            /*client_tag=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }
  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::PreferenceSpecifics* preference_specifics =
        specifics.mutable_preference();
    preference_specifics->set_name(prefs::kDeprecatedUserColorDoNotUse);
    preference_specifics->set_value(
        preferences_helper::ConvertPrefValueToValueInSpecifics(
            base::Value(static_cast<int>(SK_ColorBLUE))));

    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            /*non_unique_name=*/prefs::kDeprecatedUserColorDoNotUse,
            /*client_tag=*/prefs::kDeprecatedUserColorDoNotUse, specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }
  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::ThemeSpecifics* theme_specifics = specifics.mutable_theme();
    theme_specifics->set_browser_color_scheme(
        sync_pb::ThemeSpecifics_BrowserColorScheme_DARK);

    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            ThemeSyncableService::kSyncEntityTitle,
            ThemeSyncableService::kSyncEntityClientTag, specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }

  ASSERT_TRUE(SetupSync());

  EXPECT_FALSE(preferences_helper::GetPrefs(/*index=*/0)
                   ->GetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs));
  // Theme from prefs is not applied.
  EXPECT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))
                ->GetBrowserColorScheme(),
            ThemeService::BrowserColorScheme::kDark);
  EXPECT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))->GetUserColor(),
            std::nullopt);
}

// Verifies that the syncing theme prefs are applied if the incoming
// ThemeSpecifics does not have the new fields.
IN_PROC_BROWSER_TEST_P(
    SingleClientThemesSyncTest,
    ShouldApplyThemeFromSyncingPrefsIfNotReceivedViaSpecifics) {
  ASSERT_TRUE(SetupClients());
  ASSERT_TRUE(preferences_helper::GetPrefs(/*index=*/0)
                  ->GetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs));
  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::PreferenceSpecifics* preference_specifics =
        specifics.mutable_preference();
    preference_specifics->set_name(
        prefs::kDeprecatedBrowserColorSchemeDoNotUse);
    preference_specifics->set_value(
        preferences_helper::ConvertPrefValueToValueInSpecifics(base::Value(
            std::to_underlying(ThemeService::BrowserColorScheme::kLight))));
    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            /*non_unique_name=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            /*client_tag=*/prefs::kDeprecatedBrowserColorSchemeDoNotUse,
            specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }
  {
    sync_pb::EntitySpecifics specifics;
    sync_pb::ThemeSpecifics* theme_specifics = specifics.mutable_theme();
    theme_specifics->mutable_autogenerated_color_theme()->set_color(
        SK_ColorRED);

    GetFakeServer()->InjectEntity(
        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
            ThemeSyncableService::kSyncEntityTitle,
            ThemeSyncableService::kSyncEntityClientTag, specifics,
            /*creation_time=*/0, /*last_modified_time=*/0));
  }

  ASSERT_TRUE(SetupSync());

  EXPECT_FALSE(preferences_helper::GetPrefs(/*index=*/0)
                   ->GetBoolean(prefs::kShouldReadIncomingSyncingThemePrefs));

  // Theme from prefs is applied.
  EXPECT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))
                ->GetBrowserColorScheme(),
            ThemeService::BrowserColorScheme::kLight);
  EXPECT_EQ(ThemeServiceFactory::GetForProfile(GetProfile(0))
                ->GetAutogeneratedThemeColor(),
            SK_ColorRED);
  // The theme is also committed to the server using the new fields in
  // ThemeSpecifics.
  EXPECT_TRUE(ServerThemeMatchChecker(
                  HasBrowserColorScheme(
                      sync_pb::ThemeSpecifics_BrowserColorScheme_LIGHT))
                  .Wait());
}

class SingleClientThemesSyncTestWithoutAccountThemesSeparation
    : public SingleClientThemesSyncTest {
 public:
  SingleClientThemesSyncTestWithoutAccountThemesSeparation() {
    feature_list_.InitAndDisableFeature(syncer::kSeparateLocalAndAccountThemes);
  }

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

INSTANTIATE_TEST_SUITE_P(
    ,
    SingleClientThemesSyncTestWithoutAccountThemesSeparation,
    // Themes is not supported in transport mode without account themes
    // separation.
    testing::Values(SyncTest::SetupSyncMode::kSyncTheFeature),
    testing::PrintToStringParamName());

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithoutAccountThemesSeparation,
                       UploadsPreexistingTheme) {
  ASSERT_TRUE(SetupClients());

  UseCustomTheme(GetProfile(0), 0);

  ASSERT_TRUE(SetupSync());

  EXPECT_TRUE(
      ServerThemeMatchChecker(HasCustomThemeWithId(GetCustomTheme(0))).Wait());
}

class SingleClientThemesSyncTestWithAccountThemesSeparation
    : public SingleClientThemesSyncTest {
 public:
  SingleClientThemesSyncTestWithAccountThemesSeparation()
      : feature_list_(syncer::kSeparateLocalAndAccountThemes) {}

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

INSTANTIATE_TEST_SUITE_P(,
                         SingleClientThemesSyncTestWithAccountThemesSeparation,
                         GetSyncTestModes(),
                         testing::PrintToStringParamName());

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       ShouldNotUploadPreexistingTheme) {
  ASSERT_TRUE(SetupClients());

  // Use custom theme locally.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  // Default theme on the server.
  ASSERT_FALSE(UsingDefaultTheme(GetProfile(0)));
  GetFakeServer()->InjectEntity(CreateDefaultThemeEntity());

  // Enable sync.
  ASSERT_TRUE(SetupSync());
  ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::THEMES));

  // Local custom theme is not uploaded to the account.
  EXPECT_TRUE(ServerThemeMatchChecker(HasDefaultTheme()).Wait());
  EXPECT_TRUE(UsingCustomTheme(GetProfile(0)));
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       ShouldRestoreLocalThemeUponSyncStop) {
  ASSERT_TRUE(SetupClients());

  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  const std::string custom_theme_id = GetCustomTheme(0);
  ASSERT_FALSE(custom_theme_id.empty());

  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(GetProfile(0));
  ASSERT_TRUE(registry->GetInstalledExtension(custom_theme_id));
  ASSERT_FALSE(registry->disabled_extensions().Contains(custom_theme_id));

  GetFakeServer()->InjectEntity(CreateGrayscaleThemeEntity());

  ASSERT_TRUE(SetupSync());
  EXPECT_TRUE(GrayscaleThemeChecker(GetProfile(0)).Wait());

  // The custom theme should remain installed but disabled.
  EXPECT_TRUE(registry->GetInstalledExtension(custom_theme_id));
  EXPECT_TRUE(registry->disabled_extensions().Contains(custom_theme_id));

  // Disable sync.
  ASSERT_TRUE(
      GetClient(0)->DisableSelectableType(syncer::UserSelectableType::kThemes));

  // Original local theme should get re-applied.
  EXPECT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());
  EXPECT_FALSE(UsingGrayscaleTheme(GetProfile(0)));

  EXPECT_TRUE(registry->GetInstalledExtension(custom_theme_id));
  EXPECT_FALSE(registry->disabled_extensions().Contains(custom_theme_id));
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       PRE_ShouldPersistSavedLocalThemeOverBrowserRestart) {
  ASSERT_TRUE(SetupClients());

  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  GetFakeServer()->InjectEntity(CreateGrayscaleThemeEntity());

  ASSERT_TRUE(SetupSync());
  EXPECT_TRUE(GrayscaleThemeChecker(GetProfile(0)).Wait());
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       ShouldPersistSavedLocalThemeOverBrowserRestart) {
  ASSERT_TRUE(SetupClients());
  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
  ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::THEMES));

  // Disable sync.
  ASSERT_TRUE(
      GetClient(0)->DisableSelectableType(syncer::UserSelectableType::kThemes));

  // Original local theme should get re-applied.
  EXPECT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());
  EXPECT_FALSE(UsingGrayscaleTheme(GetProfile(0)));
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       ShouldNotApplyLocalUpdateOnLocalThemeWhenSignedIn) {
  ASSERT_TRUE(SetupSync());

  // Change to a custom theme.
  UseCustomTheme(GetProfile(0), 0);
  // Custom theme is uploaded to account.
  EXPECT_TRUE(
      ServerThemeMatchChecker(HasCustomThemeWithId(GetCustomTheme(0))).Wait());

  // Disable syncing of themes.
  ASSERT_TRUE(
      GetClient(0)->DisableSelectableType(syncer::UserSelectableType::kThemes));

  // Original local theme is restored.
  EXPECT_TRUE(DefaultThemeChecker(GetProfile(0)).Wait());
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       ShouldNotApplyRemoteUpdateOnLocalTheme) {
  ASSERT_TRUE(SetupClients());

  // Set up a custom theme first, so we can then switch back to default.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  ASSERT_TRUE(SetupSync());

  // Remote update.
  GetFakeServer()->InjectEntity(CreateDefaultThemeEntity());
  EXPECT_TRUE(DefaultThemeChecker(GetProfile(0)).Wait());

  // Disable sync.
  ASSERT_TRUE(
      GetClient(0)->DisableSelectableType(syncer::UserSelectableType::kThemes));

  // Original local theme should get re-applied.
  EXPECT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());
  EXPECT_FALSE(UsingGrayscaleTheme(GetProfile(0)));
}

// Signing out is not supported on ChromeOS, thus excluded from the following
// tests.
#if !BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       ShouldReturnLocalDataDescriptions) {
  ASSERT_TRUE(SetupClients());

  // Use a custom theme locally on client 0.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  // Sign in and activate sync transport.
  ASSERT_TRUE(SignIn());
  ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::THEMES));

  EXPECT_THAT(GetClient(0)->GetLocalDataDescriptionAndWait(syncer::THEMES),
              MatchesLocalDataDescription(
                  syncer::THEMES,
                  ElementsAre(MatchesLocalDataItemModel(
                      ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId,
                      syncer::LocalDataItemModel::NoIcon(), "faketheme0",
                      /*subtitle=*/IsEmpty())),
                  // TODO(crbug.com/373568992): Merge Desktop and Mobile data
                  // under common struct.
                  /*item_count=*/0u,
                  /*domains=*/IsEmpty(),
                  /*domain_count=*/0u));
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       ShouldBatchUploadAllEntries) {
  ASSERT_TRUE(SetupClients());

  // Use custom theme locally.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  // Grayscale theme on the server.
  GetFakeServer()->InjectEntity(CreateGrayscaleThemeEntity());

  // Sign in and activate sync transport.
  ASSERT_TRUE(SignIn());
  ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::THEMES));

  ASSERT_TRUE(GrayscaleThemeChecker(GetProfile(0)).Wait());
  // Local custom theme is not uploaded to the account.
  ASSERT_TRUE(ServerThemeMatchChecker(HasGrayscaleTheme()).Wait());
  ASSERT_THAT(GetClient(0)->GetLocalDataDescriptionAndWait(syncer::THEMES),
              MatchesLocalDataDescription(
                  syncer::THEMES,
                  ElementsAre(MatchesLocalDataItemModel(
                      ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId,
                      syncer::LocalDataItemModel::NoIcon(), "faketheme0",
                      /*subtitle=*/IsEmpty())),
                  // TODO(crbug.com/373568992): Merge Desktop and Mobile data
                  // under common struct.
                  /*item_count=*/0u,
                  /*domains=*/IsEmpty(),
                  /*domain_count=*/0u));

  GetSyncService(0)->TriggerLocalDataMigration({syncer::THEMES});

  EXPECT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());
  // Local custom theme is now uploaded to the account.
  EXPECT_TRUE(
      ServerThemeMatchChecker(HasCustomThemeWithId(GetCustomTheme(0))).Wait());

  // Sign out.
  GetClient(0)->SignOutPrimaryAccount();
  EXPECT_TRUE(DefaultThemeChecker(GetProfile(0)).Wait());
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       ShouldBatchUploadSomeEntries) {
  ASSERT_TRUE(SetupClients());

  // Use custom theme locally.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  // Grayscale theme on the server.
  GetFakeServer()->InjectEntity(CreateGrayscaleThemeEntity());

  // Sign in and activate sync transport.
  ASSERT_TRUE(SignIn());
  ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(syncer::THEMES));

  ASSERT_TRUE(GrayscaleThemeChecker(GetProfile(0)).Wait());
  // Local custom theme is not uploaded to the account.
  ASSERT_TRUE(ServerThemeMatchChecker(HasGrayscaleTheme()).Wait());
  ASSERT_THAT(GetClient(0)->GetLocalDataDescriptionAndWait(syncer::THEMES),
              MatchesLocalDataDescription(
                  syncer::THEMES,
                  ElementsAre(MatchesLocalDataItemModel(
                      ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId,
                      syncer::LocalDataItemModel::NoIcon(), "faketheme0",
                      /*subtitle=*/IsEmpty())),
                  // TODO(crbug.com/373568992): Merge Desktop and Mobile data
                  // under common struct.
                  /*item_count=*/0u,
                  /*domains=*/IsEmpty(),
                  /*domain_count=*/0u));

  // Triggering batch upload without any item should be a no-op.
  GetSyncService(0)->TriggerLocalDataMigrationForItems({{syncer::THEMES, {}}});

  EXPECT_TRUE(GrayscaleThemeChecker(GetProfile(0)).Wait());
  // Local custom theme is not uploaded to the account.
  EXPECT_TRUE(ServerThemeMatchChecker(HasGrayscaleTheme()).Wait());
  EXPECT_THAT(GetClient(0)->GetLocalDataDescriptionAndWait(syncer::THEMES),
              MatchesLocalDataDescription(
                  syncer::THEMES,
                  ElementsAre(MatchesLocalDataItemModel(
                      ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId,
                      syncer::LocalDataItemModel::NoIcon(), "faketheme0",
                      /*subtitle=*/IsEmpty())),
                  // TODO(crbug.com/373568992): Merge Desktop and Mobile data
                  // under common struct.
                  /*item_count=*/0u,
                  /*domains=*/IsEmpty(),
                  /*domain_count=*/0u));

  GetSyncService(0)->TriggerLocalDataMigrationForItems(
      {{syncer::DataType::THEMES,
        {ThemeLocalDataBatchUploader::kThemesLocalDataItemModelId}}});

  EXPECT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());
  // Local custom theme is now uploaded to the account.
  EXPECT_TRUE(
      ServerThemeMatchChecker(HasCustomThemeWithId(GetCustomTheme(0))).Wait());

  // Sign out.
  GetClient(0)->SignOutPrimaryAccount();
  EXPECT_TRUE(DefaultThemeChecker(GetProfile(0)).Wait());
}

IN_PROC_BROWSER_TEST_P(
    SingleClientThemesSyncTestWithAccountThemesSeparation,
    PRE_ShouldClearAccountDataOnStartupIfSignInAllowedBitChanged) {
  ASSERT_TRUE(SetupClients());

  // Set the sign-in allowed bit to true initially.
  preferences_helper::GetPrefs(/*index=*/0)
      ->SetBoolean(prefs::kSigninAllowedOnNextStartup, true);

  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  GetFakeServer()->InjectEntity(CreateGrayscaleThemeEntity());

  ASSERT_TRUE(SignIn());

  // Account theme is effective.
  ASSERT_TRUE(GrayscaleThemeChecker(GetProfile(0)).Wait());

  // Simulate turning off the sign-in allowed bit on the settings page.
  preferences_helper::GetPrefs(/*index=*/0)
      ->SetBoolean(prefs::kSigninAllowedOnNextStartup, false);
}

IN_PROC_BROWSER_TEST_P(
    SingleClientThemesSyncTestWithAccountThemesSeparation,
    ShouldClearAccountDataOnStartupIfSignInAllowedBitChanged) {
  base::HistogramTester histogram_tester;
  ASSERT_TRUE(SetupClients());

  // Original local theme should get re-applied.
  // Note: The account theme is not cleared instantaneously upon startup, but
  // upon themes sync initialization.
  EXPECT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());
  EXPECT_FALSE(UsingGrayscaleTheme(GetProfile(0)));

  histogram_tester.ExpectUniqueSample(
      "Sync.SyncableService.MaybeClearDataIfMetadataEmptyOrInvalid.THEME", true,
      /*expected_bucket_count=*/1);
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

IN_PROC_BROWSER_TEST_P(
    SingleClientThemesSyncTestWithAccountThemesSeparation,
    PRE_ShouldClearAccountDataOnStartupIfAccountStateChanged) {
  ASSERT_TRUE(SetupClients());

  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  GetFakeServer()->InjectEntity(CreateGrayscaleThemeEntity());

#if BUILDFLAG(IS_CHROMEOS)
  ASSERT_TRUE(SetupSync());
#else
  ASSERT_TRUE(SignIn());
#endif  // BUILDFLAG(IS_CHROMEOS)

  // Account theme is effective.
  ASSERT_TRUE(GrayscaleThemeChecker(GetProfile(0)).Wait());

  // Simulate a data type error to prevent clearing of account data.
  GetSyncService(0)->ReportDataTypeErrorForTest(syncer::THEMES);
#if BUILDFLAG(IS_CHROMEOS)
  // Disable sync.
  ASSERT_TRUE(
      GetClient(0)->DisableSelectableType(syncer::UserSelectableType::kThemes));
#else
  // Sign out. Account theme should stay because of the data type error.
  GetClient(0)->SignOutPrimaryAccount();
#endif  // BUILDFLAG(IS_CHROMEOS)
  // Local theme is not restored because stop sync is not called when there is a
  // data type error.
  ASSERT_TRUE(UsingGrayscaleTheme(GetProfile(0)));

  ExcludeDataTypesFromCheckForDataTypeFailures({syncer::THEMES});
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesSyncTestWithAccountThemesSeparation,
                       ShouldClearAccountDataOnStartupIfAccountStateChanged) {
  base::HistogramTester histogram_tester;
  ASSERT_TRUE(SetupClients());

  // Original local theme should get re-applied.
  // Note: The account theme is not cleared instantaneously upon startup, but
  // upon themes sync initialization.
  EXPECT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());
  EXPECT_FALSE(UsingGrayscaleTheme(GetProfile(0)));

  // Clearing happened with StayStoppedAndMaybeClearData() called upon startup.
  histogram_tester.ExpectUniqueSample(
      "Sync.SyncableService.MaybeClearDataIfMetadataEmptyOrInvalid.THEME", true,
      /*expected_bucket_count=*/1);
}

// Signing out is not supported on ChromeOS, thus excluded from this test suite.
#if !BUILDFLAG(IS_CHROMEOS)
class SingleClientThemesSyncTestWithAccountThemesSeparationInSigninPendingState
    : public SingleClientThemesSyncTestWithAccountThemesSeparation {
 public:
  SyncTest::SetupSyncMode GetSetupSyncMode() const override { NOTREACHED(); }

  bool HasUnsyncedThemeData() {
    return GetClient(0)
        ->GetTypesWithUnsyncedDataAndWait({syncer::THEMES})
        .contains(syncer::THEMES);
  }
};

INSTANTIATE_TEST_SUITE_P(
    ,
    SingleClientThemesSyncTestWithAccountThemesSeparationInSigninPendingState,
    // The tests do not rely on SetupSync and testing only with transport mode
    // is sufficient.
    testing::Values(SyncTest::SetupSyncMode::kSyncTransportOnly),
    testing::PrintToStringParamName());

IN_PROC_BROWSER_TEST_P(
    SingleClientThemesSyncTestWithAccountThemesSeparationInSigninPendingState,
    ShouldMarkThemeChangeWhileSigninPendingUnsynced) {
  ASSERT_TRUE(SetupClients());
  ASSERT_TRUE(UsingDefaultTheme(GetProfile(0)));

  ASSERT_TRUE(SignIn());

  themes_helper::UseGrayscaleTheme(GetProfile(0));
  ASSERT_TRUE(CommittedAllNudgedChangesChecker(GetSyncService(0)).Wait());
  ASSERT_TRUE(UsingGrayscaleTheme(GetProfile(0)));

  // Enter sign-in pending state.
  ASSERT_TRUE(GetClient(0)->EnterSignInPendingStateForPrimaryAccount());
  ASSERT_FALSE(HasUnsyncedThemeData());

  // Set up a custom theme.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  // Themes should be checked for unsynced data when sign-out is attempted.
  ASSERT_TRUE(
      syncer::TypesRequiringUnsyncedDataCheckOnSignout().Has(syncer::THEMES));

  // Themes has unsynced data.
  EXPECT_TRUE(HasUnsyncedThemeData());

  // Sign out.
  GetClient(0)->SignOutPrimaryAccount();
  EXPECT_TRUE(DefaultThemeChecker(GetProfile(0)).Wait());
}

IN_PROC_BROWSER_TEST_P(
    SingleClientThemesSyncTestWithAccountThemesSeparationInSigninPendingState,
    ShouldCommitUnsyncedThemeChangeUponResolve) {
  ASSERT_TRUE(SetupClients());
  ASSERT_TRUE(UsingDefaultTheme(GetProfile(0)));

  ASSERT_TRUE(SignIn());

  themes_helper::UseGrayscaleTheme(GetProfile(0));
  ASSERT_TRUE(CommittedAllNudgedChangesChecker(GetSyncService(0)).Wait());
  ASSERT_TRUE(UsingGrayscaleTheme(GetProfile(0)));

  // Enter sign-in pending state.
  ASSERT_TRUE(GetClient(0)->EnterSignInPendingStateForPrimaryAccount());
  ASSERT_FALSE(HasUnsyncedThemeData());

  // Set up a custom theme.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  // Themes should be checked for unsynced data when sign-out is attempted.
  ASSERT_TRUE(
      syncer::TypesRequiringUnsyncedDataCheckOnSignout().Has(syncer::THEMES));

  // Themes has unsynced data.
  EXPECT_TRUE(HasUnsyncedThemeData());
  // Server still has the old theme.
  ASSERT_TRUE(ServerThemeMatchChecker(HasGrayscaleTheme()).Wait());

  // Resolve sign-in pending state.
  ASSERT_TRUE(GetClient(0)->ExitSignInPendingStateForPrimaryAccount());
  ASSERT_TRUE(UsingCustomTheme(GetProfile(0)));
  ASSERT_TRUE(
      ServerThemeMatchChecker(HasCustomThemeWithId(GetCustomTheme(0))).Wait());
}

IN_PROC_BROWSER_TEST_P(
    SingleClientThemesSyncTestWithAccountThemesSeparationInSigninPendingState,
    PRE_ShouldPersistUnsyncedThemeChangeAcrossRestart) {
  ASSERT_TRUE(SetupClients());
  ASSERT_TRUE(UsingDefaultTheme(GetProfile(0)));

  ASSERT_TRUE(SignIn());

  themes_helper::UseGrayscaleTheme(GetProfile(0));
  ASSERT_TRUE(CommittedAllNudgedChangesChecker(GetSyncService(0)).Wait());
  ASSERT_TRUE(UsingGrayscaleTheme(GetProfile(0)));

  // Enter sign-in pending state.
  ASSERT_TRUE(GetClient(0)->EnterSignInPendingStateForPrimaryAccount());
  ASSERT_FALSE(HasUnsyncedThemeData());

  // Set up a custom theme.
  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());
  ASSERT_TRUE(HasUnsyncedThemeData());
}

IN_PROC_BROWSER_TEST_P(
    SingleClientThemesSyncTestWithAccountThemesSeparationInSigninPendingState,
    ShouldPersistUnsyncedThemeChangeAcrossRestart) {
  ASSERT_TRUE(SetupClients());
  ASSERT_TRUE(UsingCustomTheme(GetProfile(0)));
  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportPaused());
  // Hacky wait for entity tracker to be loaded.
  ASSERT_TRUE(base::test::RunUntil([&]() {
    return ThemeServiceFactory::GetForProfile(GetProfile(0))
        ->GetThemeSyncableService()
        ->GetThemeSyncStartState()
        .has_value();
  }));

  // Themes has unsynced data.
  EXPECT_TRUE(HasUnsyncedThemeData());
  // Server still has the old theme.
  ASSERT_TRUE(ServerThemeMatchChecker(HasGrayscaleTheme()).Wait());

  // Resolve sign-in pending state.
  ASSERT_TRUE(GetClient(0)->ExitSignInPendingStateForPrimaryAccount());
  ASSERT_TRUE(UsingCustomTheme(GetProfile(0)));
  ASSERT_TRUE(
      ServerThemeMatchChecker(HasCustomThemeWithId(GetCustomTheme(0))).Wait());

  // Sign out.
  GetClient(0)->SignOutPrimaryAccount();
  ASSERT_TRUE(DefaultThemeChecker(GetProfile(0)).Wait());
}

#endif  // !BUILDFLAG(IS_CHROMEOS)

class SingleClientThemesMigrateSyncingUserToSignedInSyncTest
    : public SingleClientThemesSyncTestWithAccountThemesSeparation {
 public:
  SingleClientThemesMigrateSyncingUserToSignedInSyncTest() {
    std::vector<base::test::FeatureRef> enabled_features = {
        syncer::kReplaceSyncPromosWithSignInPromos,
        syncer::kSeparateLocalAndAccountThemes};
    std::vector<base::test::FeatureRef> disabled_features;
    if (content::IsPreTest()) {
      disabled_features.push_back(switches::kMigrateSyncingUserToSignedIn);
    } else {
      enabled_features.push_back(switches::kMigrateSyncingUserToSignedIn);
    }
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

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

INSTANTIATE_TEST_SUITE_P(
    ,
    SingleClientThemesMigrateSyncingUserToSignedInSyncTest,
    testing::Values(SyncTest::SetupSyncMode::kSyncTheFeature),
    testing::PrintToStringParamName());

IN_PROC_BROWSER_TEST_P(SingleClientThemesMigrateSyncingUserToSignedInSyncTest,
                       PRE_ShouldDeduplicateSameLocalAndAccountTheme) {
  ASSERT_TRUE(SetupClients());

  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  // Same theme on the server.
  GetFakeServer()->InjectEntity(CreateCustomThemeEntity(GetCustomTheme(0)));
  ASSERT_TRUE(SetupSyncWithMode(SyncTest::SetupSyncMode::kSyncTheFeature));
  ASSERT_TRUE(UsingCustomTheme(GetProfile(0)));
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesMigrateSyncingUserToSignedInSyncTest,
                       ShouldDeduplicateSameLocalAndAccountTheme) {
  ASSERT_TRUE(SetupClients());
  // The migration is triggered right after the initial sync. Since the local
  // theme was the same as the account theme, the saved local theme will get
  // removed.
  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());

  ASSERT_TRUE(UsingCustomTheme(GetProfile(0)));
  // Disable sync.
  ASSERT_TRUE(
      GetClient(0)->DisableSelectableType(syncer::UserSelectableType::kThemes));

  // The saved local theme was de-duped. Thus upon turning off the themes sync,
  // since no local theme is saved, the default theme is applied.
  EXPECT_TRUE(DefaultThemeChecker(GetProfile(0)).Wait());
  EXPECT_FALSE(UsingCustomTheme(GetProfile(0)));
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesMigrateSyncingUserToSignedInSyncTest,
                       PRE_ShouldNotDeduplicateDifferentLocalAndAccountTheme) {
  ASSERT_TRUE(SetupClients());

  UseCustomTheme(GetProfile(0), 0);
  ASSERT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());

  // Different theme on the server.
  GetFakeServer()->InjectEntity(CreateGrayscaleThemeEntity());

  ASSERT_TRUE(SetupSyncWithMode(SyncTest::SetupSyncMode::kSyncTheFeature));
  ASSERT_TRUE(GrayscaleThemeChecker(GetProfile(0)).Wait());
}

IN_PROC_BROWSER_TEST_P(SingleClientThemesMigrateSyncingUserToSignedInSyncTest,
                       ShouldNotDeduplicateDifferentLocalAndAccountTheme) {
  ASSERT_TRUE(SetupClients());
  // The migration is triggered right after the initial sync. However, since the
  // local theme is different from the account theme, no de-duplication happens.
  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());

  // Disable sync.
  ASSERT_TRUE(
      GetClient(0)->DisableSelectableType(syncer::UserSelectableType::kThemes));

  // The saved local theme is not de-duped. Thus upon turning off the themes
  // sync, the saved local theme is applied.
  EXPECT_TRUE(CustomThemeChecker(GetProfile(0)).Wait());
}

}  // namespace
