// Copyright 2017 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/signin/account_consistency_mode_manager.h"

#include <string>

#include "base/command_line.h"
#include "base/logging.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/account_consistency_mode_manager_factory.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/signin_buildflags.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "google_apis/google_api_keys.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/account_manager/account_manager_util.h"
#endif

#if BUILDFLAG(ENABLE_DICE_SUPPORT) && BUILDFLAG(ENABLE_MIRROR)
#error "Dice and Mirror cannot be both enabled."
#endif

#if !BUILDFLAG(ENABLE_DICE_SUPPORT) && !BUILDFLAG(ENABLE_MIRROR)
#error "Either Dice or Mirror should be enabled."
#endif

using signin::AccountConsistencyMethod;

namespace {

// By default, DICE is not enabled in builds lacking an API key. May be set to
// true for tests.
bool g_ignore_missing_oauth_client_for_testing = false;

#if BUILDFLAG(ENABLE_DICE_SUPPORT)
const char kAllowBrowserSigninArgument[] = "allow-browser-signin";

bool IsBrowserSigninAllowedByCommandLine() {
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(kAllowBrowserSigninArgument)) {
    std::string allowBrowserSignin =
        command_line->GetSwitchValueASCII(kAllowBrowserSigninArgument);
    return base::EqualsCaseInsensitiveASCII(allowBrowserSignin, "true");
  }
  // If the commandline flag is not provided, the default is true.
  return true;
}

// Returns true if Desktop Identity Consistency can be enabled for this build
// (i.e. if OAuth client ID and client secret are configured).
bool CanEnableDiceForBuild() {
  if (g_ignore_missing_oauth_client_for_testing ||
      google_apis::HasOAuthClientConfigured()) {
    return true;
  }

  // Only log this once.
  [[maybe_unused]] static bool logged_warning = []() {
    LOG(WARNING) << "Desktop Identity Consistency cannot be enabled as no "
                    "OAuth client ID and client secret have been configured.";
    return true;
  }();

  return false;
}
#endif

// Computes the account consistency method for the current profile. The account
// consistency method cannot change during the lifetime of a profile.
signin::AccountConsistencyMethod ComputeAccountConsistencyMethod(
    Profile* profile) {
  DCHECK(AccountConsistencyModeManager::ShouldBuildServiceForProfile(profile));

#if BUILDFLAG(IS_CHROMEOS)
  if (!ash::IsAccountManagerAvailable(profile)) {
    return AccountConsistencyMethod::kDisabled;
  }
#endif

#if BUILDFLAG(ENABLE_MIRROR)
  return AccountConsistencyMethod::kMirror;
#elif BUILDFLAG(ENABLE_DICE_SUPPORT)
  if (!profile->GetPrefs()->GetBoolean(prefs::kSigninAllowed)) {
    VLOG(1) << "Desktop Identity Consistency disabled as sign-in to Chrome "
               "is not allowed";
    return AccountConsistencyMethod::kDisabled;
  }

  return AccountConsistencyMethod::kDice;
#else
  NOTREACHED();
#endif
}

}  // namespace

AccountConsistencyModeManager::AccountConsistencyModeManager(Profile* profile)
    : profile_(profile) {
  DCHECK(profile_);
  DCHECK(ShouldBuildServiceForProfile(profile));
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
  auto* entry = g_browser_process->profile_manager()
                    ? g_browser_process->profile_manager()
                          ->GetProfileAttributesStorage()
                          .GetProfileAttributesWithPath(profile_->GetPath())
                    : nullptr;
  PrefService* prefs = profile_->GetPrefs();
  // Propagate settings changes from the previous launch to the signin-allowed
  // pref.
  bool signin_allowed = IsDiceSignInAllowed(entry) &&
                        prefs->GetBoolean(prefs::kSigninAllowedOnNextStartup);

  // Disable sign-in if experimental-ai is enabled, regardless of channel.
  auto* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(::switches::kExperimentalAiStableChannel)) {
    signin_allowed = false;
  }

  prefs->SetBoolean(prefs::kSigninAllowed, signin_allowed);

  UMA_HISTOGRAM_BOOLEAN("Signin.SigninAllowed", signin_allowed);
#endif

  account_consistency_ = ComputeAccountConsistencyMethod(profile_);
  DCHECK_EQ(account_consistency_, ComputeAccountConsistencyMethod(profile_));
  account_consistency_initialized_ = true;
}

AccountConsistencyModeManager::~AccountConsistencyModeManager() = default;

// static
void AccountConsistencyModeManager::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterBooleanPref(prefs::kSigninAllowedOnNextStartup, true);
}

// static
AccountConsistencyMethod AccountConsistencyModeManager::GetMethodForProfile(
    Profile* profile) {
  if (!ShouldBuildServiceForProfile(profile)) {
    return AccountConsistencyMethod::kDisabled;
  }

  return AccountConsistencyModeManagerFactory::GetForProfile(profile)
      ->GetAccountConsistencyMethod();
}

// static
bool AccountConsistencyModeManager::IsDiceEnabledForProfile(Profile* profile) {
  return GetMethodForProfile(profile) == AccountConsistencyMethod::kDice;
}

#if BUILDFLAG(ENABLE_DICE_SUPPORT)
// static
bool AccountConsistencyModeManager::IsDiceSignInAllowed(
    ProfileAttributesEntry* entry) {
  // Sign in should only be allowed for OIDC profiles with 3P identities that
  // are sync-ed to Google. Otherwise, we won't have a valid GAIA ID to sign in
  // to.
  bool is_oidc_sign_in_disallowed =
      entry && !entry->GetProfileManagementOidcTokens().id_token.empty() &&
      entry->IsDasherlessManagement();
  return CanEnableDiceForBuild() && IsBrowserSigninAllowedByCommandLine() &&
         !is_oidc_sign_in_disallowed &&
         (!entry || entry->GetProfileManagementEnrollmentToken().empty());
}
#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)

// static
bool AccountConsistencyModeManager::IsMirrorEnabledForProfile(
    Profile* profile) {
  return GetMethodForProfile(profile) == AccountConsistencyMethod::kMirror;
}

// static
void AccountConsistencyModeManager::SetIgnoreMissingOAuthClientForTesting() {
  g_ignore_missing_oauth_client_for_testing = true;
}

// static
bool AccountConsistencyModeManager::ShouldBuildServiceForProfile(
    Profile* profile) {
  return profile->IsRegularProfile();
}

AccountConsistencyMethod
AccountConsistencyModeManager::GetAccountConsistencyMethod() {
#if BUILDFLAG(IS_CHROMEOS)
  // TODO(crbug.com/40583837): ChromeOS should use the cached value.
  // Changing the value dynamically is not supported.
  return ComputeAccountConsistencyMethod(profile_);
#else
  // The account consistency method should not change during the lifetime of a
  // profile. We always return the cached value, but still check that it did not
  // change, in order to detect inconsistent states. See crbug.com/40583741
  CHECK(account_consistency_initialized_);
  CHECK_EQ(ComputeAccountConsistencyMethod(profile_), account_consistency_);
  return account_consistency_;
#endif
}
