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

#ifndef COMPONENTS_SYNC_SERVICE_SYNC_AUTH_MANAGER_H_
#define COMPONENTS_SYNC_SERVICE_SYNC_AUTH_MANAGER_H_

#include <memory>
#include <string>
#include <vector>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/account_managed_status_finder_outcome.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/sync/engine/connection_status.h"
#include "components/sync/service/sync_token_status.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "net/base/backoff_entry.h"

namespace signin {
class AccessTokenFetcher;
class AccountManagedStatusFinder;
}  // namespace signin

namespace syncer {

struct SyncCredentials;

struct SyncAccountInfo {
  CoreAccountInfo account_info;
  bool is_sync_consented = false;
  signin::AccountManagedStatusFinderOutcome managed_status =
      signin::AccountManagedStatusFinderOutcome::kPending;

  friend bool operator==(const SyncAccountInfo&,
                         const SyncAccountInfo&) = default;
};

// SyncAuthManager tracks the account to be used for Sync and its authentication
// state. Note that this account may or may not be the primary account (as per
// IdentityManager::GetPrimaryAccountInfo() etc).
class SyncAuthManager : public signin::IdentityManager::Observer {
 public:
  class Delegate {
   public:
    virtual ~Delegate() = default;

    // Called when the existence of an authenticated account changes. It's
    // guaranteed that this is only called for going from "no account" to "have
    // account" or vice versa, or if the existing account's `is_primary` bit
    // changed. I.e. SyncAuthManager will never directly switch from one account
    // to a different one. Call GetActiveAccountInfo to get the new state.
    virtual void SyncAuthAccountStateChanged() = 0;

    // Called when the credential state changes, i.e. an access token was
    // added/changed/removed. Call GetCredentials to get the new state.
    virtual void SyncAuthCredentialsChanged() = 0;
  };

  // `identity_manager` may be null (this is the case if local Sync is enabled),
  // but if non-null, must outlive this object. `delegate` must not be null and
  // must outlive this object.
  SyncAuthManager(signin::IdentityManager* identity_manager,
                  Delegate* delegate,
                  base::TimeDelta account_managed_status_finder_timeout);

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

  ~SyncAuthManager() override;

  // Tells the tracker to start listening for changes to the account/sign-in
  // status. This gets called during SyncService initialization, except in the
  // case of local Sync. Before this is called, GetActiveAccountInfo will always
  // return an empty AccountInfo. Note that this will *not* trigger any
  // callbacks, even if there is an active account afterwards.
  void RegisterForAuthNotifications();

  // Returns whether all relevant account information as returned by
  // GetActiveAccountInfo() has been fully loaded.
  bool IsActiveAccountInfoFullyLoaded() const;

  // Returns the account which should be used when communicating with the Sync
  // server. Note that this account may not be blessed for Sync-the-feature.
  SyncAccountInfo GetActiveAccountInfo() const;

  // Returns the last auth error that was encountered. The error could have come
  // from the Sync server or from the IdentityManager.
  GoogleServiceAuthError GetLastAuthError() const;

  // Returns the time at which the last auth error was set.
  base::Time GetLastAuthErrorTime() const;

  // Returns whether we are in the "Sync paused" state. That means there is a
  // primary account, but the user signed out in the content area, and so we
  // don't have credentials for it anymore.
  bool IsSyncPaused() const;

  // Returns the credentials to be passed to the SyncEngine.
  SyncCredentials GetCredentials() const;

  // Returns the state of the access token and token request, for display in
  // internals UI.
  SyncTokenStatus GetSyncTokenStatus() const;

  // Requests an access token. runs `callback` with the result (can be called
  // synchronously or asynchronously).
  void FetchAccessToken(
      base::OnceCallback<void(signin::AccessTokenInfo)> callback);

  // Called by SyncServiceImpl when Sync starts up and will try talking to
  // the server soon. This initiates fetching an access token.
  void ConnectionOpened();

  // Called by SyncServiceImpl when the status of the connection to the Sync
  // server changed. Updates auth error state accordingly.
  void ConnectionStatusChanged(ConnectionStatus status);

  // Called by SyncServiceImpl when the connection to the Sync server is
  // closed (due to Sync being shut down). Clears all related state (such as
  // cached access token, error from the server, etc).
  void ConnectionClosed();

  // signin::IdentityManager::Observer implementation.
  void OnPrimaryAccountChanged(
      const signin::PrimaryAccountChangeEvent& event) override;
  void OnRefreshTokenUpdatedForAccount(
      const CoreAccountInfo& account_info) override;
  void OnRefreshTokenRemovedForAccount(
      const CoreAccountId& account_id) override;
  void OnErrorStateOfRefreshTokenUpdatedForAccount(
      const CoreAccountInfo& account_info,
      const GoogleServiceAuthError& error,
      signin_metrics::SourceForRefreshTokenOperation token_operation_source)
      override;
  void OnRefreshTokensLoaded() override;
  void OnIdentityManagerShutdown(
      signin::IdentityManager* identity_manager) override;

  // Test-only methods for inspecting/modifying internal state.
  bool IsRetryingAccessTokenFetchForTest() const;
  void ResetRequestAccessTokenBackoffForTest();

 private:
  // Helper class that ensures the account's managed-status gets queried
  // whenever the account itself changes.
  class ActiveAccount {
   public:
    // The `account_changed_callback` will be called whenever an account's
    // managed-ness is determined asynchronously.
    ActiveAccount(signin::IdentityManager* identity_manager,
                  base::TimeDelta managed_status_finder_timeout,
                  base::RepeatingClosure account_changed_callback);
    ~ActiveAccount();

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

    // To be called when the basic account info changes (e.g. sign in or sign
    // out). Will kick off determining the managed-ness status, which may
    // complete synchronously or asynchronously.
    void Set(const SyncAccountInfo& new_account);

    const SyncAccountInfo& Get() const;

   private:
    // Starts the process of determining the account type (managed or not). This
    // may be synchronous or asynchronous.
    void StartDeterminingAccountType();
    // Callback for the async case.
    void AccountTypeDeterminedAsynchronously();

    const raw_ptr<signin::IdentityManager> identity_manager_;
    const base::TimeDelta managed_status_finder_timeout_;
    base::RepeatingClosure account_changed_callback_;
    SyncAccountInfo account_info_;
    std::unique_ptr<signin::AccountManagedStatusFinder> managed_status_finder_;
    base::Time managed_status_finder_start_time_;
  };

  // Updates `sync_account_` to the appropriate account (i.e.
  // DetermineAccountToUse()) if necessary, and notifies observers of any
  // changes (sign-in/sign-out/"primary" bit change). Note that changing from
  // one account to another is exposed to observers as a sign-out + sign-in.
  // Returns whether the syncing account was updated.
  bool UpdateSyncAccountIfNecessary();

  // Called by ActiveAccount when the account's managed-ness has been determined
  // asynchronously.
  void AccountManagednessDetermined();

  // Invalidates any current access token, which means invalidating it with the
  // IdentityManager and also dropping our own cached copy. Meant to be called
  // when we know the current token is invalid (e.g. expired). Does not do
  // anything about any scheduled or ongoing request.
  void InvalidateAccessToken();

  // Clears any access token we have, and cancels any pending or scheduled
  // request for one.
  void ClearAccessTokenAndRequest();

  // Schedules a request for an access token according to the current
  // `request_access_token_backoff_`. Usually called after some transient error.
  void ScheduleAccessTokenRequest();

  // Immediately starts an access token request, unless one is already ongoing.
  // If another request is scheduled for later, it is canceled. Any access token
  // we currently have is invalidated.
  void RequestAccessToken();

  // Callback for `ongoing_access_token_fetch_`.
  void AccessTokenFetched(GoogleServiceAuthError error,
                          signin::AccessTokenInfo access_token_info);

  void SetLastAuthError(const GoogleServiceAuthError& error);

  void NotifyAccessTokenCallbacks(const signin::AccessTokenInfo& token);

  const raw_ptr<signin::IdentityManager> identity_manager_;
  base::ScopedObservation<signin::IdentityManager,
                          signin::IdentityManager::Observer>
      identity_manager_observation_{this};
  const raw_ptr<Delegate> delegate_;

  bool registered_for_auth_notifications_ = false;

  // The account which we are using to sync. If this is non-empty, that does
  // *not* necessarily imply that Sync is actually running, e.g. because of
  // delayed startup.
  ActiveAccount sync_account_;

  // This is a cache of the last authentication response we received from
  // Chrome's identity/token management system.
  GoogleServiceAuthError last_auth_error_;
  base::Time last_auth_error_time_;

  // Whether Sync is currently connected to the server, i.e. ConnectionOpened()
  // has been called, but ConnectionClosed() hasn't. While this is false, we
  // don't try to get an access token. While it's true, we will *usually* have
  // either an access token or a pending/scheduled request for one, but this is
  // not guaranteed (e.g. in the case of a persistent auth error).
  bool connection_open_ = false;

  // The current access token. This is mutually exclusive with
  // `ongoing_access_token_fetch_` and `request_access_token_retry_timer_`:
  // We have at most one of a) an access token OR b) a pending request OR c) a
  // pending retry i.e. a scheduled request.
  signin::AccessTokenInfo access_token_info_;

  // Pending request for an access token. Non-null iff there is a request
  // ongoing.
  std::unique_ptr<signin::AccessTokenFetcher> ongoing_access_token_fetch_;

  // If RequestAccessToken fails with transient error then retry requesting
  // access token with exponential backoff.
  base::OneShotTimer request_access_token_retry_timer_;
  net::BackoffEntry request_access_token_backoff_;

  // Info about the state of our access token, for display in the internals UI.
  // "Partial" because this instance is not fully populated - in particular,
  // `has_token` and `next_token_request_time` get computed on demand.
  SyncTokenStatus partial_token_status_;

  // Whether there was a retry done to fetch the access token when the request
  // was cancelled for the first time. This works around an issue that can only
  // happen once during browser startup, so it's sufficient to have a single
  // retry (i.e. not per request).
  bool access_token_retried_ = false;

  std::vector<base::OnceCallback<void(signin::AccessTokenInfo)>>
      access_token_callbacks_;

  base::WeakPtrFactory<SyncAuthManager> weak_ptr_factory_{this};
};

}  // namespace syncer

#endif  // COMPONENTS_SYNC_SERVICE_SYNC_AUTH_MANAGER_H_
