// Copyright 2021 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/toolbar/chrome_labs/chrome_labs_utils.h"

#include <algorithm>

#include "base/base_switches.h"
#include "base/metrics/field_trial_params.h"
#include "base/rand_util.h"
#include "chrome/browser/about_flags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/flag_descriptions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/toolbar/chrome_labs/chrome_labs_prefs.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/channel_info.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/webui/flags/feature_entry.h"
#include "components/webui/flags/pref_service_flags_storage.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_switches.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#endif

namespace {
bool force_activation_for_testing = false;
}

bool IsFeatureSupportedOnChannel(const LabInfo& lab) {
  return chrome::GetChannel() <= lab.allowed_channel;
}

bool IsFeatureSupportedOnPlatform(const flags_ui::FeatureEntry* entry) {
  return entry && (entry->supported_platforms &
                   flags_ui::FlagsState::GetCurrentPlatform()) != 0;
}

bool IsChromeLabsFeatureValid(const LabInfo& lab, Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS)
  PrefService* prefs = profile->GetPrefs();
#else
  PrefService* prefs = g_browser_process->local_state();
#endif
  // Note: Both ChromeOS owner and non-owner use PrefServiceFlagsStorage under
  // the hood. OwnersFlagsStorage has additional functionalities for setting
  // flags but since we are just reading the storage assume non-owner case and
  // bypass asynchronous owner check.
  auto flags_storage =
      std::make_unique<flags_ui::PrefServiceFlagsStorage>(prefs);

  const flags_ui::FeatureEntry* entry =
      about_flags::GetCurrentFlagsState()->FindFeatureEntryByName(
          lab.internal_name);

  return IsFeatureSupportedOnChannel(lab) &&
         IsFeatureSupportedOnPlatform(entry) &&
         !about_flags::ShouldSkipConditionalFeatureEntry(flags_storage.get(),
                                                         *entry);
}

void UpdateChromeLabsNewBadgePrefs(Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS)
  ScopedDictPrefUpdate update(
      profile->GetPrefs(), chrome_labs_prefs::kChromeLabsNewBadgeDictAshChrome);
#else
  ScopedDictPrefUpdate update(g_browser_process->local_state(),
                              chrome_labs_prefs::kChromeLabsNewBadgeDict);
#endif

  base::DictValue& new_badge_prefs = update.Get();

  std::vector<std::string> lab_internal_names;
  const std::vector<LabInfo>& all_labs =
      ChromeLabsModel::GetInstance()->GetLabInfo();
  for (const auto& lab : all_labs) {
    if (IsChromeLabsFeatureValid(lab, profile)) {
      lab_internal_names.push_back(lab.internal_name);
      if (!new_badge_prefs.Find(lab.internal_name)) {
        new_badge_prefs.Set(
            lab.internal_name,
            chrome_labs_prefs::kChromeLabsNewExperimentPrefValue);
      }
    }
  }
  std::vector<std::string> entries_to_remove;
  for (auto pref : new_badge_prefs) {
    // The size of |lab_internal_names| is capped around 3-5 elements.
    if (!std::ranges::contains(lab_internal_names, pref.first)) {
      entries_to_remove.push_back(pref.first);
    }
  }

  for (const std::string& key : entries_to_remove) {
    new_badge_prefs.Remove(key);
  }
}

bool ShouldShowChromeLabsUI(Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS)
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          ash::switches::kSafeMode) ||
      !ash::ProfileHelper::IsPrimaryProfile(profile)) {
    return false;
  }
#endif

  return std::ranges::any_of(ChromeLabsModel::GetInstance()->GetLabInfo(),
                             [&profile](const LabInfo& lab) {
                               return IsChromeLabsFeatureValid(lab, profile);
                             });
}

bool AreNewChromeLabsExperimentsAvailable(Profile* profile) {
#if BUILDFLAG(IS_CHROMEOS)
  ScopedDictPrefUpdate update(
      profile->GetPrefs(), chrome_labs_prefs::kChromeLabsNewBadgeDictAshChrome);
#else
  ScopedDictPrefUpdate update(g_browser_process->local_state(),
                              chrome_labs_prefs::kChromeLabsNewBadgeDict);
#endif

  base::DictValue& new_badge_prefs = update.Get();

  std::vector<std::string> lab_internal_names;
  const std::vector<LabInfo>& all_labs =
      ChromeLabsModel::GetInstance()->GetLabInfo();

  return std::ranges::any_of(
      all_labs.begin(), all_labs.end(), [&new_badge_prefs](const LabInfo& lab) {
        std::optional<int> new_badge_pref_value =
            new_badge_prefs.FindInt(lab.internal_name);
        // Show the dot indicator if new experiments have not been seen yet.
        return new_badge_pref_value ==
               chrome_labs_prefs::kChromeLabsNewExperimentPrefValue;
      });
}

bool IsChromeLabsEnabled() {
  // Always early out on the stable channel regardless of other conditions.
  if (chrome::GetChannel() == version_info::Channel::STABLE) {
    return false;
  }
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          ::switches::kEnableBenchmarking)) {
    return true;
  }
  // Could be null in unit tests.
  if (!g_browser_process->local_state()) {
    return false;
  }
  if (g_browser_process->local_state()->GetInteger(
          chrome_labs_prefs::kChromeLabsActivationThreshold) ==
      chrome_labs_prefs::kChromeLabsActivationThresholdDefaultValue) {
    g_browser_process->local_state()->SetInteger(
        chrome_labs_prefs::kChromeLabsActivationThreshold,
        base::RandIntInclusive(1, 100));
  }

  // The percentage of users that should see the feature.
  const int kChromeLabsActivationPercentage = 99;

  if (force_activation_for_testing ||
      g_browser_process->local_state()->GetInteger(
          chrome_labs_prefs::kChromeLabsActivationThreshold) <=
          kChromeLabsActivationPercentage) {
    return true;
  }
  return false;
}

void ForceChromeLabsActivationForTesting() {
  force_activation_for_testing = true;
}
