// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/sync_file_system/drive_backend/remote_to_local_syncer.h"

#include <stdint.h>

#include <limits>
#include <memory>
#include <utility>

#include "base/check_op.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/sync_file_system/drive_backend/callback_helper.h"
#include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h"
#include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
#include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
#include "chrome/browser/sync_file_system/drive_backend/sync_task_manager.h"
#include "chrome/browser/sync_file_system/drive_backend/sync_task_token.h"
#include "chrome/browser/sync_file_system/drive_backend/task_dependency_manager.h"
#include "chrome/browser/sync_file_system/logger.h"
#include "chrome/browser/sync_file_system/syncable_file_system_util.h"
#include "components/drive/drive_api_util.h"
#include "components/drive/service/drive_service_interface.h"
#include "extensions/common/extension.h"
#include "google_apis/drive/drive_api_parser.h"
#include "storage/common/file_system/file_system_util.h"

namespace sync_file_system {
namespace drive_backend {

namespace {

bool BuildFileSystemURL(MetadataDatabase* metadata_database,
                        const FileTracker& tracker,
                        storage::FileSystemURL* url) {
  base::FilePath path;
  if (!metadata_database->BuildPathForTracker(tracker.tracker_id(), &path))
    return false;

  GURL origin =
      extensions::Extension::GetBaseURLFromExtensionId(tracker.app_id());
  *url = sync_file_system::CreateSyncableFileSystemURL(origin, path);

  return true;
}

bool HasFolderAsParent(const FileDetails& details,
                       const std::string& folder_id) {
  for (int i = 0; i < details.parent_folder_ids_size(); ++i) {
    if (details.parent_folder_ids(i) == folder_id)
      return true;
  }
  return false;
}

bool HasDisabledAppRoot(MetadataDatabase* database,
                        const FileTracker& tracker) {
  DCHECK(tracker.active());
  FileTracker app_root_tracker;
  if (database->FindAppRootTracker(tracker.app_id(), &app_root_tracker)) {
    DCHECK(app_root_tracker.tracker_kind() == TRACKER_KIND_APP_ROOT ||
           app_root_tracker.tracker_kind() == TRACKER_KIND_DISABLED_APP_ROOT);
    return app_root_tracker.tracker_kind() == TRACKER_KIND_DISABLED_APP_ROOT;
  }
  return false;
}

std::unique_ptr<FileMetadata> GetFileMetadata(MetadataDatabase* database,
                                              const std::string& file_id) {
  std::unique_ptr<FileMetadata> metadata(new FileMetadata);
  if (!database->FindFileByFileID(file_id, metadata.get()))
    metadata.reset();
  return metadata;
}

// Creates a temporary file in |dir_path|.  This must be called on an
// IO-allowed task runner, and the runner must be given as |file_task_runner|.
storage::ScopedFile CreateTemporaryFile(
    const scoped_refptr<base::TaskRunner>& file_task_runner) {
  base::FilePath temp_file_path;
  if (!base::CreateTemporaryFile(&temp_file_path))
    return storage::ScopedFile();

  return storage::ScopedFile(temp_file_path,
                             storage::ScopedFile::DELETE_ON_SCOPE_OUT,
                             file_task_runner);
}

}  // namespace

RemoteToLocalSyncer::RemoteToLocalSyncer(SyncEngineContext* sync_context)
    : sync_context_(sync_context),
      file_type_(SYNC_FILE_TYPE_UNKNOWN),
      sync_action_(SYNC_ACTION_NONE),
      prepared_(false),
      sync_root_deletion_(false) {}

RemoteToLocalSyncer::~RemoteToLocalSyncer() = default;

void RemoteToLocalSyncer::RunPreflight(std::unique_ptr<SyncTaskToken> token) {
  token->InitializeTaskLog("Remote -> Local");

  if (!drive_service() || !metadata_database() || !remote_change_processor()) {
    token->RecordLog("Context not ready.");
    SyncTaskManager::NotifyTaskDone(std::move(token), SYNC_STATUS_FAILED);
    return;
  }

  dirty_tracker_ = base::WrapUnique(new FileTracker);
  if (metadata_database()->GetDirtyTracker(dirty_tracker_.get())) {
    token->RecordLog(base::StringPrintf("Start: tracker_id=%" PRId64,
                                        dirty_tracker_->tracker_id()));
    metadata_database()->DemoteTracker(dirty_tracker_->tracker_id());
    ResolveRemoteChange(std::move(token));
    return;
  }

  token->RecordLog("Nothing to do.");
  SyncTaskManager::NotifyTaskDone(std::move(token),
                                  SYNC_STATUS_NO_CHANGE_TO_SYNC);
}

void RemoteToLocalSyncer::ResolveRemoteChange(
    std::unique_ptr<SyncTaskToken> token) {
  DCHECK(dirty_tracker_);
  remote_metadata_ =
      GetFileMetadata(metadata_database(), dirty_tracker_->file_id());

  if (!remote_metadata_ || !remote_metadata_->has_details()) {
    if (remote_metadata_ && !remote_metadata_->has_details()) {
      token->RecordLog("Missing details of a remote file: " +
                       remote_metadata_->file_id());
      NOTREACHED();
    }
    token->RecordLog("Missing remote metadata case.");

    MoveToBackground(
        std::move(token),
        base::BindOnce(&RemoteToLocalSyncer::HandleMissingRemoteMetadata,
                       weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  DCHECK(remote_metadata_);
  DCHECK(remote_metadata_->has_details());
  const FileDetails& remote_details = remote_metadata_->details();

  if (!dirty_tracker_->active() ||
      HasDisabledAppRoot(metadata_database(), *dirty_tracker_)) {
    // Handle inactive tracker in SyncCompleted.
    token->RecordLog("Inactive tracker case.");
    SyncCompleted(std::move(token), SYNC_STATUS_OK);
    return;
  }

  DCHECK(dirty_tracker_->active());
  DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));

  if (!dirty_tracker_->has_synced_details()) {
    token->RecordLog(base::StringPrintf(
        "Missing synced_details of an active tracker: %" PRId64,
        dirty_tracker_->tracker_id()));
    NOTREACHED();
  }

  DCHECK(dirty_tracker_->has_synced_details());
  const FileDetails& synced_details = dirty_tracker_->synced_details();

  if (dirty_tracker_->tracker_id() ==
      metadata_database()->GetSyncRootTrackerID()) {
    if (remote_details.missing() ||
        synced_details.title() != remote_details.title() ||
        remote_details.parent_folder_ids_size()) {
      token->RecordLog("Sync-root deletion.");
      sync_root_deletion_ = true;
      SyncCompleted(std::move(token), SYNC_STATUS_OK);
      return;
    }
    token->RecordLog("Trivial sync-root change.");
    SyncCompleted(std::move(token), SYNC_STATUS_OK);
    return;
  }

  DCHECK_NE(dirty_tracker_->tracker_id(),
            metadata_database()->GetSyncRootTrackerID());

  if (!BuildFileSystemURL(metadata_database(), *dirty_tracker_, &url_)) {
    NOTREACHED();
  }

  DCHECK(url_.is_valid());

  if (remote_details.missing()) {
    if (!synced_details.missing()) {
      token->RecordLog("Remote file deletion.");
      MoveToBackground(std::move(token),
                       base::BindOnce(&RemoteToLocalSyncer::HandleDeletion,
                                      weak_ptr_factory_.GetWeakPtr()));
      return;
    }

    DCHECK(synced_details.missing());
    token->RecordLog("Found a stray missing tracker: " +
                     dirty_tracker_->file_id());
    NOTREACHED();
  }

  // Most of remote_details field is valid from here.
  DCHECK(!remote_details.missing());

  if (synced_details.file_kind() != remote_details.file_kind()) {
    token->RecordLog(base::StringPrintf(
        "Found type mismatch between remote and local file: %s"
        " type: (local) %d vs (remote) %d",
        dirty_tracker_->file_id().c_str(), synced_details.file_kind(),
        remote_details.file_kind()));
    NOTREACHED();
  }
  DCHECK_EQ(synced_details.file_kind(), remote_details.file_kind());

  if (synced_details.file_kind() == FILE_KIND_UNSUPPORTED) {
    token->RecordLog("Found an unsupported active file: " +
                     remote_metadata_->file_id());
    NOTREACHED();
  }
  DCHECK(remote_details.file_kind() == FILE_KIND_FILE ||
         remote_details.file_kind() == FILE_KIND_FOLDER);

  if (synced_details.title() != remote_details.title()) {
    // Handle rename as deletion + addition.
    token->RecordLog("Detected file rename.");

    MoveToBackground(std::move(token),
                     base::BindOnce(&RemoteToLocalSyncer::HandleFileMove,
                                    weak_ptr_factory_.GetWeakPtr()));
    return;
  }
  DCHECK_EQ(synced_details.title(), remote_details.title());

  FileTracker parent_tracker;
  if (!metadata_database()->FindTrackerByTrackerID(
          dirty_tracker_->parent_tracker_id(), &parent_tracker)) {
    token->RecordLog("Missing parent tracker for a non sync-root tracker: " +
                     dirty_tracker_->file_id());
    NOTREACHED();
  }

  if (!HasFolderAsParent(remote_details, parent_tracker.file_id())) {
    // Handle reorganize as deletion + addition.
    token->RecordLog("Detected file reorganize.");

    MoveToBackground(std::move(token),
                     base::BindOnce(&RemoteToLocalSyncer::HandleFileMove,
                                    weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  if (synced_details.file_kind() == FILE_KIND_FILE) {
    if (synced_details.md5() != remote_details.md5()) {
      token->RecordLog("Detected file content update.");
      MoveToBackground(std::move(token),
                       base::BindOnce(&RemoteToLocalSyncer::HandleContentUpdate,
                                      weak_ptr_factory_.GetWeakPtr()));
      return;
    }
  } else {
    DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind());
    if (synced_details.missing()) {
      token->RecordLog("Detected folder update.");
      MoveToBackground(std::move(token),
                       base::BindOnce(&RemoteToLocalSyncer::HandleFolderUpdate,
                                      weak_ptr_factory_.GetWeakPtr()));
      return;
    }
    if (dirty_tracker_->needs_folder_listing()) {
      token->RecordLog("Needs listing folder.");
      MoveToBackground(std::move(token),
                       base::BindOnce(&RemoteToLocalSyncer::ListFolderContent,
                                      weak_ptr_factory_.GetWeakPtr()));
      return;
    }
    SyncCompleted(std::move(token), SYNC_STATUS_OK);
    return;
  }

  token->RecordLog("Trivial file change.");
  SyncCompleted(std::move(token), SYNC_STATUS_OK);
}

void RemoteToLocalSyncer::MoveToBackground(std::unique_ptr<SyncTaskToken> token,
                                           Continuation continuation) {
  DCHECK(dirty_tracker_);

  std::unique_ptr<TaskBlocker> blocker(new TaskBlocker);
  blocker->app_id = dirty_tracker_->app_id();
  if (url_.is_valid())
    blocker->paths.push_back(url_.path());
  blocker->file_ids.push_back(dirty_tracker_->file_id());
  blocker->tracker_ids.push_back(dirty_tracker_->tracker_id());

  SyncTaskManager::UpdateTaskBlocker(
      std::move(token), std::move(blocker),
      base::BindOnce(&RemoteToLocalSyncer::ContinueAsBackgroundTask,
                     weak_ptr_factory_.GetWeakPtr(), std::move(continuation)));
}

void RemoteToLocalSyncer::ContinueAsBackgroundTask(
    Continuation continuation,
    std::unique_ptr<SyncTaskToken> token) {
  DCHECK(dirty_tracker_);

  // The SyncTask runs as a background task beyond this point.
  // Not that any task can run between MoveToBackground() and
  // ContinueAsBackgroundTask(), so we need to make sure other tasks didn't
  // affect to the current RemoteToLocalSyncer task.
  //
  // - For LocalToRemoteSyncer, it may update or delete any of FileTracker and
  //   FileMetadata. When it updates FileMetadata or FileDetails in FileTracker,
  //   it also updates |change_id|.  So, ensure the target FileTracker and
  //   FileMetadata exist and their |change_id|s are not updated.
  // - For ListChangesTask, it may update FileMetadata together with |change_id|
  //   and may delete FileTracker.
  // - For UninstallAppTask, it may delete FileMetadata and FileTracker.
  //   Check if FileTracker still exists.
  // - For other RemoteToLocalSyncer, it may delete the FileTracker of parent.
  //   Note that since RemoteToLocalSyncer demotes the target FileTracker first,
  //   any other RemoteToLocalSyncer does not run for current |dirty_tracker_|.
  // - Others, SyncEngineInitializer and RegisterAppTask doesn't affect to

  FileTracker latest_dirty_tracker;
  if (!metadata_database()->FindTrackerByTrackerID(dirty_tracker_->tracker_id(),
                                                   &latest_dirty_tracker) ||
      dirty_tracker_->active() != latest_dirty_tracker.active() ||
      !latest_dirty_tracker.dirty()) {
    SyncCompleted(std::move(token), SYNC_STATUS_RETRY);
    return;
  }

  int64_t current_change_id = std::numeric_limits<int64_t>::min();
  int64_t latest_change_id = std::numeric_limits<int64_t>::min();
  if (dirty_tracker_->has_synced_details())
    current_change_id = dirty_tracker_->synced_details().change_id();
  if (latest_dirty_tracker.has_synced_details())
    latest_change_id = latest_dirty_tracker.synced_details().change_id();
  if (current_change_id != latest_change_id) {
    SyncCompleted(std::move(token), SYNC_STATUS_RETRY);
    return;
  }

  FileMetadata latest_file_metadata;
  if (metadata_database()->FindFileByFileID(dirty_tracker_->file_id(),
                                            &latest_file_metadata)) {
    if (!remote_metadata_) {
      SyncCompleted(std::move(token), SYNC_STATUS_RETRY);
      return;
    }

    int64_t change_id = remote_metadata_->details().change_id();
    latest_change_id = latest_file_metadata.details().change_id();
    if (change_id != latest_change_id) {
      SyncCompleted(std::move(token), SYNC_STATUS_RETRY);
      return;
    }
  } else {
    if (remote_metadata_) {
      SyncCompleted(std::move(token), SYNC_STATUS_RETRY);
      return;
    }
  }
  std::move(continuation).Run(std::move(token));
}

void RemoteToLocalSyncer::HandleMissingRemoteMetadata(
    std::unique_ptr<SyncTaskToken> token) {
  DCHECK(dirty_tracker_);

  drive_service()->GetFileResource(
      dirty_tracker_->file_id(),
      base::BindOnce(&RemoteToLocalSyncer::DidGetRemoteMetadata,
                     weak_ptr_factory_.GetWeakPtr(), std::move(token)));
}

void RemoteToLocalSyncer::DidGetRemoteMetadata(
    std::unique_ptr<SyncTaskToken> token,
    google_apis::ApiErrorCode error,
    std::unique_ptr<google_apis::FileResource> entry) {
  DCHECK(sync_context_->GetWorkerTaskRunner()->RunsTasksInCurrentSequence());

  SyncStatusCode status = ApiErrorCodeToSyncStatusCode(error);
  if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
    SyncCompleted(std::move(token), status);
    return;
  }

  if (error == google_apis::HTTP_NOT_FOUND) {
    status = metadata_database()->UpdateByDeletedRemoteFile(
        dirty_tracker_->file_id());
    SyncCompleted(std::move(token), status);
    return;
  }

  if (!entry) {
    NOTREACHED();
  }

  status = metadata_database()->UpdateByFileResource(*entry);
  if (status != SYNC_STATUS_OK) {
    SyncCompleted(std::move(token), status);
    return;
  }

  metadata_database()->PromoteDemotedTracker(dirty_tracker_->tracker_id());

  // Do not update |dirty_tracker_|.
  SyncCompleted(std::move(token), SYNC_STATUS_RETRY);
}

void RemoteToLocalSyncer::DidPrepareForAddOrUpdateFile(
    std::unique_ptr<SyncTaskToken> token,
    SyncStatusCode status) {
  if (status != SYNC_STATUS_OK) {
    SyncCompleted(std::move(token), status);
    return;
  }

  DCHECK(url_.is_valid());
  DCHECK(local_metadata_);
  DCHECK(local_changes_);

  // Check if the local file exists.
  if (local_metadata_->file_type == SYNC_FILE_TYPE_UNKNOWN ||
      (!local_changes_->empty() && local_changes_->back().IsDelete())) {
    file_type_ = SYNC_FILE_TYPE_FILE;
    sync_action_ = SYNC_ACTION_ADDED;
    // Missing local file case.
    // Download the file and add it to local as a new file.
    DownloadFile(std::move(token));
    return;
  }

  DCHECK(local_changes_->empty() || local_changes_->back().IsAddOrUpdate());
  if (local_changes_->empty()) {
    if (local_metadata_->file_type == SYNC_FILE_TYPE_FILE) {
      file_type_ = SYNC_FILE_TYPE_FILE;
      sync_action_ = SYNC_ACTION_UPDATED;
      // Download the file and overwrite the existing local file.
      DownloadFile(std::move(token));
      return;
    }

    DCHECK_EQ(SYNC_FILE_TYPE_DIRECTORY, local_metadata_->file_type);

    // Got a remote regular file modification for existing local folder.
    // Our policy prioritize folders in this case.
    // Let local-to-remote sync phase process this change.
    remote_change_processor()->RecordFakeLocalChange(
        url_,
        FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                   local_metadata_->file_type),
        SyncCompletedCallback(std::move(token)));
    return;
  }

  DCHECK(local_changes_->back().IsAddOrUpdate());
  // Conflict case.
  // Do nothing for the change now, and handle this in LocalToRemoteSync phase.
  SyncCompleted(std::move(token), SYNC_STATUS_RETRY);
}

void RemoteToLocalSyncer::HandleFolderUpdate(
    std::unique_ptr<SyncTaskToken> token) {
  DCHECK(dirty_tracker_);
  DCHECK(dirty_tracker_->active());
  DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));

  DCHECK(remote_metadata_);
  DCHECK(remote_metadata_->has_details());
  DCHECK(!remote_metadata_->details().missing());
  DCHECK_EQ(FILE_KIND_FOLDER, remote_metadata_->details().file_kind());

  Prepare(base::BindOnce(&RemoteToLocalSyncer::DidPrepareForFolderUpdate,
                         weak_ptr_factory_.GetWeakPtr(), std::move(token)));
}

void RemoteToLocalSyncer::DidPrepareForFolderUpdate(
    std::unique_ptr<SyncTaskToken> token,
    SyncStatusCode status) {
  if (status != SYNC_STATUS_OK) {
    SyncCompleted(std::move(token), status);
    return;
  }

  DCHECK(url_.is_valid());
  DCHECK(local_metadata_);
  DCHECK(local_changes_);

  // Check if the local file exists.
  if (local_metadata_->file_type == SYNC_FILE_TYPE_UNKNOWN ||
      (!local_changes_->empty() && local_changes_->back().IsDelete())) {
    file_type_ = SYNC_FILE_TYPE_DIRECTORY;
    sync_action_ = SYNC_ACTION_ADDED;
    // No local file exists at the path.
    CreateFolder(std::move(token));
    return;
  }

  if (local_metadata_->file_type == SYNC_FILE_TYPE_DIRECTORY) {
    // There already exists a folder, nothing left to do.
    if (dirty_tracker_->needs_folder_listing() &&
        !dirty_tracker_->synced_details().missing()) {
      ListFolderContent(std::move(token));
    } else {
      SyncCompleted(std::move(token), SYNC_STATUS_OK);
    }
    return;
  }

  DCHECK_EQ(SYNC_FILE_TYPE_FILE, local_metadata_->file_type);
  file_type_ = SYNC_FILE_TYPE_DIRECTORY;
  sync_action_ = SYNC_ACTION_ADDED;
  // Got a remote folder for existing local file.
  // Our policy prioritize folders in this case.
  CreateFolder(std::move(token));
}

void RemoteToLocalSyncer::HandleDeletion(std::unique_ptr<SyncTaskToken> token) {
  DCHECK(dirty_tracker_);
  DCHECK(dirty_tracker_->active());
  DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));
  DCHECK(dirty_tracker_->has_synced_details());
  DCHECK(!dirty_tracker_->synced_details().missing());

  DCHECK(remote_metadata_);
  DCHECK(remote_metadata_->has_details());
  DCHECK(remote_metadata_->details().missing());

  Prepare(base::BindOnce(&RemoteToLocalSyncer::DidPrepareForDeletion,
                         weak_ptr_factory_.GetWeakPtr(), std::move(token)));
}

void RemoteToLocalSyncer::HandleFileMove(std::unique_ptr<SyncTaskToken> token) {
  DCHECK(dirty_tracker_);
  DCHECK(dirty_tracker_->active());
  DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));
  DCHECK(dirty_tracker_->has_synced_details());

  DCHECK(remote_metadata_);
  DCHECK(remote_metadata_->has_details());
  DCHECK(!remote_metadata_->details().missing());

  Prepare(base::BindOnce(&RemoteToLocalSyncer::DidPrepareForDeletion,
                         weak_ptr_factory_.GetWeakPtr(), std::move(token)));
}

void RemoteToLocalSyncer::DidPrepareForDeletion(
    std::unique_ptr<SyncTaskToken> token,
    SyncStatusCode status) {
  if (status != SYNC_STATUS_OK) {
    SyncCompleted(std::move(token), status);
    return;
  }

  DCHECK(url_.is_valid());
  DCHECK(local_metadata_);
  DCHECK(local_changes_);

  // Check if the local file exists.
  if (local_metadata_->file_type == SYNC_FILE_TYPE_UNKNOWN ||
      (!local_changes_->empty() && local_changes_->back().IsDelete())) {
    // No local file exists at the path.
    SyncCompleted(std::move(token), SYNC_STATUS_OK);
    return;
  }

  DCHECK(local_changes_->empty() || local_changes_->back().IsAddOrUpdate());
  if (local_changes_->empty()) {
    file_type_ = local_metadata_->file_type;
    sync_action_ = SYNC_ACTION_DELETED;
    DeleteLocalFile(std::move(token));
    return;
  }

  DCHECK(local_changes_->back().IsAddOrUpdate());
  // File is remotely deleted and locally updated.
  // Ignore the remote deletion and handle it as if applied successfully.
  SyncCompleted(std::move(token), SYNC_STATUS_OK);
}

void RemoteToLocalSyncer::HandleContentUpdate(
    std::unique_ptr<SyncTaskToken> token) {
  DCHECK(dirty_tracker_);
  DCHECK(dirty_tracker_->active());
  DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));
  DCHECK(dirty_tracker_->has_synced_details());
  DCHECK_EQ(FILE_KIND_FILE, dirty_tracker_->synced_details().file_kind());

  DCHECK(remote_metadata_);
  DCHECK(remote_metadata_->has_details());
  DCHECK(!remote_metadata_->details().missing());

  DCHECK_NE(dirty_tracker_->synced_details().md5(),
            remote_metadata_->details().md5());

  Prepare(base::BindOnce(&RemoteToLocalSyncer::DidPrepareForAddOrUpdateFile,
                         weak_ptr_factory_.GetWeakPtr(), std::move(token)));
}

void RemoteToLocalSyncer::ListFolderContent(
    std::unique_ptr<SyncTaskToken> token) {
  DCHECK(dirty_tracker_);
  DCHECK(dirty_tracker_->active());
  DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));
  DCHECK(dirty_tracker_->has_synced_details());
  DCHECK(!dirty_tracker_->synced_details().missing());
  DCHECK_EQ(FILE_KIND_FOLDER, dirty_tracker_->synced_details().file_kind());
  DCHECK(dirty_tracker_->needs_folder_listing());

  DCHECK(remote_metadata_);
  DCHECK(remote_metadata_->has_details());
  DCHECK(!remote_metadata_->details().missing());

  // TODO(tzik): Replace this call with ChildList version.
  drive_service()->GetFileListInDirectory(
      dirty_tracker_->file_id(),
      base::BindOnce(&RemoteToLocalSyncer::DidListFolderContent,
                     weak_ptr_factory_.GetWeakPtr(), std::move(token),
                     base::WrapUnique(new FileIDList)));
}

void RemoteToLocalSyncer::DidListFolderContent(
    std::unique_ptr<SyncTaskToken> token,
    std::unique_ptr<FileIDList> children,
    google_apis::ApiErrorCode error,
    std::unique_ptr<google_apis::FileList> file_list) {
  SyncStatusCode status = ApiErrorCodeToSyncStatusCode(error);
  if (status != SYNC_STATUS_OK) {
    SyncCompleted(std::move(token), status);
    return;
  }

  if (!file_list) {
    NOTREACHED();
  }

  children->reserve(children->size() + file_list->items().size());
  for (const auto& file_resource : file_list->items()) {
    children->push_back(file_resource->file_id());
  }

  if (!file_list->next_link().is_empty()) {
    drive_service()->GetRemainingFileList(
        file_list->next_link(),
        base::BindOnce(&RemoteToLocalSyncer::DidListFolderContent,
                       weak_ptr_factory_.GetWeakPtr(), std::move(token),
                       std::move(children)));
    return;
  }

  status = metadata_database()->PopulateFolderByChildList(
      dirty_tracker_->file_id(), *children);
  SyncCompleted(std::move(token), status);
}

void RemoteToLocalSyncer::SyncCompleted(std::unique_ptr<SyncTaskToken> token,
                                        SyncStatusCode status) {
  token->RecordLog(base::StringPrintf(
      "[Remote -> Local]: Finished: action=%s, tracker=%" PRId64 " status=%s",
      SyncActionToString(sync_action_), dirty_tracker_->tracker_id(),
      SyncStatusCodeToString(status)));

  if (sync_root_deletion_) {
    FinalizeSync(std::move(token), SYNC_STATUS_OK);
    return;
  }

  if (status == SYNC_STATUS_RETRY) {
    FinalizeSync(std::move(token), SYNC_STATUS_OK);
    return;
  }

  if (status != SYNC_STATUS_OK) {
    FinalizeSync(std::move(token), status);
    return;
  }

  DCHECK(dirty_tracker_);
  DCHECK(remote_metadata_);
  DCHECK(remote_metadata_->has_details());

  FileDetails updated_details = remote_metadata_->details();
  if (!dirty_tracker_->active() ||
      HasDisabledAppRoot(metadata_database(), *dirty_tracker_)) {
    // Operations for an inactive tracker don't update file content.
    if (dirty_tracker_->has_synced_details())
      updated_details.set_md5(dirty_tracker_->synced_details().md5());
    if (!dirty_tracker_->active()) {
      // Keep missing true, as the change hasn't been synced to local.
      updated_details.clear_md5();
      updated_details.set_missing(true);
    }
  }

  status = metadata_database()->UpdateTracker(dirty_tracker_->tracker_id(),
                                              updated_details);
  FinalizeSync(std::move(token), status);
}

void RemoteToLocalSyncer::FinalizeSync(std::unique_ptr<SyncTaskToken> token,
                                       SyncStatusCode status) {
  if (prepared_) {
    remote_change_processor()->FinalizeRemoteSync(
        url_, false /* clear_local_change */,
        base::BindOnce(SyncTaskManager::NotifyTaskDone, std::move(token),
                       status));
    return;
  }

  SyncTaskManager::NotifyTaskDone(std::move(token), status);
}

void RemoteToLocalSyncer::Prepare(SyncStatusCallback callback) {
  DCHECK(url_.is_valid());
  // TODO(crbug.com/40733540): convert passed parameters to moved
  // parameters when this is converted to base::BindOnce.
  remote_change_processor()->PrepareForProcessRemoteChange(
      url_,
      base::BindOnce(&RemoteToLocalSyncer::DidPrepare,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void RemoteToLocalSyncer::DidPrepare(SyncStatusCallback callback,
                                     SyncStatusCode status,
                                     const SyncFileMetadata& local_metadata,
                                     const FileChangeList& local_changes) {
  if (status != SYNC_STATUS_OK) {
    std::move(callback).Run(status);
    return;
  }
  prepared_ = true;

  local_metadata_ = std::make_unique<SyncFileMetadata>(local_metadata);
  local_changes_ = std::make_unique<FileChangeList>(local_changes);

  std::move(callback).Run(status);
}

void RemoteToLocalSyncer::DeleteLocalFile(
    std::unique_ptr<SyncTaskToken> token) {
  remote_change_processor()->ApplyRemoteChange(
      FileChange(FileChange::FILE_CHANGE_DELETE, SYNC_FILE_TYPE_UNKNOWN),
      base::FilePath(), url_, SyncCompletedCallback(std::move(token)));
}

void RemoteToLocalSyncer::DownloadFile(std::unique_ptr<SyncTaskToken> token) {
  DCHECK(sync_context_->GetWorkerTaskRunner()->RunsTasksInCurrentSequence());

  storage::ScopedFile file = CreateTemporaryFile(
      base::WrapRefCounted(sync_context_->GetWorkerTaskRunner()));

  base::FilePath path = file.path();
  drive_service()->DownloadFile(
      path, remote_metadata_->file_id(),
      base::BindOnce(&RemoteToLocalSyncer::DidDownloadFile,
                     weak_ptr_factory_.GetWeakPtr(), std::move(token),
                     std::move(file)),
      google_apis::GetContentCallback(), google_apis::ProgressCallback());
}

void RemoteToLocalSyncer::DidDownloadFile(std::unique_ptr<SyncTaskToken> token,
                                          storage::ScopedFile file,
                                          google_apis::ApiErrorCode error,
                                          const base::FilePath&) {
  DCHECK(sync_context_->GetWorkerTaskRunner()->RunsTasksInCurrentSequence());

  SyncStatusCode status = ApiErrorCodeToSyncStatusCode(error);
  if (status != SYNC_STATUS_OK) {
    SyncCompleted(std::move(token), status);
    return;
  }

  base::FilePath path = file.path();
  const std::string md5 = drive::util::GetMd5Digest(path);
  if (md5.empty()) {
    SyncCompleted(std::move(token), SYNC_FILE_ERROR_NOT_FOUND);
    return;
  }

  if (md5 != remote_metadata_->details().md5()) {
    // File has been modified since last metadata retrieval.
    SyncCompleted(std::move(token), SYNC_STATUS_RETRY);
    return;
  }

  remote_change_processor()->ApplyRemoteChange(
      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE),
      path, url_,
      base::BindOnce(&RemoteToLocalSyncer::DidApplyDownload,
                     weak_ptr_factory_.GetWeakPtr(), std::move(token),
                     std::move(file)));
}

void RemoteToLocalSyncer::DidApplyDownload(std::unique_ptr<SyncTaskToken> token,
                                           storage::ScopedFile,
                                           SyncStatusCode status) {
  SyncCompleted(std::move(token), status);
}

void RemoteToLocalSyncer::CreateFolder(std::unique_ptr<SyncTaskToken> token) {
  remote_change_processor()->ApplyRemoteChange(
      FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
                 SYNC_FILE_TYPE_DIRECTORY),
      base::FilePath(), url_, SyncCompletedCallback(std::move(token)));
}

drive::DriveServiceInterface* RemoteToLocalSyncer::drive_service() {
  return sync_context_->GetDriveService();
}

MetadataDatabase* RemoteToLocalSyncer::metadata_database() {
  return sync_context_->GetMetadataDatabase();
}

RemoteChangeProcessor* RemoteToLocalSyncer::remote_change_processor() {
  DCHECK(sync_context_->GetRemoteChangeProcessor());
  return sync_context_->GetRemoteChangeProcessor();
}

SyncStatusCallback RemoteToLocalSyncer::SyncCompletedCallback(
    std::unique_ptr<SyncTaskToken> token) {
  return base::BindOnce(&RemoteToLocalSyncer::SyncCompleted,
                        weak_ptr_factory_.GetWeakPtr(), std::move(token));
}

}  // namespace drive_backend
}  // namespace sync_file_system
