// Copyright 2020 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/extensions/extension_settings_overridden_dialog.h"

#include <set>
#include <utility>

#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/supports_user_data.h"
#include "base/time/time.h"
#include "base/types/to_address.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/extensions/extensions_overrides/simple_overrides.h"
#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/hats/survey_config.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/version_info/channel.h"
#include "components/version_info/version_info.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/install_prefs_helper.h"
#include "extensions/browser/management_policy.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"

namespace {

using extensions::ExtensionId;

constexpr char kShownExtensionDataKey[] = "shown_for_extensions";

// A series of helpers to record which profiles the dialog has already shown
// for (in this session only).
struct ShownExtensionSet : public base::SupportsUserData::Data {
  std::set<ExtensionId> shown_ids;
};

ShownExtensionSet* GetShownExtensionSet(Profile& profile,
                                        bool create_if_missing) {
  auto* shown_set = static_cast<ShownExtensionSet*>(
      profile.GetUserData(kShownExtensionDataKey));
  if (!shown_set && create_if_missing) {
    auto new_shown_set = std::make_unique<ShownExtensionSet>();
    shown_set = new_shown_set.get();
    profile.SetUserData(kShownExtensionDataKey, std::move(new_shown_set));
  }
  return shown_set;
}

void MarkShownFor(Profile& profile, const ExtensionId& id) {
  ShownExtensionSet* shown_set = GetShownExtensionSet(profile, true);
  DCHECK(shown_set);
  bool inserted = shown_set->shown_ids.insert(id).second;
  DCHECK(inserted);
}

}  // namespace

ExtensionSettingsOverriddenDialog::Params::Params(
    extensions::ExtensionId controlling_extension_id,
    std::string extension_name,
    const char* extension_acknowledged_preference_name,
    const char* dialog_result_histogram_name,
    ShowParams show_params)
    : controlling_extension_id(std::move(controlling_extension_id)),
      extension_name(std::move(extension_name)),
      extension_acknowledged_preference_name(
          extension_acknowledged_preference_name),
      dialog_result_histogram_name(dialog_result_histogram_name),
      content(std::move(show_params)) {}

ExtensionSettingsOverriddenDialog::Params::~Params() = default;
ExtensionSettingsOverriddenDialog::Params::Params(Params&& params) = default;

ExtensionSettingsOverriddenDialog::ExtensionSettingsOverriddenDialog(
    Params params,
    Profile& profile)
    : params_(std::move(params)), profile_(profile) {
  DCHECK(!params_.controlling_extension_id.empty());
}

ExtensionSettingsOverriddenDialog::~ExtensionSettingsOverriddenDialog() =
    default;

// static
void ExtensionSettingsOverriddenDialog::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterTimePref(kSimpleOverrideBeginConfirmationTimestamp,
                             base::Time());
}

// static
bool ExtensionSettingsOverriddenDialog::HasShownFor(Profile& profile,
                                                    const ExtensionId& id) {
  const ShownExtensionSet* shown_set = GetShownExtensionSet(profile, false);
  return shown_set && shown_set->shown_ids.count(id) > 0;
}

// static
bool ExtensionSettingsOverriddenDialog::ShouldShowForSimpleOverrideExtension(
    Profile& profile,
    const extensions::Extension& extension) {
  if (!base::FeatureList::IsEnabled(
          extensions_features::kSearchEngineUnconditionalDialog)) {
    // If the feature is disabled, clear the timestamp. This ensures that if the
    // feature is re-enabled later, the grandfathering timestamp will be reset
    // to the time of re-enabling. Any extensions installed while the feature
    // was disabled will be grandfathered.
    PrefService* prefs = profile.GetPrefs();
    prefs->ClearPref(kSimpleOverrideBeginConfirmationTimestamp);
    return false;
  }

  PrefService* prefs = profile.GetPrefs();
  base::Time enforcement_time =
      prefs->GetTime(kSimpleOverrideBeginConfirmationTimestamp);

  // If the preference is not set, this is the first time the new logic is
  // running. Set the timestamp to Now.
  if (enforcement_time.is_null()) {
    enforcement_time = base::Time::Now();
    prefs->SetTime(kSimpleOverrideBeginConfirmationTimestamp, enforcement_time);
  }

  base::Time install_time = extensions::GetFirstInstallTime(
      extensions::ExtensionPrefs::Get(&profile), extension.id());

  // If the extension was installed after the enforcement logic began,
  // show the dialog.
  return install_time >= enforcement_time;
}

// static
bool ExtensionSettingsOverriddenDialog::HasAcknowledgedExtension(
    Profile& profile,
    const ExtensionId& id,
    const std::string& extension_acknowledged_preference_name) {
  bool pref_state = false;
  return extensions::ExtensionPrefs::Get(&profile)->ReadPrefAsBoolean(
             id, extension_acknowledged_preference_name, &pref_state) &&
         pref_state;
}

bool ExtensionSettingsOverriddenDialog::ShouldShow() {
  if (params_.controlling_extension_id.empty()) {
    return false;
  }

  if (HasShownFor(*profile_, params_.controlling_extension_id)) {
    return false;
  }

  if (HasAcknowledgedExtension(
          *profile_, params_.controlling_extension_id,
          params_.extension_acknowledged_preference_name)) {
    return false;
  }

  const extensions::Extension* extension =
      extensions::ExtensionRegistry::Get(base::to_address(profile_))
          ->enabled_extensions()
          .GetByID(params_.controlling_extension_id);
  DCHECK(extension);

  // Don't display the dialog for force-installed extensions that can't be
  // disabled.
  if (extensions::ExtensionSystem::Get(base::to_address(profile_))
          ->management_policy()
          ->MustRemainEnabled(extension, nullptr)) {
    return false;
  }

  // Historically, "Simple Overrides" were exempt from this dialog. We are
  // removing that exemption, but we grandfather in extensions installed
  // before the policy change was enabled to prevent spamming existing users.
  // See bug: https://crbug.com/463711704.
  if (simple_overrides::IsSimpleOverrideExtension(*extension)) {
    return ShouldShowForSimpleOverrideExtension(*profile_, *extension);
  }

  return true;
}

SettingsOverriddenDialogController::ShowParams
ExtensionSettingsOverriddenDialog::GetShowParams() {
  DCHECK(ShouldShow());

  const extensions::Extension* extension =
      extensions::ExtensionRegistry::Get(base::to_address(profile_))
          ->enabled_extensions()
          .GetByID(params_.controlling_extension_id);

  DCHECK(extension);

  return params_.content;
}

void ExtensionSettingsOverriddenDialog::OnDialogWillBeShown() {
  DCHECK(ShouldShow());
  show_time_ = base::TimeTicks::Now();

  if (!params_.unlimited_shows) {
    MarkShownFor(*profile_, params_.controlling_extension_id);
  }
}

void ExtensionSettingsOverriddenDialog::HandleDialogResult(
    DialogResult result) {
  DCHECK(!params_.controlling_extension_id.empty());
  DCHECK(!HasAcknowledgedExtension(
      *profile_, params_.controlling_extension_id,
      params_.extension_acknowledged_preference_name));
  DCHECK(params_.unlimited_shows ||
         HasShownFor(*profile_, params_.controlling_extension_id));

  // It's possible the extension was removed or disabled while the dialog was
  // being displayed. If this is the case, bail early.
  if (!extensions::ExtensionRegistry::Get(base::to_address(profile_))
           ->enabled_extensions()
           .Contains(params_.controlling_extension_id)) {
    return;
  }

  switch (result) {
    case DialogResult::kChangeSettingsBack:
      DisableControllingExtension();
      break;
    case DialogResult::kKeepNewSettings:
      AcknowledgeControllingExtension();
      break;
    case DialogResult::kDialogDismissed:
    case DialogResult::kDialogClosedWithoutUserAction:
      // Do nothing; the dialog will display on the next run of Chrome.
      break;
  }

  base::UmaHistogramEnumeration(params_.dialog_result_histogram_name, result);

  if (dialog_result_callback_) {
    CHECK(base::FeatureList::IsEnabled(
        extensions_features::kSearchEngineExplicitChoiceDialog));
    std::move(dialog_result_callback_).Run(result);
  }

  if (params_.hats_survey_trigger) {
    base::TimeDelta duration = show_time_.is_null()
                                   ? base::TimeDelta()
                                   : base::TimeTicks::Now() - show_time_;
    MaybeShowHatsSurvey(
        base::to_address(profile_), *params_.hats_survey_trigger, result,
        duration, params_.extension_name, params_.controlling_extension_id);
  }
}

// static
void ExtensionSettingsOverriddenDialog::MaybeShowHatsSurvey(
    Profile* profile,
    const std::string& survey_trigger,
    DialogResult result,
    base::TimeDelta duration,
    const std::string& extension_name,
    const std::string& extension_id) {
  HatsService* hats_service =
      HatsServiceFactory::GetForProfile(profile, /*create_if_necessary=*/true);
  if (!hats_service) {
    return;
  }

  SurveyStringData data_map;
  data_map[kHatsPsdChannel] =
      std::string(version_info::GetChannelString(chrome::GetChannel()));

  std::string user_choice;
  switch (result) {
    case DialogResult::kChangeSettingsBack:
      user_choice = kHatsUserChoicePreviousProvider;
      break;
    case DialogResult::kKeepNewSettings:
      user_choice = kHatsUserChoiceNewProvider;
      break;
    case DialogResult::kDialogDismissed:
      user_choice = kHatsUserChoiceDismissed;
      break;
    case DialogResult::kDialogClosedWithoutUserAction:
      user_choice = kHatsUserChoiceClosedWithoutUserAction;
      break;
    default:
      NOTREACHED();
  }
  data_map[kHatsPsdUserChoice] = user_choice;

  data_map[kHatsPsdTimeDialogVisible] = base::NumberToString(
      ukm::GetExponentialBucketMinForUserTiming(duration.InSeconds()));
  data_map[kHatsPsdNewExtensionName] = extension_name;
  data_map[kHatsPsdNewExtensionId] = extension_id;

  hats_service->LaunchDelayedSurvey(survey_trigger, /*timeout_ms=*/5000, {},
                                    data_map);
}

void ExtensionSettingsOverriddenDialog::SetDialogResultCallback(
    DialogResultCallback callback) {
  CHECK(base::FeatureList::IsEnabled(
      extensions_features::kSearchEngineExplicitChoiceDialog));
  dialog_result_callback_ = std::move(callback);
}

void ExtensionSettingsOverriddenDialog::DisableControllingExtension() {
  extensions::ExtensionRegistrar::Get(base::to_address(profile_))
      ->DisableExtension(params_.controlling_extension_id,
                         {extensions::disable_reason::DISABLE_USER_ACTION});
}

void ExtensionSettingsOverriddenDialog::AcknowledgeControllingExtension() {
  extensions::ExtensionPrefs::Get(base::to_address(profile_))
      ->UpdateExtensionPref(params_.controlling_extension_id,
                            params_.extension_acknowledged_preference_name,
                            base::Value(true));
}
