// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_SIGNIN_MODEL_AUTHENTICATION_SERVICE_H_
#define IOS_CHROME_BROWSER_SIGNIN_MODEL_AUTHENTICATION_SERVICE_H_

#import <string>
#import <vector>

#import "base/functional/callback_helpers.h"
#import "base/ios/block_types.h"
#import "base/memory/raw_ptr.h"
#import "base/memory/weak_ptr.h"
#import "base/scoped_observation.h"
#import "components/keyed_service/core/keyed_service.h"
#import "components/pref_registry/pref_registry_syncable.h"
#import "components/signin/ios/browser/account_consistency_service.h"
#import "components/signin/ios/browser/signin_enabled_datasource.h"
#import "components/signin/public/base/signin_metrics.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service.h"

namespace syncer {
class SyncService;
}

class AuthenticationServiceDelegate;
class AuthenticationServiceObserver;
class FakeAuthenticationService;
class PrefService;
@protocol RefreshAccessTokenError;
@protocol SystemIdentity;

// AuthenticationService handles sign-in/sign-out operations, including various
// related state (prefs) and observing the SigninAllowed and BrowserSignin
// policies.
class AuthenticationService : public KeyedService,
                              public signin::IdentityManager::Observer,
                              public ChromeAccountManagerService::Observer,
                              public signin::SigninEnabledDataSource {
 public:
  // The service status for AuthenticationService.
  enum class ServiceStatus {
    // Sign-in forced by enterprise policy.
    SigninForcedByPolicy = 0,
    // Sign-in is possible.
    SigninAllowed = 1,
    // Sign-in disabled by user.
    SigninDisabledByUser = 2,
    // Sign-in disabled by enterprise policy.
    SigninDisabledByPolicy = 3,
    // Sign-in disabled for internal reason (probably running Chromium).
    SigninDisabledByInternal = 4,
  };

  // All passed-in services must not be null, and must outlive this service.
  AuthenticationService(ProfileIOS* profile,
                        PrefService* pref_service,
                        ChromeAccountManagerService* account_manager_service,
                        signin::IdentityManager* identity_manager,
                        syncer::SyncService* sync_service);

  AuthenticationService(const AuthenticationService&) = delete;
  AuthenticationService& operator=(const AuthenticationService&) = delete;

  ~AuthenticationService() override;

  // Registers the preferences used by AuthenticationService.
  static void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry);

  // Returns whether the AuthenticationService has been initialized. It is
  // a fatal error to invoke any method on this object except Initialize()
  // if this method returns false.
  bool initialized() const { return initialized_; }

  // Initializes the AuthenticationService.
  void Initialize(std::unique_ptr<AuthenticationServiceDelegate> delegate);

  // KeyedService implementation.
  void Shutdown() override;

  // Adds and removes observers.
  void AddObserver(AuthenticationServiceObserver* observer);
  void RemoveObserver(AuthenticationServiceObserver* observer);

  // Returns the service status, see ServiceStatus. This value can be observed
  // using AuthenticationServiceObserver::OnServiceStatusChanged().
  ServiceStatus GetServiceStatus() const;

  // Reauth prompt tracking

  // Reminds user to Sign in and sync to Chrome when a new tab is opened.
  void SetReauthPromptForSignInAndSync();

  // Clears the reminder to Sign in and sync to Chrome when a new tab is opened.
  void ResetReauthPromptForSignInAndSync();

  // Returns whether user should be prompted to Sign in and sync to Chrome.
  bool ShouldReauthPromptForSignInAndSync() const;

  // SystemIdentity management

  // Returns true if the user is signed in.
  // While the AuthenticationService is in background, this will reload the
  // credentials to ensure the value is up to date.
  bool HasPrimaryIdentity() const;

  // Returns true if the user is signed in and the identity is considered
  // managed.
  // Virtual for testing.
  virtual bool HasPrimaryIdentityManaged() const;

  // Returns true if data from the signed-in period should be cleared on
  // sign-out.
  virtual bool ShouldClearDataForSignedInPeriodOnSignOut() const;

  // Retrieves the identity of the currently authenticated user or `nil` if
  // the user is not authenticated.
  // Virtual for testing.
  virtual id<SystemIdentity> GetPrimaryIdentity() const;

  // Signs in `identity` and records the signin at `access_point`. Virtual for
  // testing.
  virtual void SignIn(id<SystemIdentity> identity,
                      signin_metrics::AccessPoint access_point);

  // Please use signin::MultiProfileSignOutForProfile().
  // Signs the authenticated user out of Chrome. This method only works for
  // the personal profile. This method should only be used by the sign-in team.
  // Sync consent is automatically removed from all signed-out accounts.
  // `completion` is then executed asynchronously.
  // Virtual for testing.
  virtual void SignOut(signin_metrics::ProfileSignout signout_source,
                       ProceduralBlock completion);

  // Returns whether there is a cached associated MDM error for `identity`.
  bool HasCachedMDMErrorForIdentity(id<SystemIdentity> identity);

  // Shows the MDM Error dialog for `identity` if it has an associated MDM
  // error. Returns true if `identity` had an associated error, false otherwise.
  bool ShowMDMErrorDialogForIdentity(
      id<SystemIdentity> identity,
      base::OnceCallback<void(bool)> callback = base::NullCallback());

  // Returns a weak pointer of this.
  base::WeakPtr<AuthenticationService> GetWeakPtr();

  // This needs to be invoked when the application enters foreground to
  // sync the accounts between the IdentityManager and the SSO library.
  void OnApplicationWillEnterForeground();

  bool SigninEnabled() const override;

 private:
  friend class AuthenticationServiceTest;
  friend class FakeAuthenticationService;

  // LINT.IfChange(IOSProfileInitializationOutcome)
  enum class ProfileInitializationOutcome {
    // Test-only code path.
    kNoneForTesting = 0,

    // Good / expected cases:

    // The multi-profile feature was disabled, so no action taken.
    kFeatureDisabledAlreadyInitialized = 1,
    kFeatureDisabledNewlyInitialized = 2,

    // This is the personal profile, so no action was necessary.
    kPersonalProfileAlreadyInitialized = 3,
    kPersonalProfileNewlyInitialized = 4,
    // This is a managed profile, and it was already initialized (i.e. already
    // signed in).
    kManagedProfileAlreadyInitialized = 5,
    // This is a managed profile, and it was newly initialized (and signed in).
    kManagedProfileNewlyInitialized = 6,

    // Bad / unexpected cases:

    // This is a managed profile which was newly initialized, but it was somehow
    // already signed in. This most likely means the "IsFullyInitialized" flag
    // wasn't properly set.
    kManagedProfileNewlyInitializedButAlreadySignedIn = 7,
    // This is a managed profile which was already initialized, but not signed
    // in yet. It was signed in (again). This means that either signin failed
    // previously, or the managed profile somehow got signed out.
    kManagedProfileAlreadyInitializedButNewlySignedIn = 8,
    // This is a managed profile, but it had no available accounts and so could
    // not be signed in. This likely indicates some issue with
    // AccountProfileMapper.
    kManagedProfileAlreadyInitializedNoAccounts = 9,
    kManagedProfileNewlyInitializedNoAccounts = 10,
    // This is a managed profile, which somehow had multiple accounts available.
    // This likely indicates some issue with AccountProfileMapper.
    kManagedProfileAlreadyInitializedMultipleAccountsAndNewlySignedIn = 11,
    kManagedProfileNewlyInitializedMultipleAccounts = 12,

    kMaxValue = kManagedProfileNewlyInitializedMultipleAccounts
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/signin/enums.xml:IOSProfileInitializationOutcome)

  // Performs any necessary first-time setup (notably, signing in the assigned
  // managed account to a managed profile), and then marks the profile as
  // initialized.
  ProfileInitializationOutcome PerformProfileInitializationIfNecessary();

  // Returns the cached MDM errors associated with `identity`. If the cache
  // is stale for `identity`, the entry might be removed.
  id<RefreshAccessTokenError> GetCachedMDMError(id<SystemIdentity> identity);

  // Handles an MDM error `error` associated with `identity`.
  // Returns whether the notification associated with `user_info` was fully
  // handled.
  bool HandleMDMError(id<SystemIdentity> identity,
                      id<RefreshAccessTokenError> error);

  // Invoked when the MDM error associated with `identity` has been handled.
  void MDMErrorHandled(id<SystemIdentity> identity, bool is_blocked);

  // Verifies that the authenticated user is still associated with a valid
  // SystemIdentity. This method must only be called when the user is
  // authenticated with the shared authentication library. If there is no valid
  // SystemIdentity associated with the currently authenticated user, or the
  // identity is `invalid_identity`, this method will sign the user out.
  //
  // `invalid_identity` is an additional identity to consider invalid. It can be
  // nil if there is no such additional identity to ignore.
  //
  // `device_restore` should be true only when called from `Initialize()` and
  // Chrome is started after a device restore.
  void HandleForgottenIdentity(id<SystemIdentity> invalid_identity,
                               bool device_restore);
  void HandleForgottenIdentityCallback(const CoreAccountInfo account_info);

  // Checks if the authenticated identity was removed by calling
  // `HandleForgottenIdentity`. Reloads the OAuth2 token service accounts if the
  // authenticated identity is still present.
  void ReloadCredentialsFromIdentities();

  // signin::IdentityManager::Observer implementation.
  void OnPrimaryAccountChanged(
      const signin::PrimaryAccountChangeEvent& event_details) override;

  // ChromeAccountManagerService::Observer implementation.
  void OnIdentitiesInProfileChanged() override;
  void OnRefreshTokenUpdated(id<SystemIdentity> identity) override;
  void OnAccessTokenRefreshFailed(id<SystemIdentity> identity,
                                  id<RefreshAccessTokenError> error,
                                  const std::set<std::string>& scopes) override;

  // Fires `OnPrimaryAccountRestricted` on all observers.
  void FirePrimaryAccountRestricted();

  // Notification for prefs::kSigninAllowed.
  void OnSigninAllowedChanged(const std::string& name);

  // Notification for prefs::kSigninAllowedOnDevice.
  void OnSigninAllowedOnDeviceChanged(const std::string& name);

  // Notification for prefs::kBrowserSigninPolicy.
  void OnBrowserSigninPolicyChanged(const std::string& name);

  // Fires `OnServiceStatusChanged` on all observers.
  void FireServiceStatusNotification();

  // Clears the account settings prefs of all removed accounts from device.
  void ClearAccountSettingsPrefsOfRemovedAccounts();

  // Returns whether the service’s profile is personal (that is: a profile that
  // does not contains any managed account).
  bool IsPersonalProfile();

  // Returns the active identities for MDM.
  NSArray<id<SystemIdentity>>* ActiveIdentities();

  // The delegate for this AuthenticationService. It is invalid to call any
  // method on this object except Initialize() or Shutdown() if this pointer
  // is null.
  std::unique_ptr<AuthenticationServiceDelegate> delegate_;

  // The profile associated to this service.
  raw_ptr<ProfileIOS> profile_;
  // Pointer to the KeyedServices used by AuthenticationService.
  raw_ptr<PrefService> pref_service_ = nullptr;
  raw_ptr<ChromeAccountManagerService> account_manager_service_ = nullptr;
  raw_ptr<signin::IdentityManager> identity_manager_ = nullptr;
  raw_ptr<syncer::SyncService> sync_service_ = nullptr;
  // TODO(crbug.com/484371187): Investigate if reentrancy can be removed.
  base::ObserverList<
      AuthenticationServiceObserver,
      true,
      base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>
      observer_list_;
  // Whether Initialize() has been called.
  bool initialized_ = false;

  // Whether the AuthenticationService is currently reloading credentials, used
  // to avoid an infinite reloading loop.
  bool is_reloading_credentials_ = false;

  // Whether the primary account was logged out because it became restricted.
  // It is used to respond to late observers.
  bool primary_account_was_restricted_ = false;

  // Map between account IDs and their associated MDM error.
  std::map<CoreAccountId, id<RefreshAccessTokenError>> cached_mdm_errors_;

  base::ScopedObservation<signin::IdentityManager,
                          signin::IdentityManager::Observer>
      identity_manager_observation_{this};

  base::ScopedObservation<ChromeAccountManagerService,
                          ChromeAccountManagerService::Observer>
      account_manager_service_observation_{this};

  // Registrar for prefs::kSigninAllowed.
  PrefChangeRegistrar pref_change_registrar_;
  // Registrar for prefs::kBrowserSigninPolicy and kSigninAllowedOnDevice.
  PrefChangeRegistrar local_pref_change_registrar_;

  base::WeakPtrFactory<AuthenticationService> weak_pointer_factory_;
};

#endif  // IOS_CHROME_BROWSER_SIGNIN_MODEL_AUTHENTICATION_SERVICE_H_
