// Copyright 2012 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/engine/sync_manager_impl.h"

#include <stddef.h>

#include <utility>

#include "base/compiler_specific.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_macros.h"
#include "base/observer_list.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/sync_invalidation.h"
#include "components/sync/engine/cancelation_signal.h"
#include "components/sync/engine/configure_reason.h"
#include "components/sync/engine/data_type_connector_proxy.h"
#include "components/sync/engine/data_type_worker.h"
#include "components/sync/engine/engine_components_factory.h"
#include "components/sync/engine/loopback_server/loopback_connection_manager.h"
#include "components/sync/engine/net/http_post_provider_factory.h"
#include "components/sync/engine/net/sync_server_connection_manager.h"
#include "components/sync/engine/net/url_translator.h"
#include "components/sync/engine/nigori/cryptographer.h"
#include "components/sync/engine/nigori/key_derivation_params.h"
#include "components/sync/engine/nigori/keystore_keys_handler.h"
#include "components/sync/engine/polling_constants.h"
#include "components/sync/engine/sync_scheduler.h"
#include "components/sync/engine/update_handler.h"
#include "components/sync/protocol/sync_enums.pb.h"

namespace syncer {
namespace {

sync_pb::SyncEnums::GetUpdatesOrigin GetOriginFromReason(
    ConfigureReason reason) {
  switch (reason) {
    case ConfigureReason::kReconfiguration:
      return sync_pb::SyncEnums::RECONFIGURATION;
    case ConfigureReason::kMigration:
      return sync_pb::SyncEnums::MIGRATION;
    case ConfigureReason::kNewClient:
      return sync_pb::SyncEnums::NEW_CLIENT;
    case ConfigureReason::kExistingClientRestart:
    case ConfigureReason::kCrypto:
      // Mapping these cases to NEWLY_SUPPORTED_DATATYPE is rather wrong, as it
      // includes common cases like sync being unpaused or a crypto error having
      // been resolved, if initial sync didn't complete earlier (or data was
      // cleared while paused). The legacy behavior is kept until a better
      // solution is found.
      return sync_pb::SyncEnums::NEWLY_SUPPORTED_DATATYPE;
    case ConfigureReason::kProgrammatic:
      return sync_pb::SyncEnums::PROGRAMMATIC;
    case ConfigureReason::kUnknown:
      NOTREACHED();
  }
  return sync_pb::SyncEnums::UNKNOWN_ORIGIN;
}

constexpr char kSyncServerSyncPath[] = "/command/";

std::string StripTrailingSlash(const std::string& s) {
  int stripped_end_pos = s.size();
  if (s.at(stripped_end_pos - 1) == '/') {
    stripped_end_pos = stripped_end_pos - 1;
  }

  return s.substr(0, stripped_end_pos);
}

GURL MakeConnectionURL(const GURL& sync_server, const std::string& client_id) {
  DCHECK_EQ(kSyncServerSyncPath[0], '/');
  std::string full_path =
      StripTrailingSlash(sync_server.GetPath()) + kSyncServerSyncPath;

  GURL::Replacements path_replacement;
  path_replacement.SetPathStr(full_path);
  return AppendSyncQueryString(sync_server.ReplaceComponents(path_replacement),
                               client_id);
}

}  // namespace

SyncManagerImpl::SyncManagerImpl(
    const std::string& name,
    network::NetworkConnectionTracker* network_connection_tracker)
    : name_(name), network_connection_tracker_(network_connection_tracker) {}

SyncManagerImpl::~SyncManagerImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!initialized_);
}

DataTypeSet SyncManagerImpl::InitialSyncEndedTypes() {
  DCHECK(initialized_);
  return data_type_registry_->GetInitialSyncEndedTypes();
}

DataTypeSet SyncManagerImpl::GetConnectedTypes() {
  DCHECK(initialized_);
  return data_type_registry_->GetConnectedTypes();
}

void SyncManagerImpl::ConfigureSyncer(ConfigureReason reason,
                                      DataTypeSet to_download,
                                      SyncFeatureState sync_feature_state,
                                      base::OnceClosure ready_task) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!ready_task.is_null());
  DCHECK(initialized_);

  DVLOG(1) << "Configuring -" << "\n\t"
           << "types to download: " << DataTypeSetToDebugString(to_download);

  scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time());
  scheduler_->ScheduleConfiguration(GetOriginFromReason(reason), to_download,
                                    std::move(ready_task));
  if (sync_feature_state != SyncFeatureState::INITIALIZING) {
    cycle_context_->set_is_sync_feature_enabled(sync_feature_state ==
                                                SyncFeatureState::ON);
  }
}

void SyncManagerImpl::Init(InitArgs* args) {
  DCHECK(!initialized_);
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!args->cache_guid.empty());
  DCHECK(args->post_factory);
  DCHECK(!args->poll_interval.is_zero());
  DCHECK(args->cancelation_signal);
  DVLOG(1) << "SyncManager starting Init...";

  DCHECK(args->encryption_observer_proxy);
  encryption_observer_proxy_ = std::move(args->encryption_observer_proxy);

  AddObserver(&debug_info_event_listener_);

  DCHECK(args->encryption_handler);
  sync_encryption_handler_ = args->encryption_handler;

  // Register for encryption related changes now. We have to do this before
  // the initial download of control types or initializing the encryption
  // handler in order to receive notifications triggered during encryption
  // startup.
  sync_encryption_handler_->AddObserver(this);
  sync_encryption_handler_->AddObserver(encryption_observer_proxy_.get());
  sync_encryption_handler_->AddObserver(&debug_info_event_listener_);

  // base::Unretained() is safe here because SyncManagerImpl outlives
  // sync_status_tracker_.
  sync_status_tracker_ =
      std::make_unique<SyncStatusTracker>(base::BindRepeating(
          &SyncManagerImpl::NotifySyncStatusChanged, base::Unretained(this)));
  sync_status_tracker_->SetHasKeystoreKey(
      !sync_encryption_handler_->GetKeystoreKeysHandler()->NeedKeystoreKey());

  if (args->enable_local_sync_backend) {
    VLOG(1) << "Running against local sync backend.";
    sync_status_tracker_->SetLocalBackendFolder(
        args->local_sync_backend_folder.AsUTF8Unsafe());
    connection_manager_ = std::make_unique<LoopbackConnectionManager>(
        args->local_sync_backend_folder);
  } else {
    connection_manager_ = std::make_unique<SyncServerConnectionManager>(
        MakeConnectionURL(args->service_url, args->cache_guid),
        std::move(args->post_factory), args->cancelation_signal);
  }
  connection_manager_->AddListener(this);

  DVLOG(1) << "Setting sync client ID: " << args->cache_guid;
  sync_status_tracker_->SetCacheGuid(args->cache_guid);

  data_type_registry_ = std::make_unique<DataTypeRegistry>(
      this, args->cancelation_signal, sync_encryption_handler_);

  // Build a SyncCycleContext and store the worker in it.
  DVLOG(1) << "Sync is bringing up SyncCycleContext.";
  std::vector<SyncEngineEventListener*> listeners = {
      this, sync_status_tracker_.get()};
  cycle_context_ = args->engine_components_factory->BuildContext(
      connection_manager_.get(), args->extensions_activity, listeners,
      &debug_info_event_listener_, data_type_registry_.get(), args->cache_guid,
      args->birthday, args->bag_of_chips, args->poll_interval);
  scheduler_ = args->engine_components_factory->BuildScheduler(
      name_, cycle_context_.get(), args->cancelation_signal,
      args->enable_local_sync_backend);

  scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time());

  initialized_ = true;

  if (!args->enable_local_sync_backend) {
    network_connection_tracker_->AddNetworkConnectionObserver(this);
  } else {
    scheduler_->OnCredentialsUpdated();
  }

  debug_info_event_listener_.InitializationComplete();
}

void SyncManagerImpl::OnPassphraseRequired(
    const KeyDerivationParams& key_derivation_params,
    const sync_pb::EncryptedData& pending_keys) {
  // Does nothing.
}

void SyncManagerImpl::OnPassphraseAccepted() {
  // Does nothing.
}

void SyncManagerImpl::OnTrustedVaultKeyRequired() {
  // Does nothing.
}

void SyncManagerImpl::OnTrustedVaultKeyAccepted() {
  // Does nothing.
}

void SyncManagerImpl::OnEncryptedTypesChanged(DataTypeSet encrypted_types,
                                              bool encrypt_everything) {
  sync_status_tracker_->SetEncryptedTypes(encrypted_types);
}

void SyncManagerImpl::OnCryptographerStateChanged(Cryptographer* cryptographer,
                                                  bool has_pending_keys) {
  sync_status_tracker_->SetCryptographerCanEncrypt(cryptographer->CanEncrypt());
  sync_status_tracker_->SetCryptoHasPendingKeys(has_pending_keys);
  sync_status_tracker_->SetKeystoreMigrationTime(
      sync_encryption_handler_->GetKeystoreMigrationTime());
  sync_status_tracker_->SetTrustedVaultDebugInfo(
      sync_encryption_handler_->GetTrustedVaultDebugInfo());
  sync_status_tracker_->SetHasKeystoreKey(
      !sync_encryption_handler_->GetKeystoreKeysHandler()->NeedKeystoreKey());
}

void SyncManagerImpl::OnPassphraseTypeChanged(
    PassphraseType type,
    base::Time explicit_passphrase_time) {
  sync_status_tracker_->SetPassphraseType(type);
  sync_status_tracker_->SetKeystoreMigrationTime(
      sync_encryption_handler_->GetKeystoreMigrationTime());
}

void SyncManagerImpl::StartSyncingNormally(base::Time last_poll_time) {
  // Start the sync scheduler.
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  scheduler_->Start(SyncScheduler::NORMAL_MODE, last_poll_time);
}

void SyncManagerImpl::StartConfiguration() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time());
}

void SyncManagerImpl::UpdateCredentials(const SyncCredentials& credentials) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(initialized_);

  cycle_context_->set_account_name(credentials.email);

  observing_network_connectivity_changes_ = true;
  if (!connection_manager_->SetAccessToken(credentials.access_token)) {
    return;  // Auth token is known to be invalid, so exit early.
  }

  scheduler_->OnCredentialsUpdated();

  // TODO(zea): pass the credential age to the debug info event listener.
}

void SyncManagerImpl::InvalidateCredentials() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  connection_manager_->SetAccessToken(std::string());
}

void SyncManagerImpl::AddObserver(SyncManager::Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observers_.AddObserver(observer);
}

void SyncManagerImpl::RemoveObserver(SyncManager::Observer* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  observers_.RemoveObserver(observer);
}

void SyncManagerImpl::ShutdownOnSyncThread() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Prevent any in-flight method calls from running.
  weak_ptr_factory_.InvalidateWeakPtrs();

  scheduler_.reset();
  cycle_context_.reset();

  data_type_registry_.reset();

  if (sync_encryption_handler_) {
    sync_encryption_handler_->RemoveObserver(&debug_info_event_listener_);
    sync_encryption_handler_->RemoveObserver(this);
    sync_encryption_handler_->RemoveObserver(encryption_observer_proxy_.get());
  }

  RemoveObserver(&debug_info_event_listener_);

  // `connection_manager_` may end up being null here in tests (in synchronous
  // initialization mode).
  //
  // TODO(akalin): Fix this behavior.
  if (connection_manager_) {
    connection_manager_->RemoveListener(this);
  }
  connection_manager_.reset();

  network_connection_tracker_->RemoveNetworkConnectionObserver(this);
  observing_network_connectivity_changes_ = false;

  initialized_ = false;
}

void SyncManagerImpl::OnConnectionChanged(
    net::NetworkChangeNotifier::ConnectionType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!observing_network_connectivity_changes_) {
    DVLOG(1) << "Network change dropped.";
    return;
  }
  DVLOG(1) << "Network change detected.";
  scheduler_->OnConnectionStatusChange(type);
}

void SyncManagerImpl::OnServerConnectionEvent(
    const ServerConnectionEvent& event) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (event.connection_code == HttpResponse::SERVER_CONNECTION_OK) {
    for (SyncManager::Observer& observer : observers_) {
      observer.OnConnectionStatusChange(CONNECTION_OK);
    }
  }

  if (event.connection_code == HttpResponse::SYNC_AUTH_ERROR) {
    observing_network_connectivity_changes_ = false;
    for (SyncManager::Observer& observer : observers_) {
      observer.OnConnectionStatusChange(CONNECTION_AUTH_ERROR);
    }
  }

  if (event.connection_code == HttpResponse::SYNC_SERVER_ERROR) {
    for (SyncManager::Observer& observer : observers_) {
      observer.OnConnectionStatusChange(CONNECTION_SERVER_ERROR);
    }
  }
}

void SyncManagerImpl::NudgeForInitialDownload(DataType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  scheduler_->ScheduleInitialSyncNudge(type);
}

void SyncManagerImpl::NudgeForCommit(DataType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  debug_info_event_listener_.OnNudgeFromDatatype(type);
  scheduler_->ScheduleLocalNudge(type);
}

void SyncManagerImpl::SetHasPendingInvalidations(
    DataType type,
    bool has_pending_invalidations) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  scheduler_->SetHasPendingInvalidations(type, has_pending_invalidations);
  sync_status_tracker_->SetHasPendingInvalidations(type,
                                                   has_pending_invalidations);
}

void SyncManagerImpl::NotifySyncStatusChanged(const SyncStatus& status) {
  for (SyncManager::Observer& observer : observers_) {
    observer.OnSyncStatusChanged(status);
  }
}

void SyncManagerImpl::OnSyncCycleEvent(const SyncCycleEvent& event) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Only send an event if this is due to a cycle ending and this cycle
  // concludes a canonical "sync" process; that is, based on what is known
  // locally we are "all happy" and up to date.  There may be new changes on
  // the server, but we'll get them on a subsequent sync.
  //
  // Notifications are sent at the end of every sync cycle, regardless of
  // whether we should sync again.
  if (event.what_happened == SyncCycleEvent::SYNC_CYCLE_ENDED) {
    if (!initialized_) {
      DVLOG(1) << "OnSyncCycleCompleted not sent because sync api is not "
               << "initialized";
      return;
    }

    DVLOG(1) << "Sending OnSyncCycleCompleted";
    for (SyncManager::Observer& observer : observers_) {
      observer.OnSyncCycleCompleted(event.snapshot);
    }
  }
}

void SyncManagerImpl::OnActionableProtocolError(
    const SyncProtocolError& error) {
  for (SyncManager::Observer& observer : observers_) {
    observer.OnActionableProtocolError(error);
  }
}

void SyncManagerImpl::OnRetryTimeChanged(base::Time) {}

void SyncManagerImpl::OnThrottledTypesChanged(DataTypeSet) {}

void SyncManagerImpl::OnBackedOffTypesChanged(DataTypeSet) {}

void SyncManagerImpl::OnMigrationRequested(DataTypeSet types) {
  for (SyncManager::Observer& observer : observers_) {
    observer.OnMigrationRequested(types);
  }
}

void SyncManagerImpl::OnProtocolEvent(const ProtocolEvent& event) {
  protocol_event_buffer_.RecordProtocolEvent(event);
  for (SyncManager::Observer& observer : observers_) {
    observer.OnProtocolEvent(event);
  }
}

void SyncManagerImpl::SetInvalidatorEnabled(bool invalidator_enabled) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DVLOG(1) << "Invalidator enabled state is now: " << invalidator_enabled;
  sync_status_tracker_->SetNotificationsEnabled(invalidator_enabled);
  scheduler_->SetNotificationsEnabled(invalidator_enabled);
}

void SyncManagerImpl::OnIncomingInvalidation(
    DataType type,
    std::unique_ptr<SyncInvalidation> invalidation) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  UpdateHandler* handler = data_type_registry_->GetMutableUpdateHandler(type);
  if (handler) {
    handler->RecordRemoteInvalidation(std::move(invalidation));
  } else {
    DataTypeWorker::LogPendingInvalidationStatus(
        PendingInvalidationStatus::kDataTypeNotConnected);
  }
  sync_status_tracker_->IncrementNotificationsReceived();
  scheduler_->ScheduleInvalidationNudge(type);
}

void SyncManagerImpl::RefreshTypes(DataTypeSet types) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const DataTypeSet types_to_refresh =
      Intersection(types, data_type_registry_->GetConnectedTypes());

  if (!types_to_refresh.empty()) {
    scheduler_->ScheduleLocalRefreshRequest(types_to_refresh);
  }
}

DataTypeConnector* SyncManagerImpl::GetDataTypeConnector() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return data_type_registry_.get();
}

std::unique_ptr<DataTypeConnector>
SyncManagerImpl::GetDataTypeConnectorProxy() {
  DCHECK(initialized_);
  return std::make_unique<DataTypeConnectorProxy>(
      base::SequencedTaskRunner::GetCurrentDefault(),
      data_type_registry_->AsWeakPtr());
}

std::string SyncManagerImpl::cache_guid() {
  DCHECK(initialized_);
  return cycle_context_->cache_guid();
}

std::string SyncManagerImpl::birthday() {
  DCHECK(initialized_);
  DCHECK(cycle_context_);
  return cycle_context_->birthday();
}

std::string SyncManagerImpl::bag_of_chips() {
  DCHECK(initialized_);
  DCHECK(cycle_context_);
  return cycle_context_->bag_of_chips();
}

bool SyncManagerImpl::HasUnsyncedItemsForTest() {
  return data_type_registry_->HasUnsyncedItems();
}

SyncEncryptionHandler* SyncManagerImpl::GetEncryptionHandler() {
  DCHECK(sync_encryption_handler_);
  return sync_encryption_handler_;
}

std::vector<std::unique_ptr<ProtocolEvent>>
SyncManagerImpl::GetBufferedProtocolEvents() {
  return protocol_event_buffer_.GetBufferedProtocolEvents();
}

void SyncManagerImpl::OnCookieJarChanged(bool account_mismatch) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  cycle_context_->set_cookie_jar_mismatch(account_mismatch);
}

void SyncManagerImpl::UpdateActiveDevicesInvalidationInfo(
    ActiveDevicesInvalidationInfo active_devices_invalidation_info) {
  cycle_context_->set_active_devices_invalidation_info(
      std::move(active_devices_invalidation_info));
}

}  // namespace syncer
