// 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/test/test_sync_service.h"

#include <utility>
#include <vector>

#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/sync/base/features.h"
#include "components/sync/base/progress_marker_map.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/engine/cycle/model_neutral_state.h"
#include "components/sync/protocol/sync_enums.pb.h"
#include "components/sync/service/sync_error.h"
#include "components/sync/service/sync_token_status.h"
#include "google_apis/gaia/gaia_id.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"

namespace syncer {

namespace {

SyncCycleSnapshot MakeDefaultCycleSnapshot() {
  return SyncCycleSnapshot(
      /*birthday=*/"", /*bag_of_chips=*/"", ModelNeutralState(),
      ProgressMarkerMap(), /*is_silenced=*/false,
      /*num_server_conflicts=*/7, /*notifications_enabled=*/false,
      /*sync_start_time=*/base::Time::Now(),
      /*poll_finish_time=*/base::Time::Now(),
      /*get_updates_origin=*/sync_pb::SyncEnums::UNKNOWN_ORIGIN,
      /*poll_interval=*/base::Minutes(30),
      /*has_remaining_local_changes=*/false);
}

CoreAccountInfo GetDefaultAccountInfo() {
  CoreAccountInfo account;
  account.email = "foo@bar.com";
  account.gaia = GaiaId("foo-gaia-id");
  account.account_id = CoreAccountId::FromGaiaId(account.gaia);
  return account;
}

}  // namespace

TestSyncService::TestSyncService()
    : user_settings_(this), last_cycle_snapshot_(MakeDefaultCycleSnapshot()) {
  SetSignedIn(IsReplaceSyncPromosWithSignInPromosEnabled()
                  ? signin::ConsentLevel::kSignin
                  : signin::ConsentLevel::kSync);
}

TestSyncService::~TestSyncService() = default;

void TestSyncService::SetSignedIn(signin::ConsentLevel consent_level) {
  SetSignedIn(consent_level, GetDefaultAccountInfo());
}

void TestSyncService::SetSignedIn(signin::ConsentLevel consent_level,
                                  const CoreAccountInfo& account_info) {
  CHECK(!local_sync_enabled_)
      << "Cannot set signed in while local sync is enabled.";

  disable_reasons_.Remove(DISABLE_REASON_NOT_SIGNED_IN);
  account_info_ = account_info;
  if (consent_level == signin::ConsentLevel::kSync) {
    has_sync_consent_ = true;
    user_settings_.SetInitialSyncFeatureSetupComplete();
  } else {
    has_sync_consent_ = false;
    user_settings_.ClearInitialSyncFeatureSetupComplete();
  }
}

void TestSyncService::SetSignedOut() {
  has_sync_consent_ = false;
  user_settings_.ClearInitialSyncFeatureSetupComplete();
  account_info_ = CoreAccountInfo();
  has_persistent_auth_error_ = false;
  disable_reasons_.Put(DISABLE_REASON_NOT_SIGNED_IN);
  CHECK_EQ(GetTransportState(), TransportState::DISABLED);
}

void TestSyncService::MimicDashboardClear() {
#if BUILDFLAG(IS_CHROMEOS)
  // Clearing sync from the dashboard results in
  // IsSyncFeatureDisabledViaDashboard() returning true.
  user_settings_.SetSyncFeatureDisabledViaDashboard();
#else
  SetSignedIn(signin::ConsentLevel::kSignin);
#endif  // BUILDFLAG(IS_CHROMEOS)
}

void TestSyncService::SetAllowedByEnterprisePolicy(bool allowed) {
  disable_reasons_.PutOrRemove(DISABLE_REASON_ENTERPRISE_POLICY, !allowed);
}

void TestSyncService::SetHasUnrecoverableError(bool has_error) {
  disable_reasons_.PutOrRemove(DISABLE_REASON_UNRECOVERABLE_ERROR, has_error);
}

void TestSyncService::SetMaxTransportState(TransportState max_transport_state) {
  CHECK_NE(max_transport_state, TransportState::DISABLED)
      << "DISABLED should be set via one of SetSignedOut(), "
         "SetAllowedByEnterprisePolicy(false) or "
         "SetHasUnrecoverableError(true)";
  CHECK_NE(max_transport_state, TransportState::PAUSED)
      << "PAUSED should be set via SetPersistentAuthError()";
  max_transport_state_ = max_transport_state;
}

void TestSyncService::SetLocalSyncEnabled(bool local_sync_enabled) {
  if (local_sync_enabled == local_sync_enabled_) {
    return;
  }

  local_sync_enabled_ = local_sync_enabled;
  if (local_sync_enabled_) {
    SetSignedOut();
    disable_reasons_.Remove(DISABLE_REASON_NOT_SIGNED_IN);
    disable_reasons_.Remove(DISABLE_REASON_ENTERPRISE_POLICY);
  } else {
    SetSignedIn(IsReplaceSyncPromosWithSignInPromosEnabled()
                    ? signin::ConsentLevel::kSignin
                    : signin::ConsentLevel::kSync);
  }
}

void TestSyncService::SetPersistentAuthError() {
  CHECK(!account_info_.IsEmpty()) << "Attempting to set persistent auth error "
                                     "when there is no signed-in account";
  has_persistent_auth_error_ = true;
}

void TestSyncService::ClearAuthError() {
  has_persistent_auth_error_ = false;
}

void TestSyncService::SetInitialSyncFeatureSetupComplete(
    bool initial_sync_feature_setup_complete) {
  if (initial_sync_feature_setup_complete) {
    user_settings_.SetInitialSyncFeatureSetupComplete();
  } else {
    user_settings_.ClearInitialSyncFeatureSetupComplete();
  }
}

void TestSyncService::SetFailedDataTypes(const DataTypeSet& types) {
  failed_data_types_ = types;
}

void TestSyncService::SetBookmarksLimitExceeded(bool exceeded) {
  bookmarks_limit_exceeded_ = exceeded;
}

void TestSyncService::SetLastCycleSnapshot(const SyncCycleSnapshot& snapshot) {
  last_cycle_snapshot_ = snapshot;
}

void TestSyncService::SetEmptyLastCycleSnapshot() {
  SetLastCycleSnapshot(SyncCycleSnapshot());
}

void TestSyncService::SetNonEmptyLastCycleSnapshot() {
  SetLastCycleSnapshot(MakeDefaultCycleSnapshot());
}

void TestSyncService::SetDetailedSyncStatus(bool engine_available,
                                            SyncStatus status) {
  detailed_sync_status_engine_available_ = engine_available;
  detailed_sync_status_ = status;
}

void TestSyncService::SetPassphraseRequired() {
  user_settings_.SetPassphraseRequired();
}

void TestSyncService::SetTrustedVaultKeyRequired(bool required) {
  user_settings_.SetTrustedVaultKeyRequired(required);
}

void TestSyncService::SetTrustedVaultRecoverabilityDegraded(bool degraded) {
  user_settings_.SetTrustedVaultRecoverabilityDegraded(degraded);
}

void TestSyncService::SetIsUsingExplicitPassphrase(bool enabled) {
  user_settings_.SetIsUsingExplicitPassphrase(enabled);
}

void TestSyncService::SetDownloadStatusFor(
    const DataTypeSet& types,
    DataTypeDownloadStatus download_status) {
  for (const auto type : types) {
    download_statuses_[type] = download_status;
  }
}

void TestSyncService::SetSetupInProgress() {
  outstanding_setup_in_progress_handles_++;
}

void TestSyncService::FireStateChanged() {
  for (SyncServiceObserver& observer : observers_) {
    observer.OnStateChanged(this);
  }
}

void TestSyncService::FireSyncCycleCompleted() {
  for (SyncServiceObserver& observer : observers_) {
    observer.OnSyncCycleCompleted(this);
  }
}

#if BUILDFLAG(IS_ANDROID)
base::android::ScopedJavaLocalRef<jobject> TestSyncService::GetJavaObject() {
  return base::android::ScopedJavaLocalRef<jobject>();
}
#endif  // BUILDFLAG(IS_ANDROID)

TestSyncUserSettings* TestSyncService::GetUserSettings() {
  return &user_settings_;
}

const TestSyncUserSettings* TestSyncService::GetUserSettings() const {
  return &user_settings_;
}

SyncService::DisableReasonSet TestSyncService::GetDisableReasons() const {
  return disable_reasons_;
}

SyncService::TransportState TestSyncService::GetTransportState() const {
  if (!disable_reasons_.empty()) {
    return TransportState::DISABLED;
  }

  if (has_persistent_auth_error_) {
    CHECK(!account_info_.IsEmpty())
        << "Detected persistent auth error when there is no signed-in account";
    return TransportState::PAUSED;
  }

  return max_transport_state_;
}

SyncService::UserActionableError TestSyncService::GetUserActionableError()
    const {
#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)
  if (HasSyncConsent()) {
    if (!user_settings_.IsInitialSyncFeatureSetupComplete()) {
      return UserActionableError::kNeedsSettingsConfirmation;
    }
    // RequiresClientUpgrade() is unrecoverable, but is treated separately
    // below.
    if (HasUnrecoverableError() &&
        detailed_sync_status_.sync_protocol_error.action !=
            syncer::UPGRADE_CLIENT) {
      return UserActionableError::kUnrecoverableError;
    }
  }
#endif  // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID)

  if (GetAuthError().state() != GoogleServiceAuthError::NONE) {
    return UserActionableError::kSignInNeedsUpdate;
  }
  if (detailed_sync_status_.sync_protocol_error.action ==
      syncer::UPGRADE_CLIENT) {
    return UserActionableError::kNeedsClientUpgrade;
  }
  if (user_settings_.IsPassphraseRequiredForPreferredDataTypes()) {
    return UserActionableError::kNeedsPassphrase;
  }
  if (user_settings_.IsTrustedVaultKeyRequiredForPreferredDataTypes()) {
    return user_settings_.IsEncryptEverythingEnabled()
               ? UserActionableError::kNeedsTrustedVaultKeyForEverything
               : UserActionableError::kNeedsTrustedVaultKeyForPasswords;
  }
  if (user_settings_.IsTrustedVaultRecoverabilityDegraded()) {
    return user_settings_.IsEncryptEverythingEnabled()
               ? UserActionableError::
                     kTrustedVaultRecoverabilityDegradedForEverything
               : UserActionableError::
                     kTrustedVaultRecoverabilityDegradedForPasswords;
  }

  if (bookmarks_limit_exceeded_) {
    return UserActionableError::kBookmarksLimitExceeded;
  }

  return UserActionableError::kNone;
}

bool TestSyncService::IsLocalSyncEnabled() const {
  return local_sync_enabled_;
}

CoreAccountInfo TestSyncService::GetAccountInfo() const {
  return account_info_;
}

bool TestSyncService::HasSyncConsent() const {
  return has_sync_consent_;
}

GoogleServiceAuthError TestSyncService::GetAuthError() const {
  return has_persistent_auth_error_
             ? GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
                   GoogleServiceAuthError::InvalidGaiaCredentialsReason::
                       CREDENTIALS_REJECTED_BY_SERVER)
             : GoogleServiceAuthError::AuthErrorNone();
}

bool TestSyncService::HasCachedPersistentAuthErrorForMetrics() const {
  return GetTransportState() == TransportState::PAUSED;
}

base::Time TestSyncService::GetAuthErrorTime() const {
  return base::Time();
}

std::unique_ptr<SyncSetupInProgressHandle>
TestSyncService::GetSetupInProgressHandle() {
  outstanding_setup_in_progress_handles_++;
  return std::make_unique<SyncSetupInProgressHandle>(
      base::BindRepeating(&TestSyncService::OnSetupInProgressHandleDestroyed,
                          weak_factory_.GetWeakPtr()));
}

bool TestSyncService::IsSetupInProgress() const {
  return outstanding_setup_in_progress_handles_ > 0;
}

DataTypeSet TestSyncService::GetPreferredDataTypes() const {
  return user_settings_.GetPreferredDataTypes();
}

DataTypeSet TestSyncService::GetDataTypesForTransportOnlyMode() const {
  return DataTypeSet::All();
}

DataTypeSet TestSyncService::GetActiveDataTypes() const {
  if (GetTransportState() != TransportState::ACTIVE) {
    return DataTypeSet();
  }
#if BUILDFLAG(IS_CHROMEOS)
  if (user_settings_.IsSyncFeatureDisabledViaDashboard()) {
    return DataTypeSet();
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  DataTypeSet types_with_encryption_error =
      (user_settings_.IsPassphraseRequired() ||
       user_settings_.IsTrustedVaultKeyRequired())
          ? user_settings_.GetAllEncryptedDataTypes()
          : DataTypeSet();
  return Difference(GetPreferredDataTypes(),
                    Union(failed_data_types_, types_with_encryption_error));
}

DataTypeSet TestSyncService::GetTypesWithPendingDownloadForInitialSync() const {
  DCHECK_NE(GetTransportState(), TransportState::INITIALIZING)
      << "Realistic behavior not implemented for INITIALIZING";
  if (GetTransportState() != TransportState::CONFIGURING) {
    return DataTypeSet();
  }
  return Difference(GetPreferredDataTypes(), failed_data_types_);
}

void TestSyncService::OnDataTypeRequestsSyncStartup(DataType type) {}

void TestSyncService::TriggerRefresh(TriggerRefreshSource source,
                                     const DataTypeSet& types) {
  if (trigger_refresh_cb_) {
    trigger_refresh_cb_.Run(source, types);
  }
}

void TestSyncService::DataTypePreconditionChanged(DataType type) {}

base::flat_set<std::string>
TestSyncService::GetCurrentDeviceCacheGuidsForAllGaiaIds() const {
  return current_device_cache_guids_for_all_gaia_ids_;
}

void TestSyncService::SetCurrentDeviceCacheGuidsForAllGaiaIds(
    base::flat_set<std::string> guids) {
  current_device_cache_guids_for_all_gaia_ids_ = std::move(guids);
}

void TestSyncService::AddObserver(SyncServiceObserver* observer) {
  observers_.AddObserver(observer);
}

void TestSyncService::RemoveObserver(SyncServiceObserver* observer) {
  observers_.RemoveObserver(observer);
}

bool TestSyncService::HasObserver(const SyncServiceObserver* observer) const {
  return observers_.HasObserver(observer);
}

SyncTokenStatus TestSyncService::GetSyncTokenStatusForDebugging() const {
  return SyncTokenStatus();
}

bool TestSyncService::QueryDetailedSyncStatusForDebugging(
    SyncStatus* result) const {
  *result = detailed_sync_status_;
  return detailed_sync_status_engine_available_;
}

base::Time TestSyncService::GetLastSyncedTimeForDebugging() const {
  return base::Time();
}

SyncCycleSnapshot TestSyncService::GetLastCycleSnapshotForDebugging() const {
  return last_cycle_snapshot_;
}

TypeStatusMapForDebugging TestSyncService::GetTypeStatusMapForDebugging()
    const {
  return TypeStatusMapForDebugging();
}

void TestSyncService::GetEntityCountsForDebugging(
    base::RepeatingCallback<void(const TypeEntitiesCount&)> callback) const {}

const GURL& TestSyncService::GetSyncServiceUrlForDebugging() const {
  return sync_service_url_;
}

std::string TestSyncService::GetUnrecoverableErrorMessageForDebugging() const {
  return std::string();
}

base::Location TestSyncService::GetUnrecoverableErrorLocationForDebugging()
    const {
  return base::Location();
}

void TestSyncService::AddProtocolEventObserver(
    ProtocolEventObserver* observer) {}

void TestSyncService::RemoveProtocolEventObserver(
    ProtocolEventObserver* observer) {}

void TestSyncService::GetAllNodesForDebugging(
    base::OnceCallback<void(base::ListValue)> callback) {}

SyncService::DataTypeDownloadStatus TestSyncService::GetDownloadStatusFor(
    DataType type) const {
  if (download_statuses_.contains(type)) {
    return download_statuses_.at(type);
  }
  return DataTypeDownloadStatus::kUpToDate;
}

void TestSyncService::SetInvalidationsForSessionsEnabled(bool enabled) {}

void TestSyncService::SendExplicitPassphraseToPlatformClient() {
  if (send_passphrase_to_platform_client_cb_) {
    send_passphrase_to_platform_client_cb_.Run();
  }
}

void TestSyncService::Shutdown() {
  for (SyncServiceObserver& observer : observers_) {
    observer.OnSyncShutdown(this);
  }
}

void TestSyncService::SetTypesWithUnsyncedData(const DataTypeSet& types) {
  unsynced_types_ = types;
}

void TestSyncService::GetTypesWithUnsyncedData(
    DataTypeSet requested_types,
    base::OnceCallback<void(absl::flat_hash_map<DataType, size_t>)> cb) const {
  absl::flat_hash_map<DataType, size_t> unsynced_data_counts;
  for (auto type : base::Intersection(requested_types, unsynced_types_)) {
    unsynced_data_counts[type] = 1;
  }
  std::move(cb).Run(std::move(unsynced_data_counts));
}

void TestSyncService::SetLocalDataDescriptions(
    const std::map<DataType, LocalDataDescription>& local_data_descriptions) {
  local_data_descriptions_ = local_data_descriptions;
}

void TestSyncService::SetPassphrasePlatformClientCallback(
    const base::RepeatingClosure& send_passphrase_to_platform_client_cb) {
  send_passphrase_to_platform_client_cb_ =
      send_passphrase_to_platform_client_cb;
}

void TestSyncService::GetLocalDataDescriptions(
    DataTypeSet types,
    base::OnceCallback<void(std::map<DataType, LocalDataDescription>)>
        callback) {
  std::map<DataType, LocalDataDescription> result;
  for (DataType type : types) {
    if (auto it = local_data_descriptions_.find(type);
        it != local_data_descriptions_.end()) {
      result.insert(*it);
    }
  }
  std::move(callback).Run(std::move(result));
}

void TestSyncService::TriggerLocalDataMigration(DataTypeSet types) {}

void TestSyncService::TriggerLocalDataMigrationForItems(
    std::map<DataType, std::vector<LocalDataItemModel::DataId>> items) {}

void TestSyncService::SelectTypeAndMigrateLocalDataItemsWhenActive(
    DataType data_type,
    std::vector<LocalDataItemModel::DataId> items) {
  // Using `SyncUserSettings::ResetSelectedType()` to be aligned with the
  // implementation in
  // `SyncServiceImpl::SelectTypeAndMigrateLocalDataItemsWhenActive()`.
  GetUserSettings()->ResetSelectedType(
      GetUserSelectableTypeFromDataType(data_type).value());

  if (auto it = local_data_descriptions_.find(data_type);
      it != local_data_descriptions_.end()) {
    const absl::flat_hash_set<LocalDataItemModel::DataId> items_to_remove(
        items.begin(), items.end());
    std::erase_if(it->second.local_data_models,
                  [&items_to_remove](const LocalDataItemModel& model) {
                    return items_to_remove.contains(model.id);
                  });
  }
}

void TestSyncService::AcknowledgeBookmarksLimitExceededError(
    BookmarksLimitExceededHelpClickedSource source) {
  bookmarks_limit_exceeded_ = false;
}

void TestSyncService::SetTriggerRefreshCallback(
    const base::RepeatingCallback<
        void(TriggerRefreshSource, const DataTypeSet&)>& trigger_refresh_cb) {
  trigger_refresh_cb_ = trigger_refresh_cb;
}

void TestSyncService::OnSetupInProgressHandleDestroyed() {
  outstanding_setup_in_progress_handles_--;
}

}  // namespace syncer
