// Copyright 2018 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/sync/service/sync_user_settings_impl.h"

#include <utility>

#include "base/auto_reset.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/version.h"
#include "components/signin/public/base/gaia_id_hash.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/sync/base/custom_passphrase_bootstrap_token.h"
#include "components/sync/base/features.h"
#include "components/sync/base/passphrase_enums.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/model/crypto/nigori.h"
#include "components/sync/service/sync_prefs.h"
#include "components/sync/service/sync_service_crypto.h"
#include "components/version_info/version_info.h"
#include "google_apis/gaia/gaia_id.h"

namespace syncer {

namespace {

// Converts `selected_types` to the corresponding DataTypeSet (e.g.
// {kExtensions} becomes {EXTENSIONS, EXTENSION_SETTINGS}).
DataTypeSet UserSelectableTypesToDataTypes(
    UserSelectableTypeSet selected_types) {
  DataTypeSet preferred_types;
  for (UserSelectableType type : selected_types) {
    preferred_types.PutAll(UserSelectableTypeToAllDataTypes(type));
  }
  return preferred_types;
}

#if BUILDFLAG(IS_CHROMEOS)
DataTypeSet UserSelectableOsTypesToDataTypes(
    UserSelectableOsTypeSet selected_types) {
  DataTypeSet preferred_types;
  for (UserSelectableOsType type : selected_types) {
    preferred_types.PutAll(UserSelectableOsTypeToAllDataTypes(type));
  }
  return preferred_types;
}
#endif  // BUILDFLAG(IS_CHROMEOS)

int GetCurrentMajorProductVersion() {
  DCHECK(version_info::GetVersion().IsValid());
  return version_info::GetVersion().components()[0];
}

}  // namespace

SyncUserSettingsImpl::SyncUserSettingsImpl(Delegate* delegate,
                                           SyncServiceCrypto* crypto,
                                           SyncPrefs* prefs,
                                           DataTypeSet registered_data_types)
    : delegate_(delegate),
      crypto_(crypto),
      prefs_(prefs),
      registered_data_types_(registered_data_types) {
  CHECK(delegate_);
  CHECK(crypto_);
  CHECK(prefs_);
  prefs_observation_.Observe(prefs_);
}

SyncUserSettingsImpl::~SyncUserSettingsImpl() = default;

bool SyncUserSettingsImpl::IsInitialSyncFeatureSetupComplete() const {
  return prefs_->IsInitialSyncFeatureSetupComplete();
}

#if !BUILDFLAG(IS_CHROMEOS)
void SyncUserSettingsImpl::SetInitialSyncFeatureSetupComplete() {
  if (IsInitialSyncFeatureSetupComplete()) {
    return;
  }
  prefs_->SetInitialSyncFeatureSetupComplete();
  delegate_->OnInitialSyncFeatureSetupCompleted();
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

bool SyncUserSettingsImpl::IsSyncEverythingEnabled() const {
  return prefs_->HasKeepEverythingSynced();
}

UserSelectableTypeSet SyncUserSettingsImpl::GetSelectedTypes() const {
  UserSelectableTypeSet types;

  switch (delegate_->GetSyncAccountStateForPrefs()) {
    case SyncPrefs::SyncAccountState::kNotSignedIn: {
      return UserSelectableTypeSet();
    }
    case SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent: {
      types = prefs_->GetSelectedTypesForAccount(
          delegate_->GetSyncAccountInfoForPrefs().gaia);
      break;
    }
    case SyncPrefs::SyncAccountState::kSyncing: {
      types = prefs_->GetSelectedTypesForSyncingUser();
      break;
    }
  }
  types.RetainAll(GetRegisteredSelectableTypes());

  return types;
}

bool SyncUserSettingsImpl::IsTypeManagedByPolicy(
    UserSelectableType type) const {
  return prefs_->IsTypeManagedByPolicy(type);
}

bool SyncUserSettingsImpl::IsTypeManagedByCustodian(
    UserSelectableType type) const {
  return prefs_->IsTypeManagedByCustodian(type);
}

SyncUserSettings::UserSelectableTypePrefState
SyncUserSettingsImpl::GetTypePrefStateForAccount(
    UserSelectableType type) const {
  if (delegate_->GetSyncAccountStateForPrefs() !=
      SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent) {
    return SyncUserSettings::UserSelectableTypePrefState::kNotApplicable;
  }
  if (prefs_->IsTypeDisabledByUserForAccount(
          type, delegate_->GetSyncAccountInfoForPrefs().gaia)) {
    return SyncUserSettings::UserSelectableTypePrefState::kDisabled;
  }
  return SyncUserSettings::UserSelectableTypePrefState::kEnabledOrDefault;
}

void SyncUserSettingsImpl::SetSelectedTypes(bool sync_everything,
                                            UserSelectableTypeSet types) {
  UserSelectableTypeSet registered_types = GetRegisteredSelectableTypes();
  DCHECK(registered_types.HasAll(types))
      << "\n registered: " << UserSelectableTypeSetToString(registered_types)
      << "\n setting to: " << UserSelectableTypeSetToString(types);

  switch (delegate_->GetSyncAccountStateForPrefs()) {
    case SyncPrefs::SyncAccountState::kNotSignedIn:
      NOTREACHED();
    case SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent:
      for (UserSelectableType type : registered_types) {
        SetSelectedType(type, types.Has(type) || sync_everything);
      }
      break;
    case SyncPrefs::SyncAccountState::kSyncing:
      prefs_->SetSelectedTypesForSyncingUser(sync_everything, registered_types,
                                             types);
      break;
  }
}

void SyncUserSettingsImpl::SetSelectedType(UserSelectableType type,
                                           bool is_type_on) {
  UserSelectableTypeSet registered_types = GetRegisteredSelectableTypes();
  CHECK(registered_types.Has(type));

  switch (delegate_->GetSyncAccountStateForPrefs()) {
    case SyncPrefs::SyncAccountState::kNotSignedIn:
      NOTREACHED();
    case SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent: {
      prefs_->SetSelectedTypeForAccount(
          type, is_type_on, delegate_->GetSyncAccountInfoForPrefs().gaia);
#if BUILDFLAG(IS_CHROMEOS)
      // TODO(crbug.com/524514663): This is for the possible migration of users
      // from kSignedInWithoutSyncConsent to kSyncing. Remove upon migrating all
      // users.
      if (IsReplaceSyncPromosWithSignInPromosEnabled()) {
        base::AutoReset<bool> auto_reset(&suppress_notifications_, true);
        prefs_->SetSelectedTypesForSyncingUser(
            /*keep_everything_synced=*/false, GetRegisteredSelectableTypes(),
            is_type_on ? base::Union(GetSelectedTypes(), {type})
                       : base::Difference(GetSelectedTypes(), {type}));
      }
#endif  // BUILDFLAG(IS_CHROMEOS)
      break;
    }
    case SyncPrefs::SyncAccountState::kSyncing: {
      DUMP_WILL_BE_CHECK(!IsSyncEverythingEnabled());
      syncer::UserSelectableTypeSet selected_types =
          is_type_on ? base::Union(GetSelectedTypes(), {type})
                     : base::Difference(GetSelectedTypes(), {type});
      SetSelectedTypes(IsSyncEverythingEnabled(), selected_types);
      break;
    }
  }
}

void SyncUserSettingsImpl::ResetSelectedType(UserSelectableType type) {
  CHECK_EQ(SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent,
           delegate_->GetSyncAccountStateForPrefs());
  prefs_->ResetSelectedTypeForAccount(
      type, delegate_->GetSyncAccountInfoForPrefs().gaia);
}

void SyncUserSettingsImpl::KeepAccountSettingsPrefsOnlyForUsers(
    const std::vector<GaiaId>& available_gaia_ids) {
  prefs_->KeepAccountSettingsPrefsOnlyForUsers(available_gaia_ids);
}

UserSelectableTypeSet SyncUserSettingsImpl::GetRegisteredSelectableTypes()
    const {
  UserSelectableTypeSet registered_types;
  for (UserSelectableType type : UserSelectableTypeSet::All()) {
    if (!base::Intersection(registered_data_types_,
                            UserSelectableTypeToAllDataTypes(type))
             .empty()) {
      registered_types.Put(type);
    }
  }
  return registered_types;
}

#if BUILDFLAG(IS_CHROMEOS)
void SyncUserSettingsImpl::SetSyncFeatureDisabledViaDashboard() {
  prefs_->SetSyncFeatureDisabledViaDashboard();
  if (delegate_->GetSyncAccountStateForPrefs() ==
          SyncPrefs::SyncAccountState::kSignedInWithoutSyncConsent &&
      IsReplaceSyncPromosWithSignInPromosEnabled()) {
    SetSelectedOsTypes(/*sync_all_os_types=*/false, UserSelectableOsTypeSet());
  }
}

void SyncUserSettingsImpl::ClearSyncFeatureDisabledViaDashboard() {
  if (!IsSyncFeatureDisabledViaDashboard()) {
    return;
  }
  prefs_->ClearSyncFeatureDisabledViaDashboard();
  delegate_->OnSyncFeatureDisabledViaDashboardCleared();
}

bool SyncUserSettingsImpl::IsSyncFeatureDisabledViaDashboard() const {
  return prefs_->IsSyncFeatureDisabledViaDashboard();
}

bool SyncUserSettingsImpl::IsSyncAllOsTypesEnabled() const {
  return prefs_->IsSyncAllOsTypesEnabled();
}

UserSelectableOsTypeSet SyncUserSettingsImpl::GetSelectedOsTypes() const {
  if (delegate_->GetSyncAccountStateForPrefs() ==
      SyncPrefs::SyncAccountState::kNotSignedIn) {
    return UserSelectableOsTypeSet();
  }

  UserSelectableOsTypeSet types = prefs_->GetSelectedOsTypes();
  types.RetainAll(GetRegisteredSelectableOsTypes());
  return types;
}

bool SyncUserSettingsImpl::IsOsTypeManagedByPolicy(
    UserSelectableOsType type) const {
  return prefs_->IsOsTypeManagedByPolicy(type);
}

void SyncUserSettingsImpl::SetSelectedOsTypes(bool sync_all_os_types,
                                              UserSelectableOsTypeSet types) {
  UserSelectableOsTypeSet registered_types = GetRegisteredSelectableOsTypes();
  DCHECK(registered_types.HasAll(types));
  prefs_->SetSelectedOsTypes(sync_all_os_types, registered_types, types);
}

UserSelectableOsTypeSet SyncUserSettingsImpl::GetRegisteredSelectableOsTypes()
    const {
  UserSelectableOsTypeSet registered_types;
  for (UserSelectableOsType type : UserSelectableOsTypeSet::All()) {
    if (!base::Intersection(registered_data_types_,
                            UserSelectableOsTypeToAllDataTypes(type))
             .empty()) {
      registered_types.Put(type);
    }
  }
  return registered_types;
}
#endif  // BUILDFLAG(IS_CHROMEOS)

bool SyncUserSettingsImpl::IsCustomPassphraseAllowed() const {
  return delegate_->IsCustomPassphraseAllowed();
}

bool SyncUserSettingsImpl::IsEncryptEverythingEnabled() const {
  return crypto_->IsEncryptEverythingEnabled();
}

bool SyncUserSettingsImpl::IsPassphraseRequired() const {
  return crypto_->IsPassphraseRequired();
}

bool SyncUserSettingsImpl::IsPassphraseRequiredForPreferredDataTypes() const {
  // If there is an encrypted datatype enabled and we don't have the proper
  // passphrase, we must prompt the user for a passphrase. The only way for the
  // user to avoid entering their passphrase is to disable the encrypted types.
  return IsEncryptedDatatypePreferred() && IsPassphraseRequired();
}

bool SyncUserSettingsImpl::IsPassphrasePromptMutedForCurrentProductVersion()
    const {
  return prefs_->GetPassphrasePromptMutedProductVersion() ==
         GetCurrentMajorProductVersion();
}

void SyncUserSettingsImpl::MarkPassphrasePromptMutedForCurrentProductVersion() {
  prefs_->SetPassphrasePromptMutedProductVersion(
      GetCurrentMajorProductVersion());
}

bool SyncUserSettingsImpl::IsTrustedVaultKeyRequired() const {
  return crypto_->IsTrustedVaultKeyRequired();
}

bool SyncUserSettingsImpl::IsKeystoreKeyRequiredForTesting() const {
  return crypto_->IsKeystoreKeyRequired();
}

bool SyncUserSettingsImpl::IsTrustedVaultKeyRequiredForPreferredDataTypes()
    const {
  return IsEncryptedDatatypePreferred() && crypto_->IsTrustedVaultKeyRequired();
}

bool SyncUserSettingsImpl::IsTrustedVaultRecoverabilityDegraded() const {
  return IsEncryptedDatatypePreferred() &&
         crypto_->IsTrustedVaultRecoverabilityDegraded();
}

bool SyncUserSettingsImpl::IsUsingExplicitPassphrase() const {
  // TODO(crbug.com/40923935): Either make this method return a Tribool, so the
  // "unknown" case is properly communicated, or just remove it altogether
  // (callers can always use the global IsExplicitPassphrase() helper).
  std::optional<PassphraseType> type = GetPassphraseType();
  if (!type.has_value()) {
    return false;
  }
  return IsExplicitPassphrase(*type);
}

base::Time SyncUserSettingsImpl::GetExplicitPassphraseTime() const {
  return crypto_->GetExplicitPassphraseTime();
}

std::optional<PassphraseType> SyncUserSettingsImpl::GetPassphraseType() const {
  return crypto_->GetPassphraseType();
}

void SyncUserSettingsImpl::SetEncryptionPassphrase(
    const std::string& passphrase) {
  crypto_->SetEncryptionPassphrase(passphrase);
}

bool SyncUserSettingsImpl::SetDecryptionPassphrase(
    const std::string& passphrase) {
  DCHECK(IsPassphraseRequired())
      << "SetDecryptionPassphrase must not be called when "
         "IsPassphraseRequired() is false.";

  DVLOG(1) << "Setting passphrase for decryption.";

  return crypto_->SetDecryptionPassphrase(passphrase);
}

DataTypeSet SyncUserSettingsImpl::GetPreferredDataTypes() const {
  DataTypeSet types = UserSelectableTypesToDataTypes(GetSelectedTypes());

#if BUILDFLAG(IS_CHROMEOS)
  if (IsSyncFeatureDisabledViaDashboard() &&
      (delegate_->GetSyncAccountStateForPrefs() ==
           SyncPrefs::SyncAccountState::kSyncing ||
       !IsReplaceSyncPromosWithSignInPromosEnabled())) {
    // If sync is disabled via dashboard, only a minimal set of datatypes should
    // sync. This prevents code changes from causing accidental behavioral
    // differences in this ChromeOS-specific edge case, as a side effect of
    // starting sync-the-transport.
    types.Clear();
  } else {
    types.PutAll(UserSelectableOsTypesToDataTypes(GetSelectedOsTypes()));
  }
#endif

  types.PutAll(AlwaysPreferredUserTypes());
  types.RetainAll(registered_data_types_);

  // Control types (in practice, NIGORI) are always considered "preferred", even
  // though they're technically not registered.
  types.PutAll(ControlTypes());

  if (prefs_->IsLocalSyncEnabled()) {
    types.RetainAll(LocalSyncSupportedTypes());
  }
  return types;
}

DataTypeSet SyncUserSettingsImpl::GetAllEncryptedDataTypes() const {
  return crypto_->GetAllEncryptedDataTypes();
}

bool SyncUserSettingsImpl::IsEncryptedDatatypePreferred() const {
  DataTypeSet preferred_types = GetPreferredDataTypes();
  const DataTypeSet encrypted_types = GetAllEncryptedDataTypes();
  DCHECK(encrypted_types.HasAll(AlwaysEncryptedUserTypes()));
  return !Intersection(preferred_types, encrypted_types).empty();
}

CustomPassphraseBootstrapToken
SyncUserSettingsImpl::GetEncryptionBootstrapToken(
    const os_crypt_async::Encryptor& encryptor) const {
  const GaiaId& gaia_id = delegate_->GetSyncAccountInfoForPrefs().gaia;
  if (gaia_id.empty()) {
    return CustomPassphraseBootstrapToken();
  }
  return prefs_->GetEncryptionBootstrapTokenForAccount(encryptor, gaia_id);
}

void SyncUserSettingsImpl::SetEncryptionBootstrapToken(
    const CustomPassphraseBootstrapToken& token,
    const os_crypt_async::Encryptor& encryptor) {
  const GaiaId& gaia_id = delegate_->GetSyncAccountInfoForPrefs().gaia;
  if (gaia_id.empty()) {
    // The user must be signed in, so the only legit scenario where SyncService
    // uses no Gaia ID is local sync (roaming profiles).
    CHECK(prefs_->IsLocalSyncEnabled());
    return;
  }
  prefs_->SetEncryptionBootstrapTokenForAccount(token, encryptor, gaia_id);
}

bool SyncUserSettingsImpl::IsSyncClientDisabledByPolicy() const {
  return prefs_->IsSyncClientDisabledByPolicy();
}

void SyncUserSettingsImpl::OnSyncManagedPrefChange(bool is_sync_managed) {
  delegate_->OnSyncClientDisabledByPolicyChanged();
}

void SyncUserSettingsImpl::OnSelectedTypesPrefChange() {
  if (suppress_notifications_) {
    return;
  }
  delegate_->OnSelectedTypesChanged();
}

}  // namespace syncer
