// 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 "components/browser_sync/sync_to_signin_migration.h"

#include <string>
#include <utility>
#include <vector>

#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/bookmarks/common/bookmark_constants.h"
#include "components/bookmarks/common/bookmark_features.h"
#include "components/browser_sync/browser_sync_switches.h"
#include "components/password_manager/core/browser/password_manager_constants.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/gaia_id_hash.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/data_type_histogram.h"
#include "components/sync/base/features.h"
#include "components/sync/base/pref_names.h"
#include "components/sync/service/sync_feature_status_for_migrations_recorder.h"
#include "components/sync/service/sync_prefs.h"
#include "extensions/buildflags/buildflags.h"
#include "google_apis/gaia/gaia_id.h"

namespace browser_sync {

#if !BUILDFLAG(IS_IOS)
namespace {

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(SyncToSigninMigrationDecision)
enum class SyncToSigninMigrationDecision {
  kMigrate = 0,
  kDontMigrateNotSignedIn = 1,
  kDontMigrateNotSyncing = 2,
  kDontMigrateSyncStatusUndefined = 3,
  kDontMigrateSyncStatusInitializing = 4,
  kDontMigrateFlagDisabled = 5,
  kUndoMigration = 6,
  kUndoNotNecessary = 7,
  kMigrateForced = 8,
  kDontMigrateAuthError = 9,
  kMaxValue = kDontMigrateAuthError
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncToSigninMigrationDecisionOverall)

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. Additionally, they're also persisted
// in a pref, so no existing entry should be removed.
// LINT.IfChange(SyncToSigninMigrationType)
enum class SyncToSigninMigrationType {
  // The user was migrated, but the migration type is unknown. This is logged in
  // case the migration happened before the corresponding histogram was
  // introduced.
  kUnknown = 0,
  // The user was migrated following the standard migration logic.
  kMigrated = 1,
  // The user was migrated with the force migration flag enabled.
  kForceMigrated = 2,
  // The user was have not yet been migrated.
  kNotMigratedYet = 3,
  // The users are already in the desired state, and the migration is not
  // needed.
  kMigrationNotNeeded = 4,
  kMaxValue = kMigrationNotNeeded
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncToSigninMigrationType)

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(SyncToSigninMigrationExecutionMode)
enum class SyncToSigninMigrationExecutionMode {
  kSynchronous = 0,
  kAsynchronous = 1,
  kMaxValue = kAsynchronous
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncToSigninMigrationExecutionMode)

#if !BUILDFLAG(IS_CHROMEOS)
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(SyncSetupIncompleteMigrationDecision)
enum class SyncSetupIncompleteMigrationDecision {
  kMigrate = 0,
  kDontMigrateNotSignedIn = 1,
  kDontMigrateNotSyncing = 2,
  kDontMigrateSyncSetupComplete = 3,
  kDontMigrateFlagDisabled = 4,
  kMaxValue = kDontMigrateFlagDisabled
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncSetupIncompleteMigrationDecision)
#endif  // !BUILDFLAG(IS_CHROMEOS)

// See docs of the kFirstTimeTriedToMigrateSyncFeaturePausedToSignin pref.
void SetFirstTimeTriedToMigrateSyncPaused(PrefService* pref_service) {
  const char* pref_name = syncer::prefs::internal::
      kFirstTimeTriedToMigrateSyncFeaturePausedToSignin;
  if (!base::FeatureList::IsEnabled(switches::kMigrateSyncingUserToSignedIn)) {
    pref_service->ClearPref(pref_name);
    return;
  }

  if (pref_service->FindPreference(pref_name)->IsDefaultValue() &&
      syncer::SyncFeatureStatusForMigrationsRecorder::
              GetSyncFeatureStatusForSyncToSigninMigration(pref_service) ==
          syncer::SyncFeatureStatusForSyncToSigninMigration::kPaused) {
    pref_service->SetTime(pref_name, base::Time::Now());
  }
}

SyncToSigninMigrationDecision GetSyncToSigninMigrationDecision(
    const PrefService* pref_service) {
  // If the flag to undo the migration is set, that overrides anything else.
  if (base::FeatureList::IsEnabled(
          switches::kUndoMigrationOfSyncingUserToSignedIn)) {
    if (pref_service
            ->GetString(prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn)
            .empty()) {
      // The user was never migrated, or the migration was already undone.
      // Nothing to be done here.
      return SyncToSigninMigrationDecision::kUndoNotNecessary;
    } else {
      // The user (or more precisely, this profile) was previously migrated.
      return SyncToSigninMigrationDecision::kUndoMigration;
    }
  }

  if (pref_service->GetString(prefs::kGoogleServicesAccountId).empty()) {
    // Signed-out user, nothing to migrate.
    return SyncToSigninMigrationDecision::kDontMigrateNotSignedIn;
  }

  if (!pref_service->GetBoolean(prefs::kGoogleServicesConsentedToSync)) {
    // Not a syncing user, nothing to migrate (or already migrated).
    return SyncToSigninMigrationDecision::kDontMigrateNotSyncing;
  }

  syncer::SyncFeatureStatusForSyncToSigninMigration status =
      syncer::SyncFeatureStatusForMigrationsRecorder::
          GetSyncFeatureStatusForSyncToSigninMigration(pref_service);
  bool forced = false;
  switch (status) {
    case syncer::SyncFeatureStatusForSyncToSigninMigration::kDisabled:
    case syncer::SyncFeatureStatusForSyncToSigninMigration::kActive:
      // In all these cases, the status is known, and migration can go ahead.
      break;
    case syncer::SyncFeatureStatusForSyncToSigninMigration::kPaused: {
      base::FeatureList::IsEnabled(switches::kForceMigrateNoopForDebugging);
      if (base::FeatureList::IsEnabled(
              switches::kForceMigrateSyncingUserToSignedIn)) {
        forced = true;
        break;
      }
      base::Time first_attempt_time = pref_service->GetTime(
          syncer::prefs::internal::
              kFirstTimeTriedToMigrateSyncFeaturePausedToSignin);
      if (!first_attempt_time.is_null() &&
          base::Time::Now() <
              first_attempt_time +
                  switches::kMinDelayToMigrateSyncPaused.Get()) {
        return SyncToSigninMigrationDecision::kDontMigrateAuthError;
      }
      // The auth error hasn't been resolved within the allotted time. Go ahead
      // with the migration.
      break;
    }
    case syncer::SyncFeatureStatusForSyncToSigninMigration::kInitializing:
      // In the previous browser run, Sync didn't finish initializing. Defer
      // migration, unless force-migration is enabled.
      base::FeatureList::IsEnabled(switches::kForceMigrateNoopForDebugging);
      if (base::FeatureList::IsEnabled(
              switches::kForceMigrateSyncingUserToSignedIn)) {
        forced = true;
        break;
      }
      return SyncToSigninMigrationDecision::kDontMigrateSyncStatusInitializing;
    case syncer::SyncFeatureStatusForSyncToSigninMigration::kUndefined:
      // The Sync status pref was never set (which should only happen once per
      // client), or has an unknown/invalid value (which should never happen).
      // Defer migration, unless force-migration is enabled.
      base::FeatureList::IsEnabled(switches::kForceMigrateNoopForDebugging);
      if (base::FeatureList::IsEnabled(
              switches::kForceMigrateSyncingUserToSignedIn)) {
        forced = true;
        break;
      }
      return SyncToSigninMigrationDecision::kDontMigrateSyncStatusUndefined;
  }

  // Check the feature flag(s) last, so that metrics can record all the other
  // reasons to not do the migration, even with the flag disabled.
  if (!syncer::IsReplaceSyncPromosWithSignInPromosEnabled() ||
      !base::FeatureList::IsEnabled(switches::kMigrateSyncingUserToSignedIn)) {
    return SyncToSigninMigrationDecision::kDontMigrateFlagDisabled;
  }

  return forced ? SyncToSigninMigrationDecision::kMigrateForced
                : SyncToSigninMigrationDecision::kMigrate;
}

void UndoSyncToSigninMigration(PrefService* pref_service) {
  const std::string migrated_gaia_id = pref_service->GetString(
      prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn);

  if (migrated_gaia_id.empty()) {
    // The user was never migrated, or the migration was already undone. Nothing
    // to be done here.
    return;
  }

  const std::string signed_in_gaia_id =
      pref_service->GetString(prefs::kGoogleServicesAccountId);

  if (signed_in_gaia_id != migrated_gaia_id) {
    // The user was migrated, but has since signed out, or signed in with a
    // different account. Clean up the migration prefs; otherwise nothing to be
    // done here.
    pref_service->ClearPref(
        prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn);
    pref_service->ClearPref(
        prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn);
    pref_service->ClearPref(prefs::kGoogleServicesSyncingUserMigrationType);
    return;
  }

  // The user was migrated, and is still signed in with the same account. Undo
  // the migration.

  // Mark the user as syncing again.
  pref_service->SetBoolean(prefs::kGoogleServicesConsentedToSync, true);
#if !BUILDFLAG(IS_CHROMEOS)
  pref_service->SetBoolean(
      syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete, true);
#endif

  // Restore the "previously syncing user" prefs too.
  pref_service->SetString(prefs::kGoogleServicesLastSyncingGaiaId,
                          signed_in_gaia_id);
  pref_service->SetString(
      prefs::kGoogleServicesLastSyncingUsername,
      pref_service->GetString(
          prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn));

  pref_service->ClearPref(
      syncer::prefs::internal::
          kFirstTimeTriedToMigrateSyncFeaturePausedToSignin);

  // Clear the "migrated user" prefs, so the "undo" logic doesn't run again.
  pref_service->ClearPref(
      prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn);
  pref_service->ClearPref(
      prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn);
  pref_service->ClearPref(prefs::kGoogleServicesSyncingUserMigrationType);

  // Selected-data-types prefs: No reverse migration - the user will just go
  // back to their previous Sync settings.

  // Bookmarks: The forward migration is an atomic file move. Either that
  // happened, in which case the Sync machinery will clean up the account store
  // and start over with the local-or-syncable store. Or the file move didn't
  // happen for some reason. Either way, nothing to be done here.

  // Passwords: Same as bookmarks, this is an atomic file move. Nothing to be
  // done here.

  // ReadingList: The migration is asynchronous. Most likely it has been
  // completed by this point, but in case it's still pending, stop attempting it
  // now.
  pref_service->ClearPref(
      syncer::prefs::internal::kMigrateReadingListFromLocalToAccount);

  // Extensions: The migration is asynchronous. Most likely it has been
  // completed by this point, but in case it's still pending, stop attempting it
  // now.
  pref_service->ClearPref(
      syncer::prefs::internal::kMigrateExtensionsFromLocalToAccount);
}

const char* GetHistogramMigratingOrNotInfix(bool doing_migration) {
  return doing_migration ? "Migration." : "DryRun.";
}

// Represents a potentially blocking operation that can succeed or fail.
using BlockingOperation = base::OnceCallback<bool()>;

// Runs `blocking_operations` and return the aggregated success or failure.
bool RunBlockingOperations(bool migration_successful,
                           std::vector<BlockingOperation> blocking_operations) {
  for (auto& operation : blocking_operations) {
    if (!std::move(operation).Run()) {
      migration_successful = false;
    }
  }
  return migration_successful;
}

// Rename a file from `from_path` to `dest_path` and report the status code
// via the histogram named `histogram_name`.
bool RenameFileAndReportSuccess(const base::FilePath& from_path,
                                const base::FilePath& dest_path,
                                const char* histogram_name) {
  base::File::Error error = base::File::Error::FILE_OK;
  base::ReplaceFile(from_path, dest_path, &error);
  base::UmaHistogramExactLinear(histogram_name, -error,
                                -base::File::FILE_ERROR_MAX);
  return error == base::File::Error::FILE_OK;
}

// Record the overall migration outcome, i.e. whether all individual data type
// migrations were successful. The total count of this histogram also serves
// as the number of migrations that were completed.
void RecordMigrationResult(base::Time start_time, bool migration_successful) {
  base::UmaHistogramBoolean("Sync.SyncToSigninMigrationOutcome",
                            migration_successful);
  base::UmaHistogramTimes("Sync.SyncToSigninMigrationTime",
                          base::Time::Now() - start_time);
}

void RecordMigrationType(PrefService* pref_service,
                         SyncToSigninMigrationDecision decision) {
  SyncToSigninMigrationType type =
      SyncToSigninMigrationType::kMigrationNotNeeded;
  switch (decision) {
    case SyncToSigninMigrationDecision::kDontMigrateNotSignedIn:
    case SyncToSigninMigrationDecision::kDontMigrateNotSyncing:
      // The user is already in the desired state.
      {
        const bool was_migrated =
            !pref_service
                 ->GetString(
                     prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn)
                 .empty();
        if (was_migrated) {
          // This returns kUnknown (the default value) if the pref is unset,
          // which implies that the migration happened before this metric
          // logging was introduced.
          type =
              static_cast<SyncToSigninMigrationType>(pref_service->GetInteger(
                  prefs::kGoogleServicesSyncingUserMigrationType));
        }
        // Else, the user was always in the desired state, so `type` remains
        // `kMigrationNotNeeded`.
      }
      break;
    case SyncToSigninMigrationDecision::kMigrate:
      // The user is currently undergoing regular migration.
      type = SyncToSigninMigrationType::kMigrated;
      break;
    case SyncToSigninMigrationDecision::kMigrateForced:
      // The user is currently undergoing forced migration.
      type = SyncToSigninMigrationType::kForceMigrated;
      break;
    case SyncToSigninMigrationDecision::kDontMigrateFlagDisabled:
    case SyncToSigninMigrationDecision::kDontMigrateSyncStatusUndefined:
    case SyncToSigninMigrationDecision::kDontMigrateSyncStatusInitializing:
    case SyncToSigninMigrationDecision::kDontMigrateAuthError:
      // These cases should not be reached in case the migration has already
      // happened, or is happening right now. But rather, this implies the user
      // is eligible for regular migration but some pre-condition has not been
      // met.
      type = SyncToSigninMigrationType::kNotMigratedYet;
      break;
    case SyncToSigninMigrationDecision::kUndoMigration:
    case SyncToSigninMigrationDecision::kUndoNotNecessary:
      // No need to record the metric in this case.
      return;
  }
  base::UmaHistogramEnumeration("Sync.SyncToSigninMigration.MigrationType",
                                type);
}

// Helper used to share the logic between MaybeMigrateSyncingUserToSignedIn()
// and MaybeMigrateSyncingUserToSignedInAsync(). If `closure` is null, all is
// run on the current sequence otherwise IO ops are scheduled on a background
// sequence and `closure` is invoked when all operation completes.
void MaybeMigrateSyncingUserToSignedInInternal(
    const base::FilePath& profile_path,
    PrefService* pref_service,
    base::OnceClosure closure) {
  base::Time start_time = base::Time::Now();

  // Store the closure in a ScopedClosureRunner to ensure it is always run
  // even if the migration is unnecessary and the function returns early.
  // Wrap it in a base::BindPostTask(...) to ensure it is always called
  // asynchronously even if no migration is necessary.
  const bool is_blocking_allowed = closure.is_null();
  base::ScopedClosureRunner runner(
      is_blocking_allowed
          ? base::OnceClosure()
          : base::BindPostTask(base::SequencedTaskRunner::GetCurrentDefault(),
                               std::move(closure)));

  // ======================================
  // Global migration decision and metrics.
  // ======================================

  // Influences GetSyncToSigninMigrationDecision(), so call it first.
  SetFirstTimeTriedToMigrateSyncPaused(pref_service);

  const SyncToSigninMigrationDecision decision =
      GetSyncToSigninMigrationDecision(pref_service);
  base::UmaHistogramEnumeration("Sync.SyncToSigninMigrationDecision", decision);
  RecordMigrationType(pref_service, decision);
  switch (decision) {
    case SyncToSigninMigrationDecision::kUndoMigration:
      // Undo the migration (if appropriate) and nothing else.
      UndoSyncToSigninMigration(pref_service);
      return;
    case SyncToSigninMigrationDecision::kDontMigrateNotSignedIn:
    case SyncToSigninMigrationDecision::kDontMigrateNotSyncing:
    case SyncToSigninMigrationDecision::kDontMigrateSyncStatusUndefined:
    case SyncToSigninMigrationDecision::kDontMigrateSyncStatusInitializing:
    case SyncToSigninMigrationDecision::kDontMigrateAuthError:
    case SyncToSigninMigrationDecision::kUndoNotNecessary:
      // No migration, and no point in recording per-type metrics - we're done.
      return;
    case SyncToSigninMigrationDecision::kDontMigrateFlagDisabled:
    case SyncToSigninMigrationDecision::kMigrate:
    case SyncToSigninMigrationDecision::kMigrateForced:
      // If actually migrating, or the feature flag being disabled is the only
      // reason for not migrating, also record more detailed per-type metrics.
      break;
  }

  // ===================================================
  // Data-type-specific migration decisions and metrics.
  // ===================================================

  const bool doing_migration =
      decision == SyncToSigninMigrationDecision::kMigrate ||
      decision == SyncToSigninMigrationDecision::kMigrateForced;

  const SyncToSigninMigrationDataTypeDecision bookmarks_decision =
      GetSyncToSigninMigrationDataTypeDecision(
          pref_service, syncer::BOOKMARKS,
          syncer::prefs::internal::kSyncBookmarks);
  base::UmaHistogramEnumeration(
      base::StrCat({"Sync.SyncToSigninMigrationDecision.",
                    GetHistogramMigratingOrNotInfix(doing_migration),
                    syncer::DataTypeToHistogramSuffix(syncer::BOOKMARKS)}),
      bookmarks_decision);

#if !BUILDFLAG(IS_ANDROID)
  // On Android no password migration is required here, because other layers
  // were responsible for migrating the user to the local+account model in the
  // past.
  const SyncToSigninMigrationDataTypeDecision passwords_decision =
      GetSyncToSigninMigrationDataTypeDecision(
          pref_service, syncer::PASSWORDS,
          syncer::prefs::internal::kSyncPasswords);
  base::UmaHistogramEnumeration(
      base::StrCat({"Sync.SyncToSigninMigrationDecision.",
                    GetHistogramMigratingOrNotInfix(doing_migration),
                    syncer::DataTypeToHistogramSuffix(syncer::PASSWORDS)}),
      passwords_decision);
#endif  // !BUILDFLAG(IS_ANDROID)

  const SyncToSigninMigrationDataTypeDecision reading_list_decision =
      GetSyncToSigninMigrationDataTypeDecision(
          pref_service, syncer::READING_LIST,
          syncer::prefs::internal::kSyncReadingList);
  base::UmaHistogramEnumeration(
      base::StrCat({"Sync.SyncToSigninMigrationDecision.",
                    GetHistogramMigratingOrNotInfix(doing_migration),
                    syncer::DataTypeToHistogramSuffix(syncer::READING_LIST)}),
      reading_list_decision);

#if BUILDFLAG(ENABLE_EXTENSIONS)
  const SyncToSigninMigrationDataTypeDecision extensions_decision =
      GetSyncToSigninMigrationDataTypeDecision(
          pref_service, syncer::EXTENSIONS,
          syncer::prefs::internal::kSyncExtensions);
  base::UmaHistogramEnumeration(
      base::StrCat({"Sync.SyncToSigninMigrationDecision.",
                    GetHistogramMigratingOrNotInfix(doing_migration),
                    syncer::DataTypeToHistogramSuffix(syncer::EXTENSIONS)}),
      extensions_decision);
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  const SyncToSigninMigrationDataTypeDecision themes_decision =
      GetSyncToSigninMigrationDataTypeDecision(
          pref_service, syncer::THEMES, syncer::prefs::internal::kSyncThemes);
  base::UmaHistogramEnumeration(
      base::StrCat({"Sync.SyncToSigninMigrationDecision.",
                    GetHistogramMigratingOrNotInfix(doing_migration),
                    syncer::DataTypeToHistogramSuffix(syncer::THEMES)}),
      themes_decision);
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)

  if (!doing_migration) {
    return;
  }

  base::UmaHistogramEnumeration(
      "Sync.SyncToSigninMigrationExecutionMode",
      is_blocking_allowed ? SyncToSigninMigrationExecutionMode::kSynchronous
                          : SyncToSigninMigrationExecutionMode::kAsynchronous);

  // =========================
  // Global (prefs) migration.
  // =========================

  // The account identifier of an account is its Gaia ID. So
  // `kGoogleServicesAccountId` stores the Gaia ID of the syncing account.
  const GaiaId gaia_id =
      GaiaId(pref_service->GetString(prefs::kGoogleServicesAccountId));
  // Guaranteed to be non-empty by GetSyncToSigninMigrationDecision().
  CHECK(!gaia_id.empty());

  // Remove ConsentLevel::kSync. This also ensures that the whole migration will
  // not run a second time.
  // Note that it's important to explicitly set this pref to false (not just
  // clear it), since the signin code treats "unset" differently.
  pref_service->SetBoolean(prefs::kGoogleServicesConsentedToSync, false);
  // Save the ID and username of the migrated account, to be able to revert the
  // migration if necessary.
  pref_service->SetString(prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn,
                          gaia_id.ToString());
  pref_service->SetString(
      prefs::kGoogleServicesSyncingUsernameMigratedToSignedIn,
      pref_service->GetString(prefs::kGoogleServicesLastSyncingUsername));
  pref_service->SetInteger(
      prefs::kGoogleServicesSyncingUserMigrationType,
      static_cast<int>(decision == SyncToSigninMigrationDecision::kMigrate
                           ? SyncToSigninMigrationType::kMigrated
                           : SyncToSigninMigrationType::kForceMigrated));
  // Clear the "previously syncing user" prefs, to prevent accidental misuse.
  pref_service->ClearPref(prefs::kGoogleServicesLastSyncingGaiaId);
  pref_service->ClearPref(prefs::kGoogleServicesLastSyncingUsername);
  // Also clear the "InitialSyncFeatureSetup" pref. It's not needed
  // post-migration, and that pref being true without ConsentLevel::kSync would
  // be an inconsistent state.
#if !BUILDFLAG(IS_CHROMEOS)
  pref_service->ClearPref(
      syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete);
#endif

  // Migrate the global data type prefs (used for Sync-the-feature) over to the
  // account-specific ones.
  syncer::SyncPrefs::MigrateGlobalDataTypePrefsToAccount(pref_service, gaia_id);

  // Ensure the prefs changes are persisted as soon as possible. (They get
  // persisted on shutdown anyway, but better make sure.)
  pref_service->CommitPendingWrite();

  // ==============================
  // Data-type-specific migrations.
  // ==============================

  bool migration_successful = true;
  std::vector<BlockingOperation> blocking_operations;

// On Android no password migration is required here, because other layers are
// responsible for migrating the user to the local+account model, e.g.
// SetUsesSplitStoresAndUPMForLocal(), PasswordStoreBackendMigrationDecorator.
#if !BUILDFLAG(IS_ANDROID)
  // Move passwords DB file, if password sync is enabled.
  if (passwords_decision == SyncToSigninMigrationDataTypeDecision::kMigrate) {
    base::FilePath from_path =
        profile_path.Append(password_manager::kLoginDataForProfileFileName);
    base::FilePath to_path =
        profile_path.Append(password_manager::kLoginDataForAccountFileName);
    blocking_operations.push_back(
        base::BindOnce(&RenameFileAndReportSuccess, from_path, to_path,
                       "Sync.SyncToSigninMigrationOutcome.PasswordsFileMove"));
    // Mark clearing of the stats table from the newly migrated password store.
    pref_service->SetBoolean(
        syncer::prefs::kCleanUpStatsTableFromAccountPasswordStore, true);
    syncer::RecordSyncToSigninMigrationStatsTableCleanupStep(
        syncer::SyncToSigninMigrationStatsTableCleanupStep::kCleanupRequested);
  }
#endif  // !BUILDFLAG(IS_ANDROID)

  // Move bookmarks json file, if bookmark sync is enabled.
  if (bookmarks_decision == SyncToSigninMigrationDataTypeDecision::kMigrate) {
    base::FilePath from_path =
        profile_path.Append(bookmarks::kLocalOrSyncableBookmarksFileName);
    base::FilePath to_path =
        profile_path.Append(bookmarks::kAccountBookmarksFileName);
    blocking_operations.push_back(
        base::BindOnce(&RenameFileAndReportSuccess, from_path, to_path,
                       "Sync.SyncToSigninMigrationOutcome.BookmarksFileMove"));
    if (base::FeatureList::IsEnabled(bookmarks::kEncryptBookmarks)) {
      base::FilePath encrypted_from_path = profile_path.Append(
          bookmarks::kEncryptedLocalOrSyncableBookmarksFileName);
      base::FilePath encrypted_to_path =
          profile_path.Append(bookmarks::kEncryptedAccountBookmarksFileName);
      blocking_operations.push_back(base::BindOnce(
          &RenameFileAndReportSuccess, encrypted_from_path, encrypted_to_path,
          "Sync.SyncToSigninMigrationOutcome.EncryptedBookmarksFileMove"));
    }
  }

  // Reading list: Set migration pref. The DataTypeStoreServiceImpl will read
  // it, and instruct the DataTypeStoreBackend to actually migrate the data.
  // Note that DataTypeStoreServiceImpl (a KeyedService) can't have been
  // constructed yet, so no risk of race conditions.
  if (reading_list_decision ==
      SyncToSigninMigrationDataTypeDecision::kMigrate) {
    pref_service->SetBoolean(
        syncer::prefs::internal::kMigrateReadingListFromLocalToAccount, true);
    syncer::RecordSyncToSigninMigrationReadingListStep(
        syncer::ReadingListMigrationStep::kMigrationRequested);

    // Note: Triggering this migration cannot fail, so no need to update
    // `migration_successful` here. The actual outcome will be recorded in other
    // histograms.
  }

#if BUILDFLAG(ENABLE_EXTENSIONS)
  if (extensions_decision == SyncToSigninMigrationDataTypeDecision::kMigrate) {
    pref_service->SetBoolean(
        syncer::prefs::internal::kMigrateExtensionsFromLocalToAccount, true);
    syncer::RecordSyncToSigninMigrationExtensionsStep(
        syncer::SyncToSigninMigrationExtensionsStep::kMigrationRequested);
  }
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  if (themes_decision == SyncToSigninMigrationDataTypeDecision::kMigrate) {
    pref_service->SetBoolean(
        syncer::prefs::internal::kMigrateThemeFromLocalToAccount, true);
    syncer::RecordSyncToSigninMigrationThemeStep(
        syncer::SyncToSigninMigrationThemeStep::kMigrationRequested);
  }
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)

  if (!is_blocking_allowed) {
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE, {base::MayBlock()},
        base::BindOnce(&RunBlockingOperations, migration_successful,
                       std::move(blocking_operations)),
        base::BindOnce(&RecordMigrationResult, start_time)
            .Then(runner.Release()));
    return;
  }

  RecordMigrationResult(start_time,
                        RunBlockingOperations(migration_successful,
                                              std::move(blocking_operations)));
}

// On ChromeOS, there exists no sync setup incomplete state.
#if !BUILDFLAG(IS_CHROMEOS)
SyncSetupIncompleteMigrationDecision GetSyncSetupIncompleteMigrationDecision(
    PrefService* pref_service) {
  if (pref_service->GetString(prefs::kGoogleServicesAccountId).empty()) {
    // Signed-out user, nothing to migrate.
    return SyncSetupIncompleteMigrationDecision::kDontMigrateNotSignedIn;
  }

  if (!pref_service->GetBoolean(prefs::kGoogleServicesConsentedToSync)) {
    // Not a syncing user, nothing to migrate.
    return SyncSetupIncompleteMigrationDecision::kDontMigrateNotSyncing;
  }

  if (pref_service->GetBoolean(
          syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete)) {
    // Sync setup is complete, nothing to migrate.
    return SyncSetupIncompleteMigrationDecision::kDontMigrateSyncSetupComplete;
  }

  return base::FeatureList::IsEnabled(syncer::kUnoPhase2FollowUp) &&
                 base::FeatureList::IsEnabled(
                     switches::kMigrateOutOfSyncSetupIncompleteState)
             ? SyncSetupIncompleteMigrationDecision::kMigrate
             : SyncSetupIncompleteMigrationDecision::kDontMigrateFlagDisabled;
}

void MaybeMigrateUserWithSyncSetupIncomplete(const base::FilePath& profile_path,
                                             PrefService* pref_service) {
  const SyncSetupIncompleteMigrationDecision decision =
      GetSyncSetupIncompleteMigrationDecision(pref_service);
  base::UmaHistogramEnumeration("Sync.SyncSetupIncompleteMigrationDecision",
                                decision);
  if (decision != SyncSetupIncompleteMigrationDecision::kMigrate) {
    return;
  }
  // Remove ConsentLevel::kSync. This also ensures that the whole migration will
  // not run a second time.
  // Note that it's important to explicitly set this pref to false (not just
  // clear it), since the signin code treats "unset" differently.
  pref_service->SetBoolean(prefs::kGoogleServicesConsentedToSync, false);
  // Clear the "previously syncing user" prefs, to prevent accidental misuse.
  pref_service->ClearPref(prefs::kGoogleServicesLastSyncingGaiaId);
  pref_service->ClearPref(prefs::kGoogleServicesLastSyncingUsername);
  // Clear the "InitialSyncFeatureSetup" pref. It's not needed post-migration,
  // given that users should not be able to enter the sync state anymore.
  pref_service->ClearPref(
      syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete);
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

}  // namespace

SyncToSigninMigrationDataTypeDecision GetSyncToSigninMigrationDataTypeDecision(
    const PrefService* pref_service,
    syncer::DataType type,
    const char* type_enabled_pref) {
#if BUILDFLAG(IS_CHROMEOS)
  // In ChromeOS-Ash, the "initial-setup-complete" pref doesn't exist.
  bool initial_setup_complete = true;
#else
  bool initial_setup_complete = pref_service->GetBoolean(
      syncer::prefs::internal::kSyncInitialSyncFeatureSetupComplete);
#endif
  bool sync_everything = pref_service->GetBoolean(
      syncer::prefs::internal::kSyncKeepEverythingSynced);

  bool type_enabled =
      initial_setup_complete &&
      (sync_everything || pref_service->GetBoolean(type_enabled_pref));
  if (!type_enabled) {
    return SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeDisabled;
  }

  bool type_active = syncer::SyncFeatureStatusForMigrationsRecorder::
      GetSyncDataTypeActiveForSyncToSigninMigration(pref_service, type);
  if (!type_active) {
    return SyncToSigninMigrationDataTypeDecision::kDontMigrateTypeNotActive;
  }

  return SyncToSigninMigrationDataTypeDecision::kMigrate;
}

void MaybeMigrateSyncingUserToSignedIn(const base::FilePath& profile_path,
                                       PrefService* pref_service) {
  // On ChromeOS, there exists no sync setup incomplete state.
#if !BUILDFLAG(IS_CHROMEOS)
  MaybeMigrateUserWithSyncSetupIncomplete(profile_path, pref_service);
#endif  // !BUILDFLAG(IS_CHROMEOS)
  MaybeMigrateSyncingUserToSignedInInternal(profile_path, pref_service, {});
}

void MaybeMigrateSyncingUserToSignedInAsync(const base::FilePath& profile_path,
                                            PrefService* pref_service,
                                            base::OnceClosure closure) {
  CHECK(!closure.is_null());
  MaybeMigrateSyncingUserToSignedInInternal(profile_path, pref_service,
                                            std::move(closure));
}
#endif  // !BUILDFLAG(IS_IOS)

bool WasPrimaryAccountMigratedFromSyncingToSignedIn(
    const signin::IdentityManager* identity_manager,
    const PrefService* pref_service) {
  // Only signed-in non-syncing users can be in the "migrated" state.
  if (!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin) ||
      identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
    return false;
  }

  // Check if the current signed-in account ID matches the migrated account ID.
  // In the common case where the account was *not* migrated, the migrated
  // account ID will be empty, and thus not match the current account ID.
  const GaiaId authenticated_gaia_id =
      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
          .gaia;
  const GaiaId migrated_gaia_id(pref_service->GetString(
      prefs::kGoogleServicesSyncingGaiaIdMigratedToSignedIn));
  return migrated_gaia_id == authenticated_gaia_id;
}

}  // namespace browser_sync
