// Copyright 2023 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/policy/messaging_layer/upload/file_upload_job.h"

#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "chrome/browser/policy/messaging_layer/proto/synced/log_upload_event.pb.h"
#include "chrome/browser/policy/messaging_layer/public/report_client.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/proto/synced/record_constants.pb.h"
#include "components/reporting/proto/synced/upload_tracker.pb.h"
#include "components/reporting/resources/resource_manager.h"
#include "components/reporting/storage/storage_module_interface.h"
#include "components/reporting/util/reporting_errors.h"
#include "components/reporting/util/status.h"

namespace reporting {

namespace {

// These three helpers call `Initiate`, `NextStep` and `Finalize` in
// delegate on respective task runner and make sure to call `cb` even if
// `delegate` is invalidated.
void CallInitiateOnSequence(
    base::WeakPtr<FileUploadJob::Delegate> delegate,
    std::string_view origin_path,
    std::string_view upload_parameters,
    base::OnceCallback<void(
        StatusOr<std::pair<int64_t /*total*/, std::string /*session_token*/>>)>
        cb) {
  if (!delegate) {
    std::move(cb).Run(base::unexpected(
        Status(error::UNAVAILABLE, "Delegate is unavailable")));
    base::UmaHistogramEnumeration(
        reporting::kUmaUnavailableErrorReason,
        UnavailableErrorReason::FILE_UPLOAD_JOB_DELEGATE_IS_NULL,
        UnavailableErrorReason::MAX_VALUE);
    return;
  }
  delegate->DoInitiate(origin_path, upload_parameters, std::move(cb));
}

void CallNextStepOnSequence(
    base::WeakPtr<FileUploadJob::Delegate> delegate,
    int64_t total,
    int64_t uploaded,
    std::string_view session_token,
    ScopedReservation scoped_reservation,
    base::OnceCallback<void(StatusOr<std::pair<int64_t /*uploaded*/,
                                               std::string /*session_token*/>>)>
        cb) {
  if (!delegate) {
    std::move(cb).Run(base::unexpected(
        Status(error::UNAVAILABLE, "Delegate is unavailable")));
    base::UmaHistogramEnumeration(
        reporting::kUmaUnavailableErrorReason,
        UnavailableErrorReason::FILE_UPLOAD_JOB_DELEGATE_IS_NULL,
        UnavailableErrorReason::MAX_VALUE);
    return;
  }
  delegate->DoNextStep(total, uploaded, session_token,
                       std::move(scoped_reservation), std::move(cb));
}

void CallFinalizeOnSequence(
    base::WeakPtr<FileUploadJob::Delegate> delegate,
    std::string_view session_token,
    base::OnceCallback<void(StatusOr<std::string /*access_parameters*/>)> cb) {
  if (!delegate) {
    std::move(cb).Run(base::unexpected(
        Status(error::UNAVAILABLE, "Delegate is unavailable")));
    base::UmaHistogramEnumeration(
        reporting::kUmaUnavailableErrorReason,
        UnavailableErrorReason::FILE_UPLOAD_JOB_DELEGATE_IS_NULL,
        UnavailableErrorReason::MAX_VALUE);
    return;
  }
  delegate->DoFinalize(session_token, std::move(cb));
}
}  // namespace

// Delegate base class implementation.

FileUploadJob::Delegate::Delegate() = default;
FileUploadJob::Delegate::~Delegate() = default;

base::WeakPtr<FileUploadJob::Delegate> FileUploadJob::Delegate::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

// Manager implementation.

// static
FileUploadJob::Manager* FileUploadJob::Manager::GetInstance() {
  return instance_ref().get();
}

// static
std::unique_ptr<FileUploadJob::Manager>&
FileUploadJob::Manager::instance_ref() {
  static base::NoDestructor<std::unique_ptr<FileUploadJob::Manager>> instance(
      new FileUploadJob::Manager());
  return *instance;
}

FileUploadJob::Manager::Manager()
    : sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::TaskPriority::BEST_EFFORT, base::MayBlock()})) {
  DETACH_FROM_SEQUENCE(manager_sequence_checker_);
}

FileUploadJob::Manager::~Manager() = default;

void FileUploadJob::Manager::Register(
    Priority priority,
    Record record_copy,
    ::ash::reporting::LogUploadEvent log_upload_event,
    Delegate::SmartPtr delegate,
    base::OnceCallback<void(StatusOr<FileUploadJob*>)> result_cb) {
  sequenced_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          [](Manager* self, Priority priority, Record record_copy,
             ::ash::reporting::LogUploadEvent log_upload_event,
             Delegate::SmartPtr delegate,
             base::OnceCallback<void(StatusOr<FileUploadJob*>)> result_cb) {
            // Retry count must allow the job to run.
            if (log_upload_event.upload_settings().retry_count() < 1) {
              std::move(result_cb).Run(base::unexpected(
                  Status(error::INVALID_ARGUMENT, "Too many upload attempts")));
              return;
            }
            // Serialize settings to get the map key.
            std::string serialized_settings;
            if (!log_upload_event.upload_settings().SerializeToString(
                    &serialized_settings)) {
              std::move(result_cb).Run(
                  base::unexpected(Status(error::INVALID_ARGUMENT,
                                          "Job settings failed to serialize")));
              return;
            }
            // Now add the job to the map.
            // Existing job is returned, new job is recorded and
            // returned.
            DCHECK_CALLED_ON_VALID_SEQUENCE(self->manager_sequence_checker_);
            auto it = self->uploads_in_progress_.find(serialized_settings);
            if (it == self->uploads_in_progress_.end()) {
              auto res = self->uploads_in_progress_.emplace(
                  serialized_settings,
                  std::make_unique<FileUploadJob>(
                      log_upload_event.upload_settings(),
                      log_upload_event.upload_tracker(), std::move(delegate)));
              CHECK(res.second);
              it = res.first;
              DCHECK_CALLED_ON_VALID_SEQUENCE(
                  it->second->job_sequence_checker_);
              it->second->timer_.Start(
                  FROM_HERE, kLifeTime,
                  base::BindRepeating(
                      [](Manager* self, std::string serialized_settings) {
                        // Locate the job in the map.
                        DCHECK_CALLED_ON_VALID_SEQUENCE(
                            self->manager_sequence_checker_);
                        auto it = self->uploads_in_progress_.find(
                            serialized_settings);
                        if (it == self->uploads_in_progress_.end()) {
                          // Not found.
                          return;
                        }
                        // Stop timer and remove the job from map (thus deleting
                        // it).
                        DCHECK_CALLED_ON_VALID_SEQUENCE(
                            it->second->job_sequence_checker_);
                        it->second->timer_.Stop();
                        self->uploads_in_progress_.erase(it);
                      },
                      base::Unretained(self), std::move(serialized_settings)));
            }
            auto* job = it->second.get();
            // Check the `job` state, schedule the action.
            DCHECK_CALLED_ON_VALID_SEQUENCE(job->job_sequence_checker_);
            if (job->event_helper_) {
              // The job already executes, the event we are dealing with
              // is likely the one that caused this, do not upload it
              // (otherwise we would lose track of the job if the device
              // restarts).
              std::move(result_cb).Run(base::unexpected(
                  Status(error::ALREADY_EXISTS, "Duplicate event")));
              return;
            }
            // Attach the event to the job.
            job->event_helper_ = std::make_unique<FileUploadJob::EventHelper>(
                job->GetWeakPtr(), priority, std::move(record_copy),
                std::move(log_upload_event));
            std::move(result_cb).Run(job);
          },
          base::Unretained(this), priority, std::move(record_copy),
          std::move(log_upload_event), std::move(delegate),
          std::move(result_cb)));
}

scoped_refptr<base::SequencedTaskRunner>
FileUploadJob::Manager::sequenced_task_runner() const {
  return sequenced_task_runner_;
}

// EventHelper implementation.

FileUploadJob::EventHelper::EventHelper(
    base::WeakPtr<FileUploadJob> job,
    Priority priority,
    Record record_copy,
    ::ash::reporting::LogUploadEvent log_upload_event)
    : job_(job),
      priority_(priority),
      record_copy_(std::move(record_copy)),
      log_upload_event_(std::move(log_upload_event)) {}

FileUploadJob::EventHelper::~EventHelper() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (done_cb_) {
    Complete(Status(error::DATA_LOSS,
                    "Helper started but completion callback not called."));
  }
}

void FileUploadJob::EventHelper::Run(
    const ScopedReservation& scoped_reservation,
    base::OnceCallback<void(Status)> done_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(!done_cb_) << "Helper already running";
  done_cb_ = std::move(done_cb);
  if (job_->tracker().has_status()) {
    // The job already failed before. Upload the event as is.
    Complete();
    return;
  }
  if (!job_->tracker().access_parameters().empty()) {
    // Job complete, nothing left to do. Upload the event as is.
    Complete();
    return;
  }
  if (job_->tracker().session_token().empty()) {
    // Job not initiated yet, do it now. Upon completion success post new
    // event and upload the current one.
    job_->Initiate(base::BindOnce(&EventHelper::RepostAndComplete,
                                  weak_ptr_factory_.GetWeakPtr()));
    return;
  }
  if (log_upload_event_.upload_tracker().session_token().empty()) {
    // Event refers to the job_ before it was initiated.
    // Upload the event, do not post a new one.
    Complete();
    return;
  }
  // Job in progress check what was uploaded.
  if (job_->tracker().uploaded() >
      log_upload_event_.upload_tracker().uploaded()) {
    // The `job_` is more advanced than the event implies.
    // Upload the event, do not post a new one.
    Complete();
    return;
  }
  if (job_->tracker().uploaded() <
      log_upload_event_.upload_tracker().uploaded()) {
    // The `job_` is less advanced than the event implies, it should not be
    // possible unless the `job_` is corrupt.
    LOG(WARNING) << "Corrupt FileUploadJob";
    // Upload the event, do not post a new one.
    Complete();
    return;
  }
  // Exact match, resume the `job_`. Note that if the `job_` is already
  // active, this will be a no-op.
  if (job_->tracker().uploaded() < job_->tracker().total()) {
    // Job in progress, perform next step. Upon completion success post new
    // event and upload the current one.
    job_->NextStep(scoped_reservation,
                   base::BindOnce(&EventHelper::RepostAndComplete,
                                  weak_ptr_factory_.GetWeakPtr()));
    return;
  }
  // Upload complete, finalize the job_.
  job_->Finalize(base::BindOnce(&EventHelper::RepostAndComplete,
                                weak_ptr_factory_.GetWeakPtr()));
}

void FileUploadJob::EventHelper::Complete(Status status) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(done_cb_);
  std::move(done_cb_).Run(status);
  // Disconnect from the job, self destruct.
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_->job_sequence_checker_);
  job_->event_helper_.reset();
}

void FileUploadJob::EventHelper::RepostAndComplete() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Post a new event reflecting its state to track later.
  // If `job_` is not available, do not allow to upload the current event.
  if (!job_) {
    Complete(Status(error::DATA_LOSS, "Upload Job has been removed"));
    base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
                                  DataLossErrorReason::UPLOAD_JOB_REMOVED,
                                  DataLossErrorReason::MAX_VALUE);
    return;
  }
  // Job is still around.
  // If the job failed, post retry event unless retry count will drop to 0.
  if (job_->tracker().has_status() && job_->settings().retry_count() > 1) {
    // Post retry event.
    PostRetry();
  }

  // Update the new event with its status.
  if (job_->tracker().access_parameters().empty() &&
      !job_->tracker().has_status()) {
    // The job_ is in progress (not succeeded and not failed),
    // flag the new tracking event to be processed when reaching uploader.
    record_copy_.set_needs_local_unencrypted_copy(true);
  }
  // Copy its tracking state to the new event.
  *log_upload_event_.mutable_upload_settings() = job_->settings();
  *log_upload_event_.mutable_upload_tracker() = job_->tracker();
  // Patch the copy event.
  if (!log_upload_event_.SerializeToString(record_copy_.mutable_data())) {
    Complete(Status(error::INVALID_ARGUMENT,
                    base::StrCat({"Updated event ",
                                  Destination_Name(record_copy_.destination()),
                                  " failed to serialize"})));
    return;
  }
  // Repost the copy event and return result via `Complete`.
  FileUploadJob::AddRecordToStorage(
      priority_, std::move(record_copy_),
      base::BindPostTaskToCurrentDefault(base::BindOnce(
          &EventHelper::Complete, weak_ptr_factory_.GetWeakPtr())));
}

void FileUploadJob::EventHelper::PostRetry() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(job_);
  // Compose retry event that has no tracker.
  // Decrement its retry count (`FileUploadJob::Manager::Register` will then
  // register it as a new job).
  auto retry_log_upload_event = log_upload_event_;
  *retry_log_upload_event.mutable_upload_settings() = job_->settings();
  retry_log_upload_event.mutable_upload_settings()->set_retry_count(
      retry_log_upload_event.upload_settings().retry_count() - 1);
  retry_log_upload_event.clear_upload_tracker();
  // Make a copy for retry.
  Record retry_copy = record_copy_;
  retry_copy.set_needs_local_unencrypted_copy(true);
  // Serialize and post retry event; if it fails, just log the error.
  if (!retry_log_upload_event.SerializeToString(retry_copy.mutable_data())) {
    LOG(ERROR) << "Retry event " << Destination_Name(retry_copy.destination())
               << " failed to serialize";
    return;
  }
  FileUploadJob::AddRecordToStorage(
      priority_, std::move(retry_copy), base::BindOnce([](Status status) {
        LOG_IF(ERROR, !status.ok()) << "Retry event failed to post: " << status;
      }));
}

// FileUploadJob implementation.

FileUploadJob::FileUploadJob(const UploadSettings& settings,
                             const UploadTracker& tracker,
                             Delegate::SmartPtr delegate)
    : delegate_(std::move(delegate)), settings_(settings), tracker_(tracker) {}

FileUploadJob::~FileUploadJob() = default;

base::ScopedClosureRunner FileUploadJob::CompletionCb(
    base::OnceClosure done_cb) {
  return base::ScopedClosureRunner(
      base::BindPostTaskToCurrentDefault(base::BindOnce(
          [](base::WeakPtr<FileUploadJob> job, base::OnceClosure done_cb) {
            // If `job` has not been destructed yet,
            // analyze its status and delete the file, if it has completed with
            // success or the last retry failed.
            if (job) {
              DCHECK_CALLED_ON_VALID_SEQUENCE(job->job_sequence_checker_);
              CHECK(job->event_helper_)
                  << "Event must be associated with the job";
              if (!job->tracker_.access_parameters().empty() ||  // success
                  (job->tracker_.has_status() &&
                   job->settings_.retry_count() <= 1)) {  // last retry
                // Perform deletion on a thread pool, do not wait for completion
                // and do not report the outcome.
                Manager::GetInstance()->sequenced_task_runner()->PostTask(
                    FROM_HERE,
                    base::BindOnce(&Delegate::DoDeleteFile,
                                   job->delegate_->GetWeakPtr(),
                                   std::string(job->settings_.origin_path())));
              }
            }
            // Execute the callback regardless of whether the `job` exists or
            // not.
            std::move(done_cb).Run();
          },
          weak_ptr_factory_.GetWeakPtr(), std::move(done_cb))));
}

void FileUploadJob::Initiate(base::OnceClosure done_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  CHECK(event_helper_) << "Event must be associated with the job";
  base::ScopedClosureRunner done(
      FileUploadJob::CompletionCb(std::move(done_cb)));
  if (tracker_.has_status()) {
    // Error detected earlier.
    return;
  }
  if (!tracker_.session_token().empty()) {
    Status{error::FAILED_PRECONDITION, "Job has already been initiated"}.SaveTo(
        tracker_.mutable_status());
    return;
  }
  if (settings_.retry_count() < 1) {
    Status{error::OUT_OF_RANGE, "Too many upload attempts"}.SaveTo(
        tracker_.mutable_status());
    return;
  }
  if (timer_.IsRunning()) {
    timer_.Reset();
  }
  Manager::GetInstance()->sequenced_task_runner()->PostTask(
      FROM_HERE,
      base::BindOnce(&CallInitiateOnSequence, delegate_->GetWeakPtr(),
                     settings_.origin_path(), settings_.upload_parameters(),
                     base::BindPostTaskToCurrentDefault(base::BindOnce(
                         &FileUploadJob::DoneInitiate,
                         weak_ptr_factory_.GetWeakPtr(), std::move(done)))));
}

void FileUploadJob::DoneInitiate(
    base::ScopedClosureRunner done,
    StatusOr<std::pair<int64_t /*total*/, std::string /*session_token*/>>
        result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  CHECK(event_helper_) << "Event must be associated with the job";
  if (!result.has_value()) {
    result.error().SaveTo(tracker_.mutable_status());
    return;
  }
  int64_t total = 0L;
  std::string_view session_token;
  std::tie(total, session_token) = result.value();
  if (total <= 0L) {
    Status{error::FAILED_PRECONDITION, "Empty upload"}.SaveTo(
        tracker_.mutable_status());
    return;
  }
  if (session_token.empty()) {
    Status{error::FAILED_PRECONDITION, "Session token not created"}.SaveTo(
        tracker_.mutable_status());
    return;
  }
  tracker_.set_total(total);
  tracker_.set_uploaded(0L);
  tracker_.set_session_token(session_token.data(), session_token.size());
}

void FileUploadJob::NextStep(const ScopedReservation& scoped_reservation,
                             base::OnceClosure done_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  CHECK(event_helper_) << "Event must be associated with the job";
  base::ScopedClosureRunner done(
      FileUploadJob::CompletionCb(std::move(done_cb)));
  if (tracker_.has_status()) {
    // Error detected earlier.
    return;
  }
  if (tracker_.session_token().empty()) {
    Status{error::FAILED_PRECONDITION, "Job has not been initiated yet"}.SaveTo(
        tracker_.mutable_status());
    return;
  }
  if (tracker_.uploaded() < 0L || tracker_.uploaded() > tracker_.total()) {
    Status{error::OUT_OF_RANGE,
           base::StrCat({"Uploaded ", base::NumberToString(tracker_.uploaded()),
                         " out of range"})}
        .SaveTo(tracker_.mutable_status());
    return;
  }
  if (tracker_.uploaded() == tracker_.total()) {
    // All done, Status is OK.
    return;
  }
  if (timer_.IsRunning()) {
    timer_.Reset();
  }
  Manager::GetInstance()->sequenced_task_runner()->PostTask(
      FROM_HERE,
      base::BindOnce(&CallNextStepOnSequence, delegate_->GetWeakPtr(),
                     tracker_.total(), tracker_.uploaded(),
                     tracker_.session_token(),
                     ScopedReservation(0uL, scoped_reservation),
                     base::BindPostTaskToCurrentDefault(base::BindOnce(
                         &FileUploadJob::DoneNextStep,
                         weak_ptr_factory_.GetWeakPtr(), std::move(done)))));
}

void FileUploadJob::DoneNextStep(
    base::ScopedClosureRunner done,
    StatusOr<std::pair<int64_t /*uploaded*/, std::string /*session_token*/>>
        result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  CHECK(event_helper_) << "Event must be associated with the job";
  if (!result.has_value()) {
    result.error().SaveTo(tracker_.mutable_status());
    return;
  }
  int64_t uploaded = 0L;
  std::string_view session_token;
  std::tie(uploaded, session_token) = result.value();
  if (session_token.empty()) {
    Status{error::DATA_LOSS, "Job has lost session_token"}.SaveTo(
        tracker_.mutable_status());
    base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
                                  DataLossErrorReason::JOB_LOST_SESSION_TOKEN,
                                  DataLossErrorReason::MAX_VALUE);
    return;
  }
  if (uploaded < tracker_.uploaded()) {
    Status{error::DATA_LOSS,
           base::StrCat({"Job has backtracked from ",
                         base::NumberToString(tracker_.uploaded()), " to ",
                         base::NumberToString(uploaded)})}
        .SaveTo(tracker_.mutable_status());
    base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
                                  DataLossErrorReason::JOB_BACKTRACKED,
                                  DataLossErrorReason::MAX_VALUE);
    return;
  }
  tracker_.set_uploaded(uploaded);
  tracker_.set_session_token(session_token.data(), session_token.size());
}

void FileUploadJob::Finalize(base::OnceClosure done_cb) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  CHECK(event_helper_) << "Event must be associated with the job";
  base::ScopedClosureRunner done(
      FileUploadJob::CompletionCb(std::move(done_cb)));
  if (tracker_.has_status()) {
    // Error detected earlier.
    return;
  }
  if (tracker_.session_token().empty()) {
    Status{error::FAILED_PRECONDITION, "Job has not been initiated yet"}.SaveTo(
        tracker_.mutable_status());
    return;
  }
  if (tracker_.uploaded() < tracker_.total()) {
    Status{error::DATA_LOSS,
           base::StrCat({"Upload incomplete ",
                         base::NumberToString(tracker_.uploaded()), " out of ",
                         base::NumberToString(tracker_.total())})}
        .SaveTo(tracker_.mutable_status());
    base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
                                  DataLossErrorReason::JOB_INCOMPLETE,
                                  DataLossErrorReason::MAX_VALUE);
    return;
  }
  if (timer_.IsRunning()) {
    timer_.Reset();
  }

  Manager::GetInstance()->sequenced_task_runner()->PostTask(
      FROM_HERE,
      base::BindOnce(&CallFinalizeOnSequence, delegate_->GetWeakPtr(),
                     tracker_.session_token(),
                     base::BindPostTaskToCurrentDefault(base::BindOnce(
                         &FileUploadJob::DoneFinalize,
                         weak_ptr_factory_.GetWeakPtr(), std::move(done)))));
}

void FileUploadJob::DoneFinalize(
    base::ScopedClosureRunner done,
    StatusOr<std::string /*access_parameters*/> result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  CHECK(event_helper_) << "Event must be associated with the job";
  if (!result.has_value()) {
    result.error().SaveTo(tracker_.mutable_status());
    return;
  }
  std::string_view access_parameters = result.value();
  if (access_parameters.empty()) {
    Status{error::FAILED_PRECONDITION, "Access parameters not set"}.SaveTo(
        tracker_.mutable_status());
    return;
  }
  tracker_.clear_session_token();
  tracker_.set_access_parameters(access_parameters.data(),
                                 access_parameters.size());
}

// static
void FileUploadJob::AddRecordToStorage(
    Priority priority,
    Record record_copy,
    base::OnceCallback<void(Status)> done_cb) {
  ReportQueueProvider::GetInstance()->sequenced_task_runner()->PostTask(
      FROM_HERE, base::BindOnce(
                     [](Priority priority, Record record_copy,
                        base::OnceCallback<void(Status)> done_cb) {
                       // We can only get to here from upload, which originates
                       // from Storage Module, so `storage()` below cannot be
                       // null.
                       const auto storage =
                           ReportQueueProvider::GetInstance()->storage();
                       CHECK(storage);
                       storage->AddRecord(priority, std::move(record_copy),
                                          std::move(done_cb));
                     },
                     priority, std::move(record_copy), std::move(done_cb)));
}

void FileUploadJob::SetEventHelperForTest(
    std::unique_ptr<FileUploadJob::EventHelper> event_helper) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  event_helper_ = std::move(event_helper);
}

FileUploadJob::EventHelper* FileUploadJob::event_helper() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  return event_helper_.get();
}

const UploadSettings& FileUploadJob::settings() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  return settings_;
}

const UploadTracker& FileUploadJob::tracker() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(job_sequence_checker_);
  return tracker_;
}

base::WeakPtr<FileUploadJob> FileUploadJob::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}
}  // namespace reporting
