// 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 "components/signin/public/identity_manager/identity_manager.h"

#include <optional>
#include <string>

#include "base/functional/bind.h"
#include "base/not_fatal_until.h"
#include "base/observer_list.h"
#include "build/build_config.h"
#include "components/signin/internal/identity_manager/account_fetcher_service.h"
#include "components/signin/internal/identity_manager/account_tracker_service.h"
#include "components/signin/internal/identity_manager/gaia_cookie_manager_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/base/signin_buildflags.h"
#include "components/signin/public/base/signin_client.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/accounts_cookie_mutator.h"
#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
#include "components/signin/public/identity_manager/accounts_mutator.h"
#include "components/signin/public/identity_manager/device_accounts_synchronizer.h"
#include "components/signin/public/identity_manager/diagnostics_provider.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/jni_string.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h"
#include "components/signin/public/android/jni_headers/IdentityManagerImpl_jni.h"
#include "google_apis/gaia/core_account_id.h"
#endif

#if BUILDFLAG(ENABLE_DICE_SUPPORT)
#include "components/signin/internal/identity_manager/mutable_profile_oauth2_token_service_delegate.h"
#endif

namespace signin {

IdentityManager::InitParameters::InitParameters() = default;

IdentityManager::InitParameters::InitParameters(InitParameters&&) = default;

IdentityManager::InitParameters::~InitParameters() = default;

IdentityManager::IdentityManager(IdentityManager::InitParameters&& parameters)
    : account_tracker_service_(std::move(parameters.account_tracker_service)),
      token_service_(std::move(parameters.token_service)),
      gaia_cookie_manager_service_(
          std::move(parameters.gaia_cookie_manager_service)),
      primary_account_manager_(std::move(parameters.primary_account_manager)),
      account_fetcher_service_(std::move(parameters.account_fetcher_service)),
      signin_client_(parameters.signin_client),
#if BUILDFLAG(IS_CHROMEOS)
      account_manager_facade_(parameters.account_manager_facade),
#endif
      identity_mutator_(std::make_unique<IdentityMutator>(
          std::move(parameters.primary_account_mutator),
          std::move(parameters.accounts_mutator),
          std::move(parameters.accounts_cookie_mutator),
          std::move(parameters.device_accounts_synchronizer))),
      diagnostics_provider_(std::move(parameters.diagnostics_provider)),
      weak_pointer_factory_(this) {
  DCHECK(account_fetcher_service_);
  DCHECK(diagnostics_provider_);
  DCHECK(signin_client_);

  primary_account_manager_observation_.Observe(primary_account_manager_.get());
  token_service_observation_.Observe(token_service_.get());
  token_service_->AddAccessTokenDiagnosticsObserver(this);

  // IdentityManager owns the ATS, GCMS and PO2TS instances and will outlive
  // them, so base::Unretained is safe.
  account_tracker_service_->SetOnAccountUpdatedCallback(base::BindRepeating(
      &IdentityManager::OnAccountUpdated, base::Unretained(this)));
  account_tracker_service_->SetOnAccountRemovedCallback(base::BindRepeating(
      &IdentityManager::OnAccountRemoved, base::Unretained(this)));
  gaia_cookie_manager_service_->SetGaiaAccountsInCookieUpdatedCallback(
      base::BindRepeating(&IdentityManager::OnGaiaAccountsInCookieUpdated,
                          base::Unretained(this)));
  gaia_cookie_manager_service_->SetGaiaCookieDeletedByUserActionCallback(
      base::BindRepeating(&IdentityManager::OnGaiaCookieDeletedByUserAction,
                          base::Unretained(this)));
  token_service_->SetRefreshTokenAvailableFromSourceCallback(
      base::BindRepeating(&IdentityManager::OnRefreshTokenAvailableFromSource,
                          base::Unretained(this)));
  token_service_->SetRefreshTokenRevokedFromSourceCallback(
      base::BindRepeating(&IdentityManager::OnRefreshTokenRevokedFromSource,
                          base::Unretained(this)));

#if BUILDFLAG(IS_ANDROID)
  java_identity_manager_ = Java_IdentityManagerImpl_create(
      base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
      token_service_->GetDelegate()->GetJavaObject());
#endif
}

IdentityManager::~IdentityManager() {
#if BUILDFLAG(IS_ANDROID)
  if (java_identity_manager_) {
    Java_IdentityManagerImpl_destroy(base::android::AttachCurrentThread(),
                                     java_identity_manager_);
  }
#endif
}

void IdentityManager::Shutdown() {
  for (auto& observer : observer_list_) {
    observer.OnIdentityManagerShutdown(this);
  }

  // It is no longer safe to use the SigninClient beyond this point, everything
  // depending on it must be destroyed.
  token_service_->RemoveAccessTokenDiagnosticsObserver(this);
  token_service_observation_.Reset();
  primary_account_manager_observation_.Reset();

  diagnostics_provider_.reset();
  identity_mutator_.reset();
  account_fetcher_service_.reset();
  gaia_cookie_manager_service_.reset();
  primary_account_manager_.reset();
  token_service_.reset();
  account_tracker_service_.reset();
}

#if BUILDFLAG(IS_IOS)
base::ScopedClosureRunner IdentityManager::StartBatchOfPrimaryAccountChanges() {
  CHECK(!batch_of_primary_account_changes_in_progress_,
        base::NotFatalUntil::M140);
  batch_of_primary_account_changes_in_progress_ = true;
  return base::ScopedClosureRunner(base::BindOnce(
      &IdentityManager::BatchOfPrimaryAccountChangesDone, GetWeakPtr()));
}
#endif  // BUILDFLAG(IS_IOS)

void IdentityManager::AddObserver(Observer* observer) {
  observer_list_.AddObserver(observer);
}

void IdentityManager::RemoveObserver(Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

// TODO(crbug.com/40584518) change return type to std::optional<CoreAccountInfo>
CoreAccountInfo IdentityManager::GetPrimaryAccountInfo(
    ConsentLevel consent) const {
  return primary_account_manager_->GetPrimaryAccountInfo(consent);
}

CoreAccountId IdentityManager::GetPrimaryAccountId(ConsentLevel consent) const {
  return GetPrimaryAccountInfo(consent).account_id;
}

bool IdentityManager::HasPrimaryAccount(ConsentLevel consent) const {
  return primary_account_manager_->HasPrimaryAccount(consent);
}

std::unique_ptr<AccessTokenFetcher>
IdentityManager::CreateAccessTokenFetcherWithDynamicScopesForAccount(
    const CoreAccountId& account_id,
    OAuthConsumerId oauth_consumer_id,
    const ScopeSet& scopes,
    AccessTokenFetcher::TokenCallback callback,
    AccessTokenFetcher::Mode mode,
    AccessTokenFetcher::Source token_source) {
  signin::OAuthConsumer oauth_consumer =
      signin::GetOAuthConsumerForDynamicScopes(oauth_consumer_id, scopes);
  return std::make_unique<AccessTokenFetcher>(
      account_id, oauth_consumer_id, oauth_consumer, token_service_.get(),
      primary_account_manager_.get(), std::move(callback), mode, token_source);
}

std::unique_ptr<AccessTokenFetcher>
IdentityManager::CreateAccessTokenFetcherForAccount(
    const CoreAccountId& account_id,
    OAuthConsumerId oauth_consumer_id,
    AccessTokenFetcher::TokenCallback callback,
    AccessTokenFetcher::Mode mode,
    AccessTokenFetcher::Source token_source) {
  signin::OAuthConsumer oauth_consumer =
      signin_client_->GetOAuthConsumerFromId(oauth_consumer_id);
  return std::make_unique<AccessTokenFetcher>(
      account_id, oauth_consumer_id, oauth_consumer, token_service_.get(),
      primary_account_manager_.get(), std::move(callback), mode, token_source);
}

std::unique_ptr<AccessTokenFetcher>
IdentityManager::CreateAccessTokenFetcherForAccount(
    const CoreAccountId& account_id,
    OAuthConsumerId oauth_consumer_id,
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
    AccessTokenFetcher::TokenCallback callback,
    AccessTokenFetcher::Mode mode) {
  signin::OAuthConsumer oauth_consumer =
      signin_client_->GetOAuthConsumerFromId(oauth_consumer_id);
  return std::make_unique<AccessTokenFetcher>(
      account_id, oauth_consumer_id, oauth_consumer, token_service_.get(),
      primary_account_manager_.get(), url_loader_factory, std::move(callback),
      mode);
}

void IdentityManager::RemoveAccessTokenFromCache(
    const CoreAccountId& account_id,
    OAuthConsumerId oauth_consumer_id,
    const std::string& access_token) {
  if (account_id.empty() || access_token.empty()) {
    return;
  }

  ScopeSet scopes =
      signin_client_->GetOAuthConsumerFromId(oauth_consumer_id).GetScopes();
  token_service_->InvalidateAccessToken(account_id, scopes, access_token);
}

std::vector<CoreAccountInfo> IdentityManager::GetAccountsWithRefreshTokens()
    const {
  std::vector<CoreAccountId> account_ids_with_tokens =
      token_service_->GetAccounts();

  std::vector<CoreAccountInfo> accounts;
  accounts.reserve(account_ids_with_tokens.size());

  for (const CoreAccountId& account_id : account_ids_with_tokens) {
    accounts.push_back(GetAccountInfoForAccountWithRefreshToken(account_id));
  }

  return accounts;
}

std::vector<AccountInfo>
IdentityManager::GetExtendedAccountInfoForAccountsWithRefreshToken() const {
  std::vector<CoreAccountId> account_ids_with_tokens =
      token_service_->GetAccounts();

  std::vector<AccountInfo> accounts;
  accounts.reserve(account_ids_with_tokens.size());

  for (const CoreAccountId& account_id : account_ids_with_tokens) {
    accounts.push_back(GetAccountInfoForAccountWithRefreshToken(account_id));
  }

  return accounts;
}

bool IdentityManager::HasPrimaryAccountWithRefreshToken(
    ConsentLevel consent_level) const {
  return HasAccountWithRefreshToken(GetPrimaryAccountId(consent_level));
}

bool IdentityManager::HasAccountWithRefreshToken(
    const CoreAccountId& account_id) const {
  return token_service_->RefreshTokenIsAvailable(account_id);
}

#if BUILDFLAG(IS_IOS)
bool IdentityManager::HasAccountWithRefreshTokenOnDevice(
    const CoreAccountId& account_id) const {
  return token_service_->RefreshTokenIsAvailableOnDevice(account_id);
}
#endif

bool IdentityManager::AreRefreshTokensLoaded() const {
  return token_service_->AreAllCredentialsLoaded();
}

bool IdentityManager::HasAccountWithRefreshTokenInPersistentErrorState(
    const CoreAccountId& account_id) const {
  return GetErrorStateOfRefreshTokenForAccount(account_id).IsPersistentError();
}

#if BUILDFLAG(ENABLE_DICE_SUPPORT)
bool IdentityManager::GenerateBindingKeyRegistrationToken(
    base::span<const crypto::SignatureVerifier::SignatureAlgorithm>
        supported_algorithms,
    std::string_view auth_code,
    base::OnceCallback<void(
        std::optional<signin::BindingKeyRegistrationTokenResult>)> callback) {
  return token_service_->GenerateBindingKeyRegistrationToken(
      supported_algorithms, auth_code, std::move(callback));
}

bool IdentityManager::HasAccountWithBoundRefreshToken(
    const CoreAccountId& account_id) const {
  return !token_service_->GetWrappedBindingKey(account_id).empty();
}

bool IdentityManager::HasAccountWithRefreshTokenBoundToMtls(
    const CoreAccountId& account_id) const {
  return token_service_->IsRefreshTokenBoundToMtls(account_id);
}

bool IdentityManager::AllBoundTokensShareSameBindingKey() const {
  return token_service_->AllBoundTokensShareSameBindingKey();
}

std::vector<uint8_t> IdentityManager::GetWrappedBindingKey() const {
  CHECK(AreRefreshTokensLoaded());
  // All bound tokens are supposed to use the same key. Having two different
  // keys should be considered a bug. To be extra safe, we check the primary
  // account first.
  if (HasPrimaryAccount(ConsentLevel::kSignin)) {
    const std::vector<uint8_t> wrapped_binding_key =
        token_service_->GetWrappedBindingKey(
            GetPrimaryAccountId(ConsentLevel::kSignin));
    if (!wrapped_binding_key.empty()) {
      return wrapped_binding_key;
    }
  }
  for (const CoreAccountId& account_id : token_service_->GetAccounts()) {
    const std::vector<uint8_t> wrapped_binding_key =
        token_service_->GetWrappedBindingKey(account_id);
    if (!wrapped_binding_key.empty()) {
      return wrapped_binding_key;
    }
  }
  return {};
}
#endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)

GoogleServiceAuthError IdentityManager::GetErrorStateOfRefreshTokenForAccount(
    const CoreAccountId& account_id) const {
  return token_service_->GetAuthError(account_id);
}

AccountInfo IdentityManager::FindExtendedAccountInfo(
    const CoreAccountInfo& account_info) const {
  return FindExtendedAccountInfoByAccountId(account_info.account_id);
}

AccountInfo IdentityManager::FindExtendedAccountInfoByAccountId(
    const CoreAccountId& account_id) const {
  // Skip the the token check if the switch is enabled, for consistency with the
  // behavior of FindExtendedAccountInfoByEmailAddress
  if (!HasAccountWithRefreshToken(account_id) &&
      !base::FeatureList::IsEnabled(
          switches::kSkipRefreshTokenCheckInIdentityManager)) {
    return AccountInfo();
  }
  // AccountTrackerService returns an empty AccountInfo if the account is not
  // found.
  return account_tracker_service_->GetAccountInfo(account_id);
}

AccountInfo IdentityManager::FindExtendedAccountInfoByEmailAddress(
    std::string_view email_address) const {
  AccountInfo account_info =
      account_tracker_service_->FindAccountInfoByEmail(email_address);
  // Skip the the token check if the switch is enabled.
  // This prevents a crash that occurs when the account info is retrieved before
  // the account's refresh token is available, causing the check to fail.
  // See https://crbug.com/366252188 and https://crbug.com/40183609
  if (base::FeatureList::IsEnabled(
          switches::kSkipRefreshTokenCheckInIdentityManager)) {
    return account_info;
  }
  // AccountTrackerService always returns an AccountInfo, even on failure. In
  // case of failure, the AccountInfo will be unpopulated, thus we should not
  // be able to find a valid refresh token.
  return HasAccountWithRefreshToken(account_info.account_id) ? account_info
                                                             : AccountInfo();
}

AccountInfo IdentityManager::FindExtendedAccountInfoByGaiaId(
    const GaiaId& gaia_id) const {
  AccountInfo account_info =
      account_tracker_service_->FindAccountInfoByGaiaId(gaia_id);
  // Skip the the token check if the switch is enabled, for consistency with the
  // behavior of FindExtendedAccountInfoByEmailAddress
  if (base::FeatureList::IsEnabled(
          switches::kSkipRefreshTokenCheckInIdentityManager)) {
    return account_info;
  }
  // AccountTrackerService always returns an AccountInfo, even on failure. In
  // case of failure, the AccountInfo will be unpopulated, thus we should not
  // be able to find a valid refresh token.
  return HasAccountWithRefreshToken(account_info.account_id) ? account_info
                                                             : AccountInfo();
}

AccountsInCookieJarInfo IdentityManager::GetAccountsInCookieJar() const {
  if (base::FeatureList::IsEnabled(
          switches::kAvoidAutoTriggerListAccountsOnStale)) {
    return gaia_cookie_manager_service_->GetCachedListAccounts();
  } else {
    return gaia_cookie_manager_service_->ListAccounts();
  }
}

AccountsInCookieJarInfo IdentityManager::GetCachedAccountsInCookieJar() const {
  return gaia_cookie_manager_service_->GetCachedListAccounts();
}

std::optional<size_t> IdentityManager::GetSessionIndexForPrimaryAccount()
    const {
  CoreAccountInfo primary_account_info =
      GetPrimaryAccountInfo(ConsentLevel::kSignin);
  if (primary_account_info.gaia.empty()) {
    return std::nullopt;
  }

  AccountsInCookieJarInfo accounts_in_cookie_jar = GetAccountsInCookieJar();
  const std::vector<gaia::ListedAccount>& accounts =
      accounts_in_cookie_jar.GetAllAccounts();
  for (size_t i = 0; i < accounts.size(); ++i) {
    if (accounts[i].gaia_id == primary_account_info.gaia) {
      return i;
    }
  }

  return std::nullopt;
}

PrimaryAccountMutator* IdentityManager::GetPrimaryAccountMutator() {
  return identity_mutator_->GetPrimaryAccountMutator();
}

AccountsMutator* IdentityManager::GetAccountsMutator() {
  return identity_mutator_->GetAccountsMutator();
}

AccountsCookieMutator* IdentityManager::GetAccountsCookieMutator() {
  return identity_mutator_->GetAccountsCookieMutator();
}

DeviceAccountsSynchronizer* IdentityManager::GetDeviceAccountsSynchronizer() {
  return identity_mutator_->GetDeviceAccountsSynchronizer();
}

#if BUILDFLAG(IS_IOS)
std::vector<AccountInfo> IdentityManager::GetAccountsOnDevice() const {
  return token_service_->GetAccountsOnDevice();
}
#endif

void IdentityManager::SetCapabilityOverride(const CoreAccountId& account_id,
                                            std::string_view capability_name,
                                            std::optional<Tribool> override_value) {
  account_tracker_service_->SetCapabilityOverride(account_id, capability_name,
                                                  override_value);
}

void IdentityManager::AddDiagnosticsObserver(DiagnosticsObserver* observer) {
  diagnostics_observation_list_.AddObserver(observer);
}

void IdentityManager::RemoveDiagnosticsObserver(DiagnosticsObserver* observer) {
  diagnostics_observation_list_.RemoveObserver(observer);
}

void IdentityManager::OnNetworkInitialized() {
  gaia_cookie_manager_service_->InitCookieListener();
  account_fetcher_service_->OnNetworkInitialized();
  // Trigger ListAccounts once the network is initialized to ensure the accounts
  // in cookie jar are up to date.
  if (base::FeatureList::IsEnabled(
          switches::kAvoidAutoTriggerListAccountsOnStale)) {
    gaia_cookie_manager_service_->ListAccounts();
  }
}

CoreAccountId IdentityManager::PickAccountIdForAccount(
    const GaiaId& gaia,
    const std::string& email) const {
  return account_tracker_service_->PickAccountIdForAccount(gaia, email);
}

// static
void IdentityManager::RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
  PrimaryAccountManager::RegisterPrefs(registry);
}

// static
void IdentityManager::RegisterProfilePrefs(PrefRegistrySimple* registry) {
  ProfileOAuth2TokenService::RegisterProfilePrefs(registry);
  PrimaryAccountManager::RegisterProfilePrefs(registry);
  AccountFetcherService::RegisterPrefs(registry);
  AccountTrackerService::RegisterPrefs(registry);
  GaiaCookieManagerService::RegisterPrefs(registry);
}

DiagnosticsProvider* IdentityManager::GetDiagnosticsProvider() {
  return diagnostics_provider_.get();
}

void IdentityManager::PrepareForAddingNewAccount() {
  account_fetcher_service_->PrepareForFetchingAccountCapabilities();
}

#if BUILDFLAG(IS_ANDROID)
base::android::ScopedJavaLocalRef<jobject> IdentityManager::GetJavaObject()
    const {
  DCHECK(java_identity_manager_);
  return base::android::ScopedJavaLocalRef<jobject>(java_identity_manager_);
}

// static
IdentityManager* IdentityManager::FromJavaObject(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& j_identity_manager) {
  if (!j_identity_manager) {
    return nullptr;
  }
  return reinterpret_cast<IdentityManager*>(
      Java_IdentityManagerImpl_getNativePointer(env, j_identity_manager));
}

base::android::ScopedJavaLocalRef<jobject>
IdentityManager::GetIdentityMutatorJavaObject() {
  return base::android::ScopedJavaLocalRef<jobject>(
      identity_mutator_->GetJavaObject());
}

void IdentityManager::RefreshAccountInfoIfStale(
    const CoreAccountId& account_id) {
  DCHECK(HasAccountWithRefreshToken(account_id));
  account_fetcher_service_->RefreshAccountInfoIfStale(account_id);
}

void IdentityManager::RefreshAccountInfoIfStale(JNIEnv* env) {
  std::vector<CoreAccountInfo> accounts = GetAccountsWithRefreshTokens();
  for (const CoreAccountInfo& account : accounts) {
    RefreshAccountInfoIfStale(account.account_id);
  }
}

base::android::ScopedJavaLocalRef<jobject>
IdentityManager::GetPrimaryAccountInfo(JNIEnv* env) const {
  CoreAccountInfo account_info = GetPrimaryAccountInfo(ConsentLevel::kSignin);
  if (account_info.IsEmpty()) {
    return nullptr;
  }
  AccountInfo extended_info =
      account_tracker_service_->GetAccountInfo(account_info.account_id);
  return ConvertToJavaAccountInfo(env, extended_info);
}

base::android::ScopedJavaLocalRef<jobject>
IdentityManager::FindExtendedAccountInfoByAccountId(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& j_account_id) const {
  AccountInfo account_info = FindExtendedAccountInfoByAccountId(
      ConvertFromJavaCoreAccountId(env, j_account_id));
  if (account_info.IsEmpty()) {
    return nullptr;
  }
  return ConvertToJavaAccountInfo(env, account_info);
}

base::android::ScopedJavaLocalRef<jobject>
IdentityManager::FindExtendedAccountInfoByEmailAddress(
    JNIEnv* env,
    const base::android::JavaRef<jstring>& j_email) const {
  AccountInfo account_info = FindExtendedAccountInfoByEmailAddress(
      base::android::ConvertJavaStringToUTF8(env, j_email));
  if (account_info.IsEmpty()) {
    return nullptr;
  }
  return ConvertToJavaAccountInfo(env, account_info);
}

bool IdentityManager::IsClearPrimaryAccountAllowed(JNIEnv* env) const {
  return signin_client_->IsClearPrimaryAccountAllowed();
}
#endif

base::WeakPtr<IdentityManager> IdentityManager::GetWeakPtr() {
  return weak_pointer_factory_.GetWeakPtr();
}

AccountInfo IdentityManager::FindExtendedPrimaryAccountInfo(
    ConsentLevel consent_level) {
  CoreAccountId account_id = GetPrimaryAccountId(consent_level);
  return account_tracker_service_->GetAccountInfo(account_id);
}

PrimaryAccountManager* IdentityManager::GetPrimaryAccountManager() const {
  return primary_account_manager_.get();
}

ProfileOAuth2TokenService* IdentityManager::GetTokenService() const {
  return token_service_.get();
}

AccountTrackerService* IdentityManager::GetAccountTrackerService() const {
  return account_tracker_service_.get();
}

AccountFetcherService* IdentityManager::GetAccountFetcherService() const {
  return account_fetcher_service_.get();
}

GaiaCookieManagerService* IdentityManager::GetGaiaCookieManagerService() const {
  return gaia_cookie_manager_service_.get();
}

#if BUILDFLAG(IS_CHROMEOS)
account_manager::AccountManagerFacade*
IdentityManager::GetAccountManagerFacade() const {
  return account_manager_facade_;
}
#endif

AccountInfo IdentityManager::GetAccountInfoForAccountWithRefreshToken(
    const CoreAccountId& account_id) const {
  // TODO(crbug.com/41434401): This invariant is not currently possible to
  // enforce on Android due to the underlying relationship between
  // O2TS::GetAccounts(), O2TS::RefreshTokenIsAvailable(), and
  // O2TS::Observer::OnRefreshTokenAvailable().
#if !BUILDFLAG(IS_ANDROID)
  DCHECK(HasAccountWithRefreshToken(account_id));
#endif

  AccountInfo account_info =
      account_tracker_service_->GetAccountInfo(account_id);
  DCHECK(!account_info.IsEmpty());

  return account_info;
}

void IdentityManager::OnPrimaryAccountChanged(
    const PrimaryAccountChangeEvent& event_details) {
  CoreAccountId event_primary_account_id =
      event_details.GetCurrentState().primary_account.account_id;
  DCHECK_EQ(event_primary_account_id,
            GetPrimaryAccountId(event_details.GetCurrentState().consent_level));
  for (auto& observer : observer_list_) {
    observer.OnPrimaryAccountChanged(event_details);
    // Ensure that |observer| did not change the primary account as otherwise
    // |event_details| would not longer be correct.
    DCHECK_EQ(
        event_primary_account_id,
        GetPrimaryAccountId(event_details.GetCurrentState().consent_level));
  }

#if BUILDFLAG(IS_ANDROID)
  if (java_identity_manager_) {
    JNIEnv* env = base::android::AttachCurrentThread();
    base::android::ScopedJavaLocalRef<jobject> event =
        ConvertToJavaPrimaryAccountChangeEvent(env, event_details);
    if (event) {
      Java_IdentityManagerImpl_onPrimaryAccountChanged(
          env, java_identity_manager_, event);
    }
  }
#endif
#if BUILDFLAG(IS_IOS)
  if (!batch_of_primary_account_changes_in_progress_) {
    FireOnEndBatchOfPrimaryAccountChanges();
  }
#endif  // BUILDFLAG(IS_IOS)
}

void IdentityManager::OnRefreshTokenAvailable(const CoreAccountId& account_id) {
  CoreAccountInfo account_info =
      GetAccountInfoForAccountWithRefreshToken(account_id);

  for (auto& observer : observer_list_) {
    observer.OnRefreshTokenUpdatedForAccount(account_info);
  }
#if BUILDFLAG(IS_ANDROID)
  if (java_identity_manager_) {
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_IdentityManagerImpl_onRefreshTokenUpdatedForAccount(
        env, java_identity_manager_,
        ConvertToJavaCoreAccountInfo(env, account_info));
  }
#endif
}

void IdentityManager::OnRefreshTokenRevoked(const CoreAccountId& account_id) {
  for (auto& observer : observer_list_) {
    observer.OnRefreshTokenRemovedForAccount(account_id);
  }
#if BUILDFLAG(IS_ANDROID)
  if (java_identity_manager_) {
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_IdentityManagerImpl_onRefreshTokenRemovedForAccount(
        env, java_identity_manager_,
        ConvertToJavaCoreAccountId(env, account_id));
  }
#endif
}

void IdentityManager::OnRefreshTokensLoaded() {
  for (auto& observer : observer_list_) {
    observer.OnRefreshTokensLoaded();
  }
#if BUILDFLAG(IS_ANDROID)
  if (java_identity_manager_) {
    Java_IdentityManagerImpl_onRefreshTokensLoaded(
        base::android::AttachCurrentThread(), java_identity_manager_);
  }
#endif
}

void IdentityManager::OnEndBatchChanges() {
  for (auto& observer : observer_list_) {
    observer.OnEndBatchOfRefreshTokenStateChanges();
  }
}

void IdentityManager::OnAuthErrorChanged(
    const CoreAccountId& account_id,
    const GoogleServiceAuthError& auth_error,
    signin_metrics::SourceForRefreshTokenOperation token_operation_source) {
  CoreAccountInfo account_info =
      GetAccountInfoForAccountWithRefreshToken(account_id);

  for (auto& observer : observer_list_) {
    observer.OnErrorStateOfRefreshTokenUpdatedForAccount(
        account_info, auth_error, token_operation_source);
  }
}

#if BUILDFLAG(IS_IOS)
void IdentityManager::OnAccountsOnDeviceChanged() {
  for (auto& observer : observer_list_) {
    observer.OnAccountsOnDeviceChanged();
  }
}

void IdentityManager::OnAccountOnDeviceUpdated(
    const AccountInfo& account_info) {
  for (auto& observer : observer_list_) {
    observer.OnExtendedAccountInfoUpdated(account_info);
  }
}
#endif

void IdentityManager::OnGaiaAccountsInCookieUpdated(
    const AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
    const GoogleServiceAuthError& error) {
  bool succeeded = error == GoogleServiceAuthError::AuthErrorNone();
  CHECK(accounts_in_cookie_jar_info.AreAccountsFresh() == succeeded);

  for (auto& observer : observer_list_) {
    observer.OnAccountsInCookieUpdated(accounts_in_cookie_jar_info, error);
  }
}

void IdentityManager::OnGaiaCookieDeletedByUserAction() {
  for (auto& observer : observer_list_) {
    observer.OnAccountsCookieDeletedByUserAction();
  }
#if BUILDFLAG(IS_ANDROID)
  if (java_identity_manager_) {
    Java_IdentityManagerImpl_onAccountsCookieDeletedByUserAction(
        base::android::AttachCurrentThread(), java_identity_manager_);
  }
#endif
}

void IdentityManager::OnAccessTokenRequested(const CoreAccountId& account_id,
                                             const std::string& consumer_id,
                                             const ScopeSet& scopes) {
  for (auto& observer : diagnostics_observation_list_) {
    observer.OnAccessTokenRequested(account_id, consumer_id, scopes);
  }
}

void IdentityManager::OnFetchAccessTokenComplete(
    const CoreAccountId& account_id,
    const std::string& consumer_id,
    const ScopeSet& scopes,
    const GoogleServiceAuthError& error,
    base::Time expiration_time) {
  for (auto& observer : diagnostics_observation_list_) {
    observer.OnAccessTokenRequestCompleted(account_id, consumer_id, scopes,
                                           error, expiration_time);
  }
}

void IdentityManager::OnAccessTokenRemoved(const CoreAccountId& account_id,
                                           const ScopeSet& scopes) {
  for (auto& observer : diagnostics_observation_list_) {
    observer.OnAccessTokenRemovedFromCache(account_id, scopes);
  }
}

void IdentityManager::OnRefreshTokenAvailableFromSource(
    const CoreAccountId& account_id,
    bool is_refresh_token_valid,
    const std::string& source) {
  for (auto& observer : diagnostics_observation_list_) {
    observer.OnRefreshTokenUpdatedForAccountFromSource(
        account_id, is_refresh_token_valid, source);
  }
}

void IdentityManager::OnRefreshTokenRevokedFromSource(
    const CoreAccountId& account_id,
    const std::string& source) {
  // Copy the account ID to avoid a use-after-free if one of the observers
  // owns the reference to the account ID and destroys it in
  // `OnRefreshTokenRemovedForAccountFromSource()`.
  CoreAccountId account_id_copy = account_id;
  for (auto& observer : diagnostics_observation_list_) {
    observer.OnRefreshTokenRemovedForAccountFromSource(account_id_copy, source);
  }
}

void IdentityManager::OnAccountUpdated(const AccountInfo& info) {
  if (HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
    const CoreAccountId primary_account_id =
        GetPrimaryAccountId(ConsentLevel::kSignin);
    if (primary_account_id == info.account_id) {
      primary_account_manager_->UpdatePrimaryAccountInfo();
    }
  }

  for (auto& observer : observer_list_) {
    observer.OnExtendedAccountInfoUpdated(info);
  }
#if BUILDFLAG(IS_ANDROID)
  if (java_identity_manager_) {
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_IdentityManagerImpl_onExtendedAccountInfoUpdated(
        env, java_identity_manager_, ConvertToJavaAccountInfo(env, info));
  }
#endif
}

void IdentityManager::OnAccountRemoved(const AccountInfo& info) {
#if (BUILDFLAG(IS_ANDROID))
  account_fetcher_service_->DestroyFetchers(info.account_id);
#endif
  for (auto& observer : observer_list_) {
    observer.OnExtendedAccountInfoRemoved(info);
  }
}

#if BUILDFLAG(IS_IOS)
bool IdentityManager::IsBatchOfPrimaryAccountChangesInProgress() {
  return batch_of_primary_account_changes_in_progress_;
}

void IdentityManager::BatchOfPrimaryAccountChangesDone() {
  CHECK(batch_of_primary_account_changes_in_progress_,
        base::NotFatalUntil::M140);
  batch_of_primary_account_changes_in_progress_ = false;
  FireOnEndBatchOfPrimaryAccountChanges();
}

void IdentityManager::FireOnEndBatchOfPrimaryAccountChanges() {
  CHECK(!batch_of_primary_account_changes_in_progress_,
        base::NotFatalUntil::M140);
  for (auto& observer : observer_list_) {
    observer.OnEndBatchOfPrimaryAccountChanges();
  }
}
#endif  // BUILDFLAG(IS_IOS)
}  // namespace signin

#if BUILDFLAG(IS_ANDROID)
DEFINE_JNI(IdentityManagerImpl)
#endif
