// Copyright 2026 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/update_client/delta_patch_operation.h"

#include <optional>
#include <string>
#include <utility>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/sequence_checker.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "components/update_client/crx_cache.h"
#include "components/update_client/patcher.h"
#include "components/update_client/pipeline_util.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/task_traits.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/utils.h"

namespace update_client {

DeltaPatchOperation::DeltaPatchOperation(
    scoped_refptr<CrxCache> crx_cache,
    base::RepeatingCallback<void(base::DictValue)> event_adder,
    base::RepeatingCallback<void(ComponentState)> state_tracker,
    const std::string& old_hash,
    uint32_t new_file_flags,
    const std::string& output_hash,
    int expected_success_result,
    const base::FilePath& patch_file,
    int event_type,
    bool is_foreground,
    base::OnceCallback<void(base::expected<base::FilePath, CategorizedError>)>
        callback)
    : crx_cache_(crx_cache),
      event_adder_(event_adder),
      state_tracker_(state_tracker),
      old_hash_(old_hash),
      new_file_flags_(new_file_flags),
      output_hash_(output_hash),
      expected_success_result_(expected_success_result),
      patch_file_(patch_file),
      event_type_(event_type),
      is_foreground_(is_foreground),
      callback_(std::move(callback)) {}

DeltaPatchOperation::~DeltaPatchOperation() = default;

void DeltaPatchOperation::Operation(
    base::OnceCallback<
        void(base::File /*old_file*/,
             base::File /*patch_file*/,
             base::File /*output_file*/,
             base::OnceCallback<void(int)> /*completion_callback*/)> strategy) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  strategy_ = std::move(strategy);
  state_tracker_.Run(ComponentState::kPatching);

  crx_cache_->GetByHash(
      old_hash_, base::BindOnce(&DeltaPatchOperation::CacheLookupDone, this));
}

void DeltaPatchOperation::CacheLookupDone(
    base::expected<base::FilePath, UnpackerError> result) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!result.has_value()) {
    base::ThreadPool::PostTaskAndReply(
        FROM_HERE, kTaskTraits,
        base::BindOnce(
            [](const base::FilePath& patch_file) {
              DeleteFileAndEmptyParentDirectory(patch_file);
            },
            patch_file_),
        base::BindOnce(&DeltaPatchOperation::PatchDone, this,
                       base::unexpected<CategorizedError>(
                           {.category = ErrorCategory::kUnpack,
                            .code = std::to_underlying(result.error())})));
    return;
  }

  base::ThreadPool::CreateSequencedTaskRunner(
      is_foreground_ ? kTaskTraits : kTaskTraitsBackgroundDecompression)
      ->PostTask(FROM_HERE, base::BindOnce(&DeltaPatchOperation::Patch, this,
                                           result.value()));
}

void DeltaPatchOperation::Patch(const base::FilePath& old_file) {
  base::FilePath new_file =
      patch_file_.DirName().Append(FILE_PATH_LITERAL("delta_patch_out"));

  std::move(strategy_).Run(
      base::File(old_file, base::File::FLAG_OPEN | base::File::FLAG_READ |
                               base::File::FLAG_NO_FOLLOW),
      base::File(patch_file_, base::File::FLAG_OPEN | base::File::FLAG_READ |
                                  base::File::FLAG_NO_FOLLOW),
      base::File(new_file, new_file_flags_),
      base::BindOnce(&DeltaPatchOperation::VerifyAndCleanUp, this, new_file));
}

void DeltaPatchOperation::VerifyAndCleanUp(const base::FilePath& new_file,
                                           int result) {
  RetryFileOperation(&base::DeleteFile, patch_file_);

  const auto expected_filepath =
      [&] -> base::expected<base::FilePath, CategorizedError> {
    if (result != expected_success_result_) {
      return base::unexpected<CategorizedError>(
          {.category = ErrorCategory::kUnpack,
           .code = std::to_underlying(UnpackerError::kDeltaOperationFailure),
           .extra = result});
    }

    if (!VerifyFileHash256(new_file, output_hash_)) {
      return base::unexpected<CategorizedError>(
          {.category = ErrorCategory::kUnpack,
           .code = std::to_underlying(UnpackerError::kPatchOutHashMismatch)});
    }

    return new_file;
  }();

  if (!expected_filepath.has_value()) {
    DeleteFileAndEmptyParentDirectory(new_file);
  }

  original_sequence_->PostTask(
      FROM_HERE,
      base::BindOnce(&DeltaPatchOperation::PatchDone, this, expected_filepath));
}

void DeltaPatchOperation::PatchDone(
    base::expected<base::FilePath, CategorizedError> result) {
  event_adder_.Run(MakeSimpleOperationEvent(result, event_type_));
  std::move(callback_).Run(result);
}

}  // namespace update_client
