// 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 "chrome/browser/ui/webui/help/version_updater.h"

#import <Foundation/Foundation.h>

#include <algorithm>
#include <memory>
#include <string>
#include <utility>

#include "base/apple/foundation_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/mac/authorization_util.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/version.h"
#include "chrome/browser/updater/updater.h"
#include "chrome/browser/upgrade_detector/upgrade_detector.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/updater_scope.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_mac.h"

namespace {

int GetDownloadProgress(int64_t downloaded_bytes, int64_t total_bytes) {
  if (downloaded_bytes < 0 || total_bytes <= 0) {
    return -1;
  }
  return 100 * std::clamp(static_cast<double>(downloaded_bytes) / total_bytes,
                          0.0, 1.0);
}

void UpdateStatus(VersionUpdater::StatusCallback status_callback,
                  const updater::UpdateService::UpdateState& update_state) {
  VersionUpdater::Status status = VersionUpdater::Status::CHECKING;
  int progress = 0;
  std::string version;
  std::u16string err_message;

  switch (update_state.state) {
    case updater::UpdateService::UpdateState::State::kCheckingForUpdates:
      [[fallthrough]];
    case updater::UpdateService::UpdateState::State::kUpdateAvailable:
      status = VersionUpdater::Status::CHECKING;
      break;
    case updater::UpdateService::UpdateState::State::kDownloading:
      progress = GetDownloadProgress(update_state.downloaded_bytes,
                                     update_state.total_bytes);
      [[fallthrough]];
    case updater::UpdateService::UpdateState::State::kDecompressing:
      [[fallthrough]];
    case updater::UpdateService::UpdateState::State::kPatching:
      [[fallthrough]];
    case updater::UpdateService::UpdateState::State::kInstalling:
      status = VersionUpdater::Status::UPDATING;
      break;
    case updater::UpdateService::UpdateState::State::kUpdated:
      status = VersionUpdater::Status::NEARLY_UPDATED;
      break;
    case updater::UpdateService::UpdateState::State::kNoUpdate:
      status = UpgradeDetector::GetInstance()->is_upgrade_available()
                   ? VersionUpdater::Status::NEARLY_UPDATED
                   : VersionUpdater::Status::UPDATED;
      break;
    case updater::UpdateService::UpdateState::State::kUpdateError:
      switch (update_state.error_code) {
        case updater::GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY:
          status = VersionUpdater::Status::DISABLED_BY_ADMIN;
          err_message =
              l10n_util::GetStringUTF16(IDS_UPGRADE_DISABLED_BY_POLICY);
          break;
        case updater::GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL:
          status = VersionUpdater::Status::DISABLED_BY_ADMIN;
          err_message =
              l10n_util::GetStringUTF16(IDS_UPGRADE_DISABLED_BY_POLICY_MANUAL);
          break;
        default:
          status = VersionUpdater::Status::FAILED;
          err_message = l10n_util::GetStringFUTF16(
              IDS_ABOUT_BOX_ERROR_DURING_UPDATE_CHECK,
              l10n_util::GetStringFUTF16(IDS_ABOUT_BOX_GOOGLE_UPDATE_ERROR,
                                         base::UTF8ToUTF16(base::StringPrintf(
                                             "%d", update_state.error_code)),
                                         base::UTF8ToUTF16(base::StringPrintf(
                                             "%d", update_state.extra_code1))));
          break;
      }
      break;
    case updater::UpdateService::UpdateState::State::kNotStarted:
      [[fallthrough]];
    case updater::UpdateService::UpdateState::State::kUnknown:
      return;
  }

  status_callback.Run(status, progress, false, false, version, 0, err_message);
}

// macOS implementation of version update functionality, used by the WebUI
// About/Help page.
class VersionUpdaterMac : public VersionUpdater {
 public:
  VersionUpdaterMac() = default;
  VersionUpdaterMac(const VersionUpdaterMac&) = delete;
  VersionUpdaterMac& operator=(const VersionUpdaterMac&) = delete;
  ~VersionUpdaterMac() override = default;

  // VersionUpdater implementation.
  void CheckForUpdate(StatusCallback status_callback,
                      PromoteCallback promote_callback) override {
    updater::EnsureUpdater(
        base::TaskPriority::USER_VISIBLE,
        base::BindOnce(promote_callback, PromotionState::PROMOTE_ENABLED),
        base::BindOnce(&updater::CheckForUpdate,
                       base::BindRepeating(&UpdateStatus, status_callback)));
  }
  void PromoteUpdater() override { updater::SetUpSystemUpdater(); }
};

}  // namespace

std::unique_ptr<VersionUpdater> VersionUpdater::Create(
    content::WebContents* /* web_contents */) {
  return base::WrapUnique(new VersionUpdaterMac());
}
