// 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/ui/safety_hub/extensions_result.h"

#include <algorithm>
#include <memory>
#include <optional>

#include "base/notreached.h"
#include "base/values.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/extension_safety_check_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/safety_hub/safety_hub_constants.h"
#include "chrome/browser/ui/safety_hub/safety_hub_result.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "extensions/browser/blocklist_extension_prefs.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension_set.h"
#include "ui/base/l10n/l10n_util.h"

namespace developer = extensions::api::developer_private;

SafetyHubExtensionsResult::SafetyHubExtensionsResult(
    std::set<extensions::ExtensionId> triggering_extensions,
    bool is_unpublished_extensions_only)
    : triggering_extensions_(triggering_extensions),
      is_unpublished_extensions_only_(is_unpublished_extensions_only) {}

SafetyHubExtensionsResult::SafetyHubExtensionsResult(
    const SafetyHubExtensionsResult&) = default;
SafetyHubExtensionsResult& SafetyHubExtensionsResult::operator=(
    const SafetyHubExtensionsResult&) = default;
SafetyHubExtensionsResult::~SafetyHubExtensionsResult() = default;

// static
std::optional<std::unique_ptr<SafetyHubResult>>
SafetyHubExtensionsResult::GetResult(Profile* profile,
                                     bool only_unpublished_extensions = false) {
  extensions::ExtensionRegistry* extension_registry =
      extensions::ExtensionRegistry::Get(profile);
  std::set<extensions::ExtensionId> triggering_extensions;
  const extensions::ExtensionSet all_installed_extensions =
      extension_registry->GenerateInstalledExtensionsSet();
  // If `only_unpublished_extensions` is true, GetSafetyCheckWarningReason
  // will ignore other extension warning types and only consider
  // unpublished extensions.
  for (const auto& extension : all_installed_extensions) {
    developer::SafetyCheckWarningReason warning_reason =
        extensions::ExtensionSafetyCheckUtils::GetSafetyCheckWarningReason(
            *extension.get(), profile, only_unpublished_extensions);
    if (warning_reason != developer::SafetyCheckWarningReason::kNone) {
      triggering_extensions.insert(std::move(extension->id()));
    }
  }
  return std::make_unique<SafetyHubExtensionsResult>(
      triggering_extensions, only_unpublished_extensions);
}

std::unique_ptr<SafetyHubResult> SafetyHubExtensionsResult::Clone() const {
  return std::make_unique<SafetyHubExtensionsResult>(*this);
}

void SafetyHubExtensionsResult::OnExtensionPrefsUpdated(
    const std::string& extension_id,
    Profile* profile) {
  auto extension_ptr = triggering_extensions_.find(extension_id);
  if (extension_ptr != triggering_extensions_.end()) {
    extensions::ExtensionRegistry* extension_registry =
        extensions::ExtensionRegistry::Get(profile);
    const extensions::Extension* extension =
        extension_registry->GetExtensionById(
            extension_id, extensions::ExtensionRegistry::EVERYTHING);
    // If the extension is NULL it has been uninstalled and should be
    // removed from `triggering_extensions_`.
    if (!extension) {
      triggering_extensions_.erase(extension_ptr);
      return;
    }
    developer::SafetyCheckWarningReason warning_reason =
        extensions::ExtensionSafetyCheckUtils::GetSafetyCheckWarningReason(
            *extension, profile);
    if (warning_reason == developer::SafetyCheckWarningReason::kNone) {
      triggering_extensions_.erase(extension_ptr);
    }
  }
}

void SafetyHubExtensionsResult::OnExtensionUninstalled(
    content::BrowserContext* browser_context,
    const extensions::Extension* extension,
    extensions::UninstallReason reason) {
  auto extension_ptr = triggering_extensions_.find(extension->id());
  if (extension_ptr != triggering_extensions_.end()) {
    triggering_extensions_.erase(extension_ptr);
  }
}

base::DictValue SafetyHubExtensionsResult::ToDictValue() const {
  // Only results that contain extensions that have been unpublished for a long
  // time should be serialized.
  CHECK(is_unpublished_extensions_only_);
  base::DictValue result = BaseToDictValue();
  base::ListValue extensions_list;
  for (const auto& triggering_extension : triggering_extensions_) {
    extensions_list.Append(triggering_extension);
  }
  result.Set(safety_hub::kSafetyHubTriggeringExtensionIdsKey,
             std::move(extensions_list));
  return result;
}

bool SafetyHubExtensionsResult::IsTriggerForMenuNotification() const {
  // Only results that have unpublished extensions can result in a menu
  // notification.
  return is_unpublished_extensions_only_ && !triggering_extensions_.empty();
}

unsigned int SafetyHubExtensionsResult::GetNumTriggeringExtensions() const {
  return triggering_extensions_.size();
}

bool SafetyHubExtensionsResult::WarrantsNewMenuNotification(
    const base::DictValue& previous_result_dict) const {
  std::set<extensions::ExtensionId> previous_triggering_extensions;
  for (const base::Value& extension_id : *previous_result_dict.FindList(
           safety_hub::kSafetyHubTriggeringExtensionIdsKey)) {
    previous_triggering_extensions.insert(extension_id.GetString());
  }
  // Only results that are for unpublished extensions can result in a menu
  // notification.
  if (!is_unpublished_extensions_only_) {
    return false;
  }
  return !std::ranges::includes(previous_triggering_extensions,
                                triggering_extensions_);
}

std::u16string SafetyHubExtensionsResult::GetNotificationString() const {
  CHECK(is_unpublished_extensions_only_);
  return l10n_util::GetPluralStringFUTF16(
      IDS_SETTINGS_SAFETY_HUB_EXTENSIONS_MENU_NOTIFICATION,
      triggering_extensions_.size());
}

int SafetyHubExtensionsResult::GetNotificationCommandId() const {
  CHECK(is_unpublished_extensions_only_);
  return IDC_SAFETY_HUB_MANAGE_EXTENSIONS;
}

void SafetyHubExtensionsResult::ClearTriggeringExtensionsForTesting() {
  triggering_extensions_.clear();
}

void SafetyHubExtensionsResult::SetTriggeringExtensionForTesting(
    std::string extension_id) {
  triggering_extensions_.insert(extension_id);
}
