// 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/unified_consent/unified_consent_service.h"

#include "base/check_op.h"
#include "build/build_config.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/sync/base/features.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "components/unified_consent/pref_names.h"

namespace unified_consent {

// static
UnifiedConsentService::SyncState UnifiedConsentService::GetSyncState(
    const syncer::SyncService* sync_service) {
  CHECK(syncer::IsReplaceSyncPromosWithSignInPromosEnabled());

  if (sync_service->HasDisableReason(
          syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN)) {
    return SyncState::kSignedOut;
  }

  if (!sync_service->GetUserSettings()->GetSelectedTypes().Has(
          syncer::UserSelectableType::kHistory)) {
    return SyncState::kSignedInWithoutHistory;
  }

  std::optional<syncer::PassphraseType> passphrase_type =
      sync_service->GetUserSettings()->GetPassphraseType();

  if (!passphrase_type.has_value()) {
    return SyncState::kSignedInWithHistoryWaitingForPassphrase;
  }

  if (syncer::IsExplicitPassphrase(*passphrase_type)) {
    return SyncState::kSignedInWithHistoryAndExplicitPassphrase;
  }

  return SyncState::kSignedInWithHistoryAndNoPassphrase;
}

// static
bool UnifiedConsentService::ShouldEnableUrlKeyedAnonymizedDataCollection(
    SyncState old_sync_state,
    SyncState new_sync_state) {
  CHECK(syncer::IsReplaceSyncPromosWithSignInPromosEnabled());

  // If nothing changed, leave UrlKeyedAnonymizedDataCollection alone.
  if (old_sync_state == new_sync_state) {
    return false;
  }

  // UrlKeyedAnonymizedDataCollection is only ever automatically enabled when
  // entering the history-on-without-passphrase or -waiting-for-passphrase
  // state.
  switch (new_sync_state) {
    case SyncState::kSignedOut:
    case SyncState::kSignedInWithoutHistory:
    case SyncState::kSignedInWithHistoryAndExplicitPassphrase:
      return false;
    case SyncState::kSignedInWithHistoryWaitingForPassphrase:
    case SyncState::kSignedInWithHistoryAndNoPassphrase:
      // UrlKeyedAnonymizedDataCollection can maybe be enabled, depending on
      // `old_sync_state`.
      // Note that `kSignedInWithHistoryWaitingForPassphrase` will *usually*
      // be entered from `kSignedOut`, but it's not the only possibility: It's
      // also possible to get there from any of the "signed-in" state, e.g. if
      // the user reset their sync data via the dashboard.
      break;
  }

  switch (old_sync_state) {
    case SyncState::kSignedOut:
    case SyncState::kSignedInWithoutHistory:
      // History got turned on, and there's no explicit passphrase, so
      // UrlKeyedAnonymizedDataCollection can be enabled.
      return true;
    case SyncState::kSignedInWithHistoryWaitingForPassphrase:
    case SyncState::kSignedInWithHistoryAndNoPassphrase:
      // UrlKeyedAnonymizedDataCollection would've been turned on based on the
      // old state already, so nothing to do here.
      return false;
    case SyncState::kSignedInWithHistoryAndExplicitPassphrase:
      // Explicit-passphrase was turned off, and history is on,
      // UrlKeyedAnonymizedDataCollection can be enabled.
      // Note that while turning off an explicit passphrase involves resetting
      // all the server-side data and starting over fresh, this process is not
      // expressed in the SyncState.
      return true;
  }
}

// static
bool UnifiedConsentService::ShouldDisableUrlKeyedAnonymizedDataCollection(
    SyncState old_sync_state,
    SyncState new_sync_state) {
  CHECK(syncer::IsReplaceSyncPromosWithSignInPromosEnabled());

  // If nothing changed, leave UrlKeyedAnonymizedDataCollection alone.
  if (old_sync_state == new_sync_state) {
    return false;
  }

  // UrlKeyedAnonymizedDataCollection only ever needs to be automatically
  // disabled if it was previously automatically enabled, which means the old
  // state was history-on-without-passphrase (or -waiting-for-passphrase) state.
  switch (old_sync_state) {
    case SyncState::kSignedOut:
    case SyncState::kSignedInWithoutHistory:
    case SyncState::kSignedInWithHistoryAndExplicitPassphrase:
      return false;
    case SyncState::kSignedInWithHistoryWaitingForPassphrase:
    case SyncState::kSignedInWithHistoryAndNoPassphrase:
      // UrlKeyedAnonymizedDataCollection might have to be disabled, depending
      // on `new_sync_state`.
      break;
  }

  switch (new_sync_state) {
    case SyncState::kSignedOut:
      // User signed out; UrlKeyedAnonymizedDataCollection should be disabled.
      return true;
    case SyncState::kSignedInWithoutHistory:
      // History was turned off, UrlKeyedAnonymizedDataCollection should be
      // disabled.
      return true;
    case SyncState::kSignedInWithHistoryAndNoPassphrase:
      // Passphrase state became known, and there's no explicit passphrase.
      // Nothing to be done.
      return false;
    case SyncState::kSignedInWithHistoryWaitingForPassphrase:
      // Nothing to do - wait until the passphrase state becomes known.
      // Note that this transition (from `kSignedInWithHistoryAndNoPassphrase`
      // to `kSignedInWithHistoryWaitingForPassphrase`) is not typically
      // possible, but it can happen if the local sync (meta)data gets reset and
      // sync starts over.
      return false;
    case SyncState::kSignedInWithHistoryAndExplicitPassphrase:
      // If explicit-passphrase was turned on, UrlKeyedAnonymizedDataCollection
      // should be disabled.
      return true;
  }
}

UnifiedConsentService::UnifiedConsentService(
    sync_preferences::PrefServiceSyncable* pref_service,
    signin::IdentityManager* identity_manager,
    syncer::SyncService* sync_service,
    const std::vector<std::string>& service_pref_names
#if BUILDFLAG(IS_CHROMEOS)
    ,
    bool is_new_profile
#endif
    )
    : pref_service_(pref_service),
      identity_manager_(identity_manager),
      sync_service_(sync_service),
      service_pref_names_(service_pref_names) {
  DCHECK(pref_service_);
  DCHECK(identity_manager_);
  DCHECK(sync_service_);

#if BUILDFLAG(IS_CHROMEOS)
  if (GetMigrationState() == MigrationState::kNotInitialized)
    MigrateProfileToUnifiedConsent();
#endif

  if (syncer::IsReplaceSyncPromosWithSignInPromosEnabled()) {
    last_sync_state_ = GetSyncState(sync_service_);
#if BUILDFLAG(IS_CHROMEOS)
    // On ChromeOS, the user is signed in before this service is constructed.
    // If this is a new profile, set the initial value of MSBB by simulating a
    // transition from signed-out to the current sync state.
    if (!sync_service_->HasSyncConsent() && is_new_profile) {
      SetUrlKeyedAnonymizedDataCollectionEnabled(
          ShouldEnableUrlKeyedAnonymizedDataCollection(SyncState::kSignedOut,
                                                       last_sync_state_));
    }
#endif
  }

  pref_service_->AddObserver(this);
  identity_manager_->AddObserver(this);
  sync_service_->AddObserver(this);
}

UnifiedConsentService::~UnifiedConsentService() = default;

// static
void UnifiedConsentService::RegisterPrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterBooleanPref(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
                                false);
#if BUILDFLAG(IS_CHROMEOS)
  registry->RegisterIntegerPref(
      prefs::kUnifiedConsentMigrationState,
      static_cast<int>(MigrationState::kNotInitialized));
#endif
}

void UnifiedConsentService::SetUrlKeyedAnonymizedDataCollectionEnabled(
    bool enabled) {
#if BUILDFLAG(IS_CHROMEOS)
  if (GetMigrationState() != MigrationState::kCompleted)
    SetMigrationState(MigrationState::kCompleted);
#endif

  pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
                            enabled);
}

void UnifiedConsentService::Shutdown() {
  pref_service_->RemoveObserver(this);
  identity_manager_->RemoveObserver(this);
  sync_service_->RemoveObserver(this);
}

void UnifiedConsentService::OnPrimaryAccountChanged(
    const signin::PrimaryAccountChangeEvent& event) {
  // TODO(crbug.com/40066949): Simplify once kSync becomes unreachable or is
  // deleted from the codebase. See ConsentLevel::kSync documentation for
  // details.
  if (event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
      signin::PrimaryAccountChangeEvent::Type::kCleared) {
    // By design, clearing the primary account disables URL-keyed data
    // collection.
    SetUrlKeyedAnonymizedDataCollectionEnabled(false);
  }
}

void UnifiedConsentService::OnStateChanged(syncer::SyncService* sync) {
  // Update the UrlKeyedAnonymizedDataCollectionEnabled if user changed the
  // history opt-in state or the explicit-passphrase state.
  if (syncer::IsReplaceSyncPromosWithSignInPromosEnabled()) {
    const SyncState new_sync_state = GetSyncState(sync_service_);

    // Before updating the cached state, remember the old value, to detect
    // if anything changed.
    const SyncState old_sync_state = last_sync_state_;
    last_sync_state_ = new_sync_state;

    // Ignore syncing users - UrlKeyedAnonymizedDataCollectionEnabled is
    // updated based on their sync opt-in state.
    if (!sync_service_->HasSyncConsent()) {
      if (ShouldDisableUrlKeyedAnonymizedDataCollection(old_sync_state,
                                                        new_sync_state)) {
        SetUrlKeyedAnonymizedDataCollectionEnabled(false);
      } else if (ShouldEnableUrlKeyedAnonymizedDataCollection(old_sync_state,
                                                              new_sync_state)) {
        SetUrlKeyedAnonymizedDataCollectionEnabled(true);
      }
    }
  }

  // Start observing pref changes when the user enters sync setup.
  // Note: Only |sync->IsSetupInProgress()| is used (i.e. no check for
  // |IsInitialSyncFeatureSetupComplete()|), because on Android
  // |SetInitialSyncFeatureSetupComplete()| is called automatically during the
  // first setup, i.e. the value could change in the meantime.
  // TODO(crbug.com/40067025): Simplify (remove the following block) once
  // kReplaceSyncPromosWithSigninPromos is rolled out on all platforms, and thus
  // IsSetupInProgress() always returns false. See ConsentLevel::kSync
  // documentation for details.
  if (sync->IsSetupInProgress() && !pref_service_->IsSyncing()) {
    StartObservingServicePrefChanges();
  } else {
    StopObservingServicePrefChanges();

    // If the user cancelled the sync setup, clear all observed changes.
    if (!sync->CanSyncFeatureStart())
      service_pref_changes_.clear();
  }

#if BUILDFLAG(IS_CHROMEOS)
  // TODO(crbug.com/40066949): Simplify (remove the following block) after
  // Sync-the-feature users are migrated to ConsentLevel::kSignin (and thus
  // CanSyncFeatureStart() always returns false).
  if (!sync_service_->CanSyncFeatureStart() ||
      !sync_service_->IsEngineInitialized()) {
    return;
  }

  if (GetMigrationState() == MigrationState::kInProgressWaitForSyncInit)
    UpdateSettingsForMigration();
#endif
}

void UnifiedConsentService::OnSyncShutdown(syncer::SyncService*) {
  // Unreachable, since this service is Shutdown() before the SyncService.
  NOTREACHED();
}

void UnifiedConsentService::OnIsSyncingChanged() {
  if (pref_service_->IsSyncing() && !service_pref_changes_.empty()) {
    // Re-apply all observed service pref changes.
    // If any service prefs had a value coming in through Sync, then that
    // would've overridden any changes that the user made during the first sync
    // setup. So re-apply the local changes to make sure they stick.
    for (const auto& pref_change : service_pref_changes_) {
      pref_service_->Set(pref_change.first, pref_change.second);
    }
    service_pref_changes_.clear();
  }
}

void UnifiedConsentService::StartObservingServicePrefChanges() {
  if (!service_pref_change_registrar_.IsEmpty())
    return;

  service_pref_change_registrar_.Init(pref_service_);
  for (const std::string& pref_name : service_pref_names_) {
    service_pref_change_registrar_.Add(
        pref_name,
        base::BindRepeating(&UnifiedConsentService::ServicePrefChanged,
                            base::Unretained(this)));
  }
}

void UnifiedConsentService::StopObservingServicePrefChanges() {
  service_pref_change_registrar_.RemoveAll();
}

void UnifiedConsentService::ServicePrefChanged(const std::string& name) {
  DCHECK(sync_service_->IsSetupInProgress());
  const base::Value& value = pref_service_->GetValue(name);
  service_pref_changes_[name] = value.Clone();
}

#if BUILDFLAG(IS_CHROMEOS)
MigrationState UnifiedConsentService::GetMigrationState() {
  int migration_state_int =
      pref_service_->GetInteger(prefs::kUnifiedConsentMigrationState);
  DCHECK_LE(static_cast<int>(MigrationState::kNotInitialized),
            migration_state_int);
  DCHECK_GE(static_cast<int>(MigrationState::kCompleted), migration_state_int);
  return static_cast<MigrationState>(migration_state_int);
}

void UnifiedConsentService::SetMigrationState(MigrationState migration_state) {
  pref_service_->SetInteger(prefs::kUnifiedConsentMigrationState,
                            static_cast<int>(migration_state));
}

void UnifiedConsentService::MigrateProfileToUnifiedConsent() {
  DCHECK_EQ(GetMigrationState(), MigrationState::kNotInitialized);

  // TODO(crbug.com/40066949): Simplify once kSync becomes unreachable or is
  // deleted from the codebase. See ConsentLevel::kSync documentation for
  // details.
  if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
    SetMigrationState(MigrationState::kCompleted);
    return;
  }

  UpdateSettingsForMigration();
}

void UnifiedConsentService::UpdateSettingsForMigration() {
  if (!sync_service_->IsEngineInitialized()) {
    SetMigrationState(MigrationState::kInProgressWaitForSyncInit);
    return;
  }

  // Set URL-keyed anonymized metrics to the state it had before unified
  // consent.
  // TODO(crbug.com/40066949): Simplify (remove the following block) after
  // Sync-the-feature users are migrated to ConsentLevel::kSignin, and thus
  // IsSyncFeatureEnabled() always returns false. (The UKM state for kSignin
  // users is set in OnStateChanged(), so no need for the logic here.)
  bool url_keyed_metrics_enabled =
      sync_service_->IsSyncFeatureEnabled() &&
      sync_service_->GetUserSettings()->GetSelectedTypes().Has(
          syncer::UserSelectableType::kHistory) &&
      !sync_service_->GetUserSettings()->IsUsingExplicitPassphrase();
  SetUrlKeyedAnonymizedDataCollectionEnabled(url_keyed_metrics_enabled);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  //  namespace unified_consent
