// 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 "components/browsing_data/core/browsing_data_policies_utils.h"

#include <algorithm>
#include <forward_list>

#include "base/containers/fixed_flat_map.h"
#include "base/containers/span.h"
#include "base/strings/stringprintf.h"
#include "components/browsing_data/core/browsing_data_utils.h"
#include "components/browsing_data/core/pref_names.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_prefs.h"

namespace browsing_data {

namespace {

namespace policy_data_types {
// Data retention policy types that require sync to be disabled.
constexpr char kBrowsingHistoryName[] = "browsing_history";
constexpr char kPasswordSigninName[] = "password_signin";
constexpr char kAutofillName[] = "autofill";
constexpr char kSiteSettingsName[] = "site_settings";
// Data retention policy types that do not require sync to be disabled.
constexpr char kHostedAppDataName[] = "hosted_app_data";
constexpr char kDownloadHistoryName[] = "download_history";
constexpr char kCookiesAndOtherSiteDataName[] = "cookies_and_other_site_data";
constexpr char kCachedImagesAndFilesName[] = "cached_images_and_files";
}  // namespace policy_data_types

// The format of the log message shown in chrome://policy/logs when sync types
// are automatically disabled.
constexpr char kDisabledSyncTypesLogFormat[] =
    "The policy %s automatically disabled the following sync types: %s";

// Adds the sync type for the `browsing_data_type` to  `sync_types` if it
// should be disabled.
void AppendSyncTypesIfRequired(const base::Value& browsing_data_type,
                               syncer::UserSelectableTypeSet* sync_types) {
  // Map of browsing data types to sync types that need to be disabled for
  // them.
  static constexpr auto kDataToSyncTypesMap =
      base::MakeFixedFlatMap<std::string_view, syncer::UserSelectableTypeSet>(
          {{policy_data_types::kBrowsingHistoryName,
            {syncer::UserSelectableType::kHistory,
             syncer::UserSelectableType::kTabs,
             syncer::UserSelectableType::kSavedTabGroups}},
           {policy_data_types::kPasswordSigninName,
            {syncer::UserSelectableType::kPasswords}},
           {policy_data_types::kSiteSettingsName,
            {syncer::UserSelectableType::kPreferences}},
           {policy_data_types::kAutofillName,
            {syncer::UserSelectableType::kAutofill,
             syncer::UserSelectableType::kPayments}},
           {policy_data_types::kDownloadHistoryName, {}},
           {policy_data_types::kCookiesAndOtherSiteDataName,
            {syncer::UserSelectableType::kCookies}},
           {policy_data_types::kCachedImagesAndFilesName, {}},
           {policy_data_types::kHostedAppDataName, {}}});

  // When a new sync type or browsing data type is introduced in the code,
  // kDataToSyncTypesMap should be updated if needed to ensure that browsing
  // data that can be cleared by policy is not already synced across devices.
  static_assert(static_cast<int>(syncer::UserSelectableType::kLastType) == 13,
                "It looks like a sync type was added or removed. Please update "
                "`kDataToSyncTypesMap` value maps above if it affects any of "
                "the browsing data types.");

  static_assert(
      static_cast<int>(PolicyDataType::kNumTypes) ==
          static_cast<int>(kDataToSyncTypesMap.size()),
      "It looks like a browsing data type that can be managed by policy was "
      "added or removed. Please update `kDataToSyncTypesMap` above to include "
      "the new type and the sync types it maps to if this data is synced.");

  const auto it = kDataToSyncTypesMap.find(browsing_data_type.GetString());
  if (it == kDataToSyncTypesMap.end()) {
    return;
  }
  for (const syncer::UserSelectableType sync_type_needed : it->second) {
    sync_types->Put(sync_type_needed);
  }
}

}  // namespace

syncer::UserSelectableTypeSet GetSyncTypesForClearBrowsingData(
    const base::Value& policy_value) {
  // The use of GetList() without type checking is safe because this
  // function is only called if the policy schema is valid.
  const auto& items = policy_value.GetList();
  syncer::UserSelectableTypeSet sync_types;
  for (const auto& item : items) {
    AppendSyncTypesIfRequired(item, &sync_types);
  }
  return sync_types;
}

syncer::UserSelectableTypeSet GetSyncTypesForBrowsingDataLifetime(
    const base::Value& policy_value) {
  // The use of GetList() and GetDict() without type checking are safe because
  // this function is only called if the policy schema is valid.
  const auto& items = policy_value.GetList();
  syncer::UserSelectableTypeSet sync_types;
  for (const auto& item : items) {
    const base::Value* data_types = item.GetDict().Find("data_types");
    for (const auto& type : data_types->GetList()) {
      AppendSyncTypesIfRequired(type, &sync_types);
    }
  }
  return sync_types;
}

std::string DisableSyncTypes(const syncer::UserSelectableTypeSet& types_set,
                             PrefValueMap* prefs,
                             const std::string& policy_name) {
  for (const syncer::UserSelectableType type : types_set) {
    syncer::SyncPrefs::SetTypeDisabledByPolicy(prefs, type);
  }
  if (types_set.size() > 0) {
    return base::StringPrintf(kDisabledSyncTypesLogFormat, policy_name.c_str(),
                              UserSelectableTypeSetToString(types_set).c_str());
  }
  return std::string();
}

base::flat_set<std::string> GetBrowsingDataLifetimePlatformUnsupportedTypes(
    const base::Value& policy_value) {
#if !BUILDFLAG(IS_ANDROID)
  return base::flat_set<std::string>();
#else
  // The use of GetList() and GetDict() without type checking are safe because
  // this function is only called if the policy schema is valid.
  // On Android, hosted_app_data and download_history are not supported for the
  // BrowsingDataLifetime policy, while the ClearBrowsingDataOnExit policy is
  // not supported at all.
  static constexpr std::string_view kUnsupportedTypes[] = {
      policy_data_types::kHostedAppDataName,
      policy_data_types::kDownloadHistoryName};
  std::forward_list<std::string> unsupported_types;
  const auto& items = policy_value.GetList();
  for (const auto& item : items) {
    const base::ListValue& data_types =
        item.GetDict().Find("data_types")->GetList();
    std::for_each(data_types.begin(), data_types.end(),
                  [&unsupported_types](const base::Value& type) {
                    const std::string& type_string = type.GetString();
                    if (std::ranges::contains(kUnsupportedTypes, type_string)) {
                      unsupported_types.push_front(type_string);
                    }
                  });
  }
  return base::flat_set<std::string>(unsupported_types.begin(),
                                     unsupported_types.end());
#endif  // !BUILDFLAG(IS_ANDROID)
}

std::optional<PolicyDataType> NameToPolicyDataType(
    const std::string& type_name) {
  static constexpr auto kNameToDataType =
      base::MakeFixedFlatMap<std::string_view, PolicyDataType>({
          {policy_data_types::kBrowsingHistoryName,
           PolicyDataType::kBrowsingHistory},
          {policy_data_types::kPasswordSigninName,
           PolicyDataType::kPasswordSignin},
          {policy_data_types::kAutofillName, PolicyDataType::kAutofill},
          {policy_data_types::kSiteSettingsName, PolicyDataType::kSiteSettings},
          {policy_data_types::kHostedAppDataName,
           PolicyDataType::kHostedAppData},
          {policy_data_types::kDownloadHistoryName,
           PolicyDataType::kDownloadHistory},
          {policy_data_types::kCookiesAndOtherSiteDataName,
           PolicyDataType::kCookiesAndOtherSiteData},
          {policy_data_types::kCachedImagesAndFilesName,
           PolicyDataType::kCachedImagesAndFiles},
      });

  const auto it = kNameToDataType.find(type_name);
  if (it == kNameToDataType.end()) {
    return std::nullopt;
  }
  return it->second;
}

}  // namespace browsing_data
