// 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.

#ifndef IOS_CHROME_BROWSER_SIGNIN_MODEL_SYSTEM_IDENTITY_MANAGER_H_
#define IOS_CHROME_BROWSER_SIGNIN_MODEL_SYSTEM_IDENTITY_MANAGER_H_

#import <UIKit/UIKit.h>

#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>

#include "base/functional/callback.h"
#include "base/functional/callback_forward.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "ios/chrome/browser/signin/model/capabilities_types.h"
#include "ios/chrome/browser/signin/model/system_identity_manager_observer.h"

class GaiaId;
@protocol RefreshAccessTokenError;
@protocol SystemIdentity;
@protocol SystemIdentityInteractionManager;
class SystemIdentityManagerObserver;

// Provider for building external privacy contexts, which requires a
// UIViewController (e.g. to present system UI).
@protocol ExternalPrivacyContextUIProvider <NSObject>

// Returns the view controller to use for presenting UI during External Privacy
// Context building.
- (UIViewController*)viewControllerForExternalPrivacyContext;

// Notifies the provider that an External Privacy Context build is requested.
// The provider should block the UI during the build.
- (void)blockUIForExternalPrivacyContextBuild;

// Notifies the provider that the External Privacy Context build is done.
// The provider should unblock the UI.
- (void)unblockUIOnExternalPrivacyContextBuilt;

@end

// SystemIdentityManager is Chrome's interface to the iOS shared authentication
// library: It provides access to accounts on the device and information about
// them, independent of whether or not the user is signed in to Chrome, and
// whether the accounts were added through Chrome or through some other Google
// app. It also allows adding accounts to the device via
// `SystemIdentityInteractionManager`, and displaying some account-related UIs.
class SystemIdentityManager {
 public:
  using CapabilityResult = SystemIdentityCapabilityResult;

  // Value returned by IdentityIteratorCallback.
  enum class IteratorResult {
    kContinueIteration,
    kInterruptIteration,
  };

  // Value representing an OAuth access token.
  struct AccessTokenInfo {
    // The access token itself.
    std::string token;

    // The time at which this access token will expire. This will be set to the
    // NULL time value of `base::Time()` when no expiration time is available.
    base::Time expiration_time;
  };

  // Struct to hold all the parameters needed to present a dialog like
  // WebAndAppSettingDetails, AccountDetails or LinkedServicesSettingsDetails.
  struct PresentDialogConfiguration {
   public:
    // Identity to use for the dialog to present.
    id<SystemIdentity> identity;
    // View controller who present the dialog.
    UIViewController* view_controller;
    // Whether the presentation should be animated or not.
    bool animated;
    // Completion block that will be called once the dialog is dismissed.
    base::OnceClosure dismissal_completion;

    PresentDialogConfiguration();
    ~PresentDialogConfiguration();
    PresentDialogConfiguration(PresentDialogConfiguration&&);
    PresentDialogConfiguration(const PresentDialogConfiguration&) = delete;
    PresentDialogConfiguration& operator=(const PresentDialogConfiguration&) =
        delete;
  };

  // Callback invoked for each id<SystemIdentity> when iterating over them
  // with `IterateOverIdentities()`. The returned value can be used to stop
  // the iteration prematurely.
  using IdentityIteratorCallback =
      base::RepeatingCallback<IteratorResult(id<SystemIdentity>)>;

  // Callback returned when presenting an account detail view. This callback
  // can be invoked to dismiss the view (with animation if `animated` is true).
  using DismissViewCallback = base::OnceCallback<void(bool animated)>;

  // Callback invoked when the `ForgetIdentity()` operation completes.
  using ForgetIdentityCallback = base::OnceCallback<void(NSError*)>;

  // Callback invoked when the `GetAccessToken()` operation completes.
  // TODO(crbug.com/502126003): Delete this callback when
  // AccessTokenRequestCallback is used instead.
  using AccessTokenCallback =
      base::OnceCallback<void(std::optional<AccessTokenInfo>, NSError*)>;

  // Callback invoked when the `GetAccessToken()` operation completes.
  using AccessTokenRequestCallback = base::OnceCallback<void(
      base::expected<AccessTokenInfo, GoogleServiceAuthError>)>;

  // Callback invoked when the `GetHostedDomain()` operation completes.
  using HostedDomainCallback = base::OnceCallback<void(NSString*, NSError*)>;

  // Callback invoked when the `FetchTokenAuthURL()` operation completes.
  using AuthenticatedURLCallback = base::OnceCallback<void(NSURL*, NSError*)>;

  // Callback invoked when the `FetchCapabilities()` operation completes.
  using FetchCapabilitiesCallback =
      base::OnceCallback<void(std::map<std::string, CapabilityResult>)>;

  // Callback invoked when the `FetchCapabilities()` operation completes for a
  // subset of the capabilities. Can be called multiple times as capabilities
  // are fetched.
  using FetchPartialCapabilitiesCallback =
      base::RepeatingCallback<void(std::map<std::string, CapabilityResult>)>;

  // Callback invoked when the `FetchCapabilitiesWithPartial()` operation
  // completes.
  using FetchCapabilitiesCompletion = base::OnceClosure;

  // Callback invoked when `HandleMDMNotification` completes. It is invoked
  // with a boolean indicating whether the device is blocked or not.
  using HandleMDMCallback = base::OnceCallback<void(bool)>;

  SystemIdentityManager();

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

  virtual ~SystemIdentityManager();

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

  // Presents a new Account Details view and returns a callback that can be
  // used to dismiss the view (can be ignored if not needed).
  // * `identity` is the identity used to present the view.
  // * `view_controller` is the view used to present the details.
  // * `animated` controls whether the view is presented with an animation.
  // * `dismissal_completion` is called once the dialog is dismissed.
  DismissViewCallback PresentAccountDetailsController(
      id<SystemIdentity> identity,
      UIViewController* view_controller,
      bool animated,
      base::OnceClosure dismissal_completion);

  // Presents a new Web and App Setting Details view and returns a callback
  // that can be used to dismiss the view (can be ignore if not needed).
  // * `identity` is the identity used to present the view.
  // * `view_controller` is the view used to present the details.
  // * `animated` controls whether the view is presented with an animation.
  // * `dismissal_completion` is called once the dialog is dismissed.
  DismissViewCallback PresentWebAndAppSettingDetailsController(
      id<SystemIdentity> identity,
      UIViewController* view_controller,
      bool animated,
      base::OnceClosure dismissal_completion);

  // Presents a new Linked Services Settings Details view and returns a callback
  // that can be used to dismiss the view (can be ignore if not needed).
  // * `identity` is the identity used to present the view.
  // * `view_controller` is the view used to present the details.
  // * `animated` controls whether the view is presented with an animation.
  // * `dismissal_completion` is called once the dialog is dismissed.
  DismissViewCallback PresentLinkedServicesSettingsDetailsController(
      id<SystemIdentity> identity,
      UIViewController* view_controller,
      bool animated,
      base::OnceClosure dismissal_completion);

  // Returns whether signin is supported by the provider.
  virtual bool IsSigninSupported() = 0;

  // Handles open URL authentication callback. Should be called within
  // `-[UISceneDelegate application:openURLContexts:]` context. Returns
  // whether one the URLs was actually handled.
  virtual bool HandleSessionOpenURLContexts(
      UIScene* scene,
      NSSet<UIOpenURLContext*>* url_contexts) = 0;

  // Discards scene session data. Should be called within
  // `-[UIApplicationDelegate application:didDiscardSceneSessions:]`.
  virtual void ApplicationDidDiscardSceneSessions(
      NSSet<UISceneSession*>* scene_sessions) = 0;

  // Dismisses all the dialogs created by the abstracted flows.
  virtual void DismissDialogs() = 0;

  // Creates a new SystemIdentityInteractionManager instance.
  virtual id<SystemIdentityInteractionManager> CreateInteractionManager() = 0;

  // Iterates over all known identities, sorted by the ordering used in
  // account manager, which is typically based on the keychain ordering
  // of the accounts.
  virtual void IterateOverIdentities(IdentityIteratorCallback callback) = 0;

  // Asynchronously forgets `identity` and logs the user out. The callback
  // is invoked on the calling sequence when the operation completes.
  virtual void ForgetIdentity(id<SystemIdentity> identity,
                              ForgetIdentityCallback callback) = 0;

  // Returns true if the identity was removed by calling `ForgetIdentity()`.
  // Returns false If the identity was not removed or disappeared without
  // calling `ForgetIdentity()`.
  virtual bool IdentityRemovedByUser(const GaiaId& gaia_id) = 0;

  // Asynchronously retrieves access tokens for `identity` with `scopes`. The
  // callback is invoked on the calling sequence when the operation completes.
  // Uses the default client id and client secret.
  virtual void GetAccessToken(id<SystemIdentity> identity,
                              const std::set<std::string>& scopes,
                              AccessTokenCallback callback) = 0;

  // Asynchronously retrieves access tokens for `identity` with `scopes`. The
  // callback is invoked on the calling sequence when the operation completes.
  // TODO(crbug.com/502126003): remove this method when the one with
  // AccessTokenRequestCallback is used instead.
  virtual void GetAccessToken(id<SystemIdentity> identity,
                              const std::string& client_id,
                              const std::set<std::string>& scopes,
                              AccessTokenCallback callback) = 0;

  // Asynchronously retrieves access tokens for `identity` with `scopes`. The
  // callback is invoked on the calling sequence when the operation completes.
  // TODO(crbug.com/502440730): make this method pure virtual after updating the
  // internal implementation.
  virtual void GetAccessToken(id<SystemIdentity> identity,
                              const std::string& client_id,
                              const std::set<std::string>& scopes,
                              AccessTokenRequestCallback callback) {}

  // Asynchronously retrieves access tokens for `identity` with `scopes`. The
  // callback is invoked on the calling sequence when the operation completes.
  // Uses the default client id and client secret.
  // TODO(crbug.com/502440730): make this method pure virtual after updating the
  // internal implementation.
  virtual void GetAccessToken(id<SystemIdentity> identity,
                              const std::set<std::string>& scopes,
                              AccessTokenRequestCallback callback) {}

  // Asynchronously fetches the avatar for `identity` from the network and
  // store it in the cache. The image can be large to avoid pixelation on
  // high resolution devices. Observers will be notified when the avatar is
  // available by the `OnIdentityUpdated()` method.
  virtual void FetchAvatarForIdentity(id<SystemIdentity> identity) = 0;

  // Synchronously returns the last cached avatar for `identity`. Should be
  // preceded by a call to `FetchAvatarForIdentity()` to populate the cache.
  virtual UIImage* GetCachedAvatarForIdentity(id<SystemIdentity> identity) = 0;

  // Asynchronously fetch the identity hosted domain. The callback is invoked
  // on the calling sequence when the operation completes.
  virtual void GetHostedDomain(id<SystemIdentity> identity,
                               HostedDomainCallback callback) = 0;

  // Returns the hosted domain for `identity` from the cache. Returns:
  //   + nil if the hosted domain value has not been fetched from the server,
  //   + an empty string if this is a consumer account (e.g. foo@gmail.com),
  //   + the hosted domain as a non-empty string otherwise.
  virtual NSString* GetCachedHostedDomainForIdentity(
      id<SystemIdentity> identity) = 0;

  // Asynchronously returns the capabilities for `identity`.
  // TODO(crbug.com/517899430): remove this method once it's replaced by
  // FetchCapabilitiesWithPartial.
  virtual void FetchCapabilities(id<SystemIdentity> identity,
                                 const std::vector<std::string>& names,
                                 FetchCapabilitiesCallback callback) = 0;

  // Asynchronously returns the capabilities for `identity`.
  // * `partial_callback` is called multiple times as a subset of capabilities
  // in `names` are fetched.
  // * `completion` is called once after all capabilities in `names` are fetched
  // or the fetch has failed. No more calls to `partial_callback` are expected
  // after `completion` is called.
  // TODO(crbug.com/517899430): Have `completion` as the last parameter.
  virtual void FetchCapabilitiesWithPartial(
      id<SystemIdentity> identity,
      const std::vector<std::string>& names,
      FetchCapabilitiesCompletion completion,
      FetchPartialCapabilitiesCallback partial_callback) = 0;

  // Registers the provider for building external privacy context.
  virtual void RegisterExternalPrivacyContextProvider(
      id<ExternalPrivacyContextUIProvider> provider) = 0;

  // Unregisters the provider for building external privacy context.
  virtual void UnregisterExternalPrivacyContextProvider(
      id<ExternalPrivacyContextUIProvider> provider) = 0;

  // Called when a provider is ready for capabilities fetching.
  // The `provider` must have been previously registered via
  // `RegisterExternalPrivacyContextProvider`.
  virtual void ExternalPrivacyContextProviderReady(
      id<ExternalPrivacyContextUIProvider> provider) = 0;

  // Asynchronously handles a potential MDM (Mobile Device Management) event.
  // The callback is invoked on the calling sequence when the operation
  // completes.
  // Returns YES if the MDM notification display process is successfully
  // initiated.
  // Should not be called if the error is not an MDM error.
  virtual bool DisplayMDMNotification(id<SystemIdentity> identity,
                                      const GoogleServiceAuthError& error,
                                      HandleMDMCallback callback) = 0;

  // Asynchronously handles a potential MDM (Mobile Device Management) event.
  // The callback is invoked on the calling sequence when the operation
  // completes.
  // Returns YES if the device status is blocked.
  // This method will be removed during the MDM cleanup.
  virtual bool HandleMDMNotification(
      id<SystemIdentity> identity,
      NSArray<id<SystemIdentity>>* active_identities,
      id<RefreshAccessTokenError> error,
      HandleMDMCallback callback) = 0;

  // Returns whether the `error` is due to restricted access to the scopes in
  // the access token request.
  // TODO(crbug.com/425592221): Convert to pure virtual method.
  virtual bool IsScopeLimitedError(id<RefreshAccessTokenError> error);

  // Returns whether the `error` associated with `identity` is due to MDM
  // (Mobile Device Management) or not.
  virtual bool IsMDMError(id<SystemIdentity> identity, NSError* error) = 0;

  // Asynchronously fetches the token auth URL that can be used to
  // authorize a webview for the given identity.
  // The callback is invoked on the calling sequence when the operation
  // completes.
  virtual void FetchTokenAuthURL(id<SystemIdentity> identity,
                                 NSURL* target_url,
                                 AuthenticatedURLCallback callback) = 0;

 protected:
  // Invokes `OnIdentityListChanged(...)` for all observers.
  void FireIdentityListChanged();

  // Invokes `OnIdentityUpdated(...)` for all observers.
  void FireIdentityUpdated(id<SystemIdentity> identity);

  // Invokes OnIdentityRefreshTokenUpdated(...)` for all observers.
  void FireIdentityRefreshTokenUpdated(id<SystemIdentity> identity);

  // Invokes OnIdentityAccessTokenRefreshFailed(...)` for all observers.
  void FireIdentityAccessTokenRefreshFailed(
      id<SystemIdentity> identity,
      id<RefreshAccessTokenError> error,
      const std::set<std::string>& scopes);

  // Presents a new Account Details view and returns a callback that can be
  // used to dismiss the view (can be ignored if not needed).
  virtual DismissViewCallback PresentAccountDetailsController(
      PresentDialogConfiguration configuration) = 0;

  // Presents a new Web and App Setting Details view and returns a callback
  // that can be used to dismiss the view (can be ignored if not needed).
  virtual DismissViewCallback PresentWebAndAppSettingDetailsController(
      PresentDialogConfiguration configuration) = 0;

  // Presents a new Linked Services Settings Details view and returns a callback
  // that can be used to dismiss the view (can be ignored if not needed).
  virtual DismissViewCallback PresentLinkedServicesSettingsDetailsController(
      PresentDialogConfiguration configuration) = 0;

  // The SystemIdentityManager is sequence-affine. This is protected to
  // allow sub-classes access to the member field for use in DCHECK().
  SEQUENCE_CHECKER(sequence_checker_);

 private:
  // TODO(crbug.com/484371187): Investigate if reentrancy can be removed.
  base::ObserverList<
      SystemIdentityManagerObserver,
      true,
      base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>
      observers_;
};

#endif  // IOS_CHROME_BROWSER_SIGNIN_MODEL_SYSTEM_IDENTITY_MANAGER_H_
