// Copyright 2022 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/updater/policy/policy_manager.h"

#include <algorithm>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "base/containers/flat_set.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/updater/get_updater_scope.h"
#include "chrome/updater/policy/manager.h"

namespace updater {

namespace {

constexpr char kCloudPolicyOverridesPlatformPolicy[] =
    "CloudPolicyOverridesPlatformPolicy";

// Preferences Category.
constexpr char kAutoUpdateCheckPeriodOverrideMinutes[] =
    "AutoUpdateCheckPeriodMinutes";
constexpr char kUpdatesSuppressedStartHour[] = "UpdatesSuppressedStartHour";
constexpr char kUpdatesSuppressedStartMin[] = "UpdatesSuppressedStartMin";
constexpr char kUpdatesSuppressedDurationMin[] = "UpdatesSuppressedDurationMin";

// This policy specifies what kind of download URLs could be returned to the
// client in the update response and in which order of priority. The client
// provides this information in the update request as a hint for the server.
// The server may decide to ignore the hint. As a general idea, some urls are
// cacheable, some urls have higher bandwidth, and some urls are slightly more
// secure since they are https.
constexpr char kDownloadPreference[] = "DownloadPreference";

// Proxy Server Category.  (The keys used, and the values of ProxyMode,
// directly mirror that of Chrome.  However, we omit ProxyBypassList, as the
// domains that Omaha uses are largely fixed.)
constexpr char kProxyMode[] = "ProxyMode";
constexpr char kProxyServer[] = "ProxyServer";
constexpr char kProxyPacUrl[] = "ProxyPacUrl";

// Package cache constants.
constexpr char kCacheSizeLimitMBytes[] = "PackageCacheSizeLimit";
constexpr char kCacheLifeLimitDays[] = "PackageCacheLifeLimit";

// Applications Category.
// The prefix strings have the app's GUID appended to them.
constexpr char kInstallAppsDefault[] = "installdefault";
constexpr char kInstallAppPrefix[] = "install";
constexpr char kUpdateAppsDefault[] = "updatedefault";
constexpr char kUpdateAppPrefix[] = "update";
constexpr char kTargetVersionPrefix[] = "targetversionprefix";
constexpr char kTargetChannel[] = "targetchannel";
constexpr char kRollbackToTargetVersion[] = "RollbackToTargetVersion";
constexpr char kMajorVersionRolloutPrefix[] = "MajorVersionRollout";
constexpr char kMinorVersionRolloutPrefix[] = "MinorVersionRollout";

}  // namespace

PolicyManager::PolicyManager(base::DictValue policies)
    : policies_(std::move(policies)) {
  static constexpr size_t kInstallAppPrefixLength =
      std::string_view(kInstallAppPrefix).length();
  std::ranges::for_each(policies_, [&](const auto& policy) {
    const std::string policy_name = policy.first;
    VLOG_IF(1, policy_name != base::ToLowerASCII(policy_name))
        << "Policy [" << policy_name
        << "] is ignored because it's not all lower-case.";
    if (policy_name.length() <= kInstallAppPrefixLength ||
        !base::StartsWith(policy_name, kInstallAppPrefix,
                          base::CompareCase::INSENSITIVE_ASCII) ||
        base::StartsWith(policy_name, kInstallAppsDefault,
                         base::CompareCase::INSENSITIVE_ASCII) ||
        !policy.second.is_int()) {
      return;
    }

    if (policy.second.GetInt() != (IsSystemInstall()
                                       ? kPolicyForceInstallMachine
                                       : kPolicyForceInstallUser)) {
      return;
    }

    force_install_apps_.push_back(policy_name.substr(kInstallAppPrefixLength));
  });
}

PolicyManager::~PolicyManager() = default;

std::optional<bool> PolicyManager::CloudPolicyOverridesPlatformPolicy() const {
  std::optional<int> policy =
      GetIntegerPolicy(kCloudPolicyOverridesPlatformPolicy);
  return policy ? std::optional<bool>(policy.value()) : std::nullopt;
}

bool PolicyManager::HasActiveDevicePolicies() const {
  return true;
}

std::string PolicyManager::source() const {
  return kSourceDictValuesPolicyManager;
}

std::optional<base::TimeDelta> PolicyManager::GetLastCheckPeriod() const {
  std::optional<int> minutes =
      GetIntegerPolicy(kAutoUpdateCheckPeriodOverrideMinutes);
  if (!minutes) {
    return std::nullopt;
  }
  return base::Minutes(*minutes);
}

std::optional<UpdatesSuppressedTimes> PolicyManager::GetUpdatesSuppressedTimes()
    const {
  std::optional<int> start_hour = GetIntegerPolicy(kUpdatesSuppressedStartHour);
  std::optional<int> start_min = GetIntegerPolicy(kUpdatesSuppressedStartMin);
  std::optional<int> duration_min =
      GetIntegerPolicy(kUpdatesSuppressedDurationMin);

  if (!start_hour || !start_min || !duration_min) {
    return std::nullopt;
  }

  UpdatesSuppressedTimes supressed_times;
  supressed_times.start_hour_ = start_hour.value();
  supressed_times.start_minute_ = start_min.value();
  supressed_times.duration_minute_ = duration_min.value();
  return supressed_times;
}

std::optional<std::string> PolicyManager::GetDownloadPreference() const {
  return GetStringPolicy(kDownloadPreference);
}

std::optional<int> PolicyManager::GetPackageCacheSizeLimitMBytes() const {
  return GetIntegerPolicy(kCacheSizeLimitMBytes);
}

std::optional<int> PolicyManager::GetPackageCacheExpirationTimeDays() const {
  return GetIntegerPolicy(kCacheLifeLimitDays);
}

std::optional<int> PolicyManager::GetEffectivePolicyForAppInstalls(
    const std::string& app_id) const {
  std::optional<int> policy =
      GetIntegerPolicy(base::StrCat({kInstallAppPrefix, app_id}));
  return policy ? policy : GetIntegerPolicy(kInstallAppsDefault);
}

std::optional<int> PolicyManager::GetEffectivePolicyForAppUpdates(
    const std::string& app_id) const {
  std::optional<int> policy =
      GetIntegerPolicy(base::StrCat({kUpdateAppPrefix, app_id}));
  return policy ? policy : GetIntegerPolicy(kUpdateAppsDefault);
}

std::optional<std::string> PolicyManager::GetTargetChannel(
    const std::string& app_id) const {
  return GetStringPolicy(base::StrCat({kTargetChannel, app_id}));
}

std::optional<std::string> PolicyManager::GetTargetVersionPrefix(
    const std::string& app_id) const {
  return GetStringPolicy(base::StrCat({kTargetVersionPrefix, app_id}));
}

std::optional<bool> PolicyManager::IsRollbackToTargetVersionAllowed(
    const std::string& app_id) const {
  std::optional<int> policy =
      GetIntegerPolicy(base::StrCat({kRollbackToTargetVersion, app_id}));
  return policy ? std::optional<bool>(policy.value()) : std::nullopt;
}

std::optional<int> PolicyManager::GetMajorVersionRolloutPolicy(
    const std::string& app_id) const {
  return GetIntegerPolicy(base::StrCat({kMajorVersionRolloutPrefix, app_id}));
}

std::optional<int> PolicyManager::GetMinorVersionRolloutPolicy(
    const std::string& app_id) const {
  return GetIntegerPolicy(base::StrCat({kMinorVersionRolloutPrefix, app_id}));
}

std::optional<std::string> PolicyManager::GetProxyMode() const {
  return GetStringPolicy(kProxyMode);
}

std::optional<std::string> PolicyManager::GetProxyPacUrl() const {
  return GetStringPolicy(kProxyPacUrl);
}

std::optional<std::string> PolicyManager::GetProxyServer() const {
  return GetStringPolicy(kProxyServer);
}

std::optional<std::vector<std::string>> PolicyManager::GetForceInstallApps()
    const {
  return force_install_apps_.empty() ? std::optional<std::vector<std::string>>()
                                     : force_install_apps_;
}

std::optional<std::vector<std::string>> PolicyManager::GetAppsWithPolicy()
    const {
  const base::flat_set<std::string> prefixed_policy_names = {
      // prefixed by kUpdateAppPrefix:
      base::ToLowerASCII(kUpdatesSuppressedStartHour),
      base::ToLowerASCII(kUpdatesSuppressedStartMin),
      base::ToLowerASCII(kUpdatesSuppressedDurationMin),
  };
  static constexpr const char* kAppPolicyPrefixes[] = {
      kInstallAppsDefault,       kInstallAppPrefix,
      kUpdateAppsDefault,        kUpdateAppPrefix,
      kTargetVersionPrefix,      kTargetChannel,
      kRollbackToTargetVersion,  kMajorVersionRolloutPrefix,
      kMinorVersionRolloutPrefix};
  std::vector<std::string> apps_with_policy;
  std::ranges::for_each(policies_, [&](const auto& policy) {
    const std::string policy_name = policy.first;
    std::ranges::for_each(kAppPolicyPrefixes, [&](const auto& prefix) {
      if (base::StartsWith(policy_name, base::ToLowerASCII(prefix)) &&
          !prefixed_policy_names.contains(policy_name)) {
        apps_with_policy.push_back(
            policy_name.substr(std::string_view(prefix).length()));
      }
    });
  });

  return apps_with_policy;
}

std::optional<int> PolicyManager::GetIntegerPolicy(
    const std::string& key) const {
  return policies_.FindInt(base::ToLowerASCII(key));
}

std::optional<std::string> PolicyManager::GetStringPolicy(
    const std::string& key) const {
  const std::string* policy = policies_.FindString(base::ToLowerASCII(key));
  return policy ? std::make_optional(*policy) : std::nullopt;
}

scoped_refptr<PolicyManagerInterface> CreateDictPolicyManager(
    base::DictValue policies) {
  return policies.empty()
             ? nullptr
             : base::MakeRefCounted<PolicyManager>(std::move(policies));
}

}  // namespace updater
