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

#ifndef COMPONENTS_SIGNIN_IOS_BROWSER_ACCOUNT_CONSISTENCY_SERVICE_H_
#define COMPONENTS_SIGNIN_IOS_BROWSER_ACCOUNT_CONSISTENCY_SERVICE_H_

#include <deque>
#include <map>
#include <memory>
#include <set>
#include <string>

#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/signin/core/browser/gaia_cookie_manager_service.h"
#include "components/signin/core/browser/signin_manager.h"
#import "components/signin/ios/browser/manage_accounts_delegate.h"
#include "ios/web/public/active_state_manager.h"

namespace web {
class BrowserState;
class WebState;
class WebStatePolicyDecider;
}

class AccountReconcilor;
class SigninClient;

@class AccountConsistencyNavigationDelegate;
@class WKWebView;

// Handles actions necessary for Account Consistency to work on iOS. This
// includes setting the Account Consistency cookie (informing Gaia that the
// Account Consistency is on).
//
// This is currently only used when WKWebView is enabled.
class AccountConsistencyService : public KeyedService,
                                  public GaiaCookieManagerService::Observer,
                                  public SigninManagerBase::Observer,
                                  public web::ActiveStateManager::Observer {
 public:
  // Name of the preference property that persists the domains that have a
  // CHROME_CONNECTED cookie set by this service.
  static const char kDomainsWithCookiePref[];

  AccountConsistencyService(
      web::BrowserState* browser_state,
      AccountReconcilor* account_reconcilor,
      scoped_refptr<content_settings::CookieSettings> cookie_settings,
      GaiaCookieManagerService* gaia_cookie_manager_service,
      SigninClient* signin_client,
      SigninManager* signin_manager);
  ~AccountConsistencyService() override;

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

  // Sets the handler for |web_state| that reacts on Gaia responses with the
  // X-Chrome-Manage-Accounts header and notifies |delegate|.
  void SetWebStateHandler(web::WebState* web_state,
                          id<ManageAccountsDelegate> delegate);
  // Removes the handler associated with |web_state|.
  void RemoveWebStateHandler(web::WebState* web_state);

  // Enqueues a request to add the CHROME_CONNECTED cookie to |domain|. If the
  // cookie is already on |domain|, this function will do nothing unless
  // |force_update_if_too_old| is true. In this case, the cookie will be
  // refreshed if it is considered too old.
  void AddChromeConnectedCookieToDomain(const std::string& domain,
                                        bool force_update_if_too_old);

  // Enqueues a request to remove the CHROME_CONNECTED cookie to |domain|.
  // Does nothing if the cookie is not set on |domain|.
  void RemoveChromeConnectedCookieFromDomain(const std::string& domain);

  // Notifies the AccountConsistencyService that browsing data has been removed
  // for any time period.
  void OnBrowsingDataRemoved();

 private:
  friend class AccountConsistencyServiceTest;

  // The type of a cookie request.
  enum CookieRequestType {
    ADD_CHROME_CONNECTED_COOKIE,
    REMOVE_CHROME_CONNECTED_COOKIE
  };

  // A CHROME_CONNECTED cookie request to be applied by the
  // AccountConsistencyService.
  struct CookieRequest {
    static CookieRequest CreateAddCookieRequest(const std::string& domain);
    static CookieRequest CreateRemoveCookieRequest(const std::string& domain);
    CookieRequestType request_type;
    std::string domain;
  };

  // Loads the domains with a CHROME_CONNECTED cookie from the prefs.
  void LoadFromPrefs();

  // KeyedService implementation.
  void Shutdown() override;

  // Applies the pending CHROME_CONNECTED cookie requests one by one.
  void ApplyCookieRequests();

  // Called when the current CHROME_CONNECTED cookie request is done.
  void FinishedApplyingCookieRequest(bool success);

  // Returns the cached WKWebView if it exists, or creates one if necessary.
  // Can return nil if the browser state is not active.
  WKWebView* GetWKWebView();
  // Actually creates a WKWebView. Virtual for testing.
  virtual WKWebView* CreateWKWebView() NS_RETURNS_RETAINED;
  // Stops any page loading in the WKWebView currently in use and releases it.
  void ResetWKWebView();

  // Returns whether the CHROME_CONNECTED cookie should be added to |domain|.
  // If the cookie is already on |domain|, this function will return false
  // unless |force_update_if_too_old| is true. In this case, it will return true
  // if the cookie is considered to be too old.
  bool ShouldAddChromeConnectedCookieToDomain(const std::string& domain,
                                              bool force_update_if_too_old);

  // Adds CHROME_CONNECTED cookies on all the main Google domains.
  void AddChromeConnectedCookies();
  // Removes CHROME_CONNECTED cookies on all the Google domains where it was
  // set.
  void RemoveChromeConnectedCookies();

  // GaiaCookieManagerService::Observer implementation.
  void OnAddAccountToCookieCompleted(
      const std::string& account_id,
      const GoogleServiceAuthError& error) override;
  void OnGaiaAccountsInCookieUpdated(
      const std::vector<gaia::ListedAccount>& accounts,
      const std::vector<gaia::ListedAccount>& signed_out_accounts,
      const GoogleServiceAuthError& error) override;

  // SigninManagerBase::Observer implementation.
  void GoogleSigninSucceeded(const std::string& account_id,
                             const std::string& username,
                             const std::string& password) override;
  void GoogleSignedOut(const std::string& account_id,
                       const std::string& username) override;

  // ActiveStateManager::Observer implementation.
  void OnActive() override;
  void OnInactive() override;

  // Browser state associated with the service, used to create WKWebViews.
  web::BrowserState* browser_state_;
  // Service managing accounts reconciliation, notified of GAIA responses with
  // the X-Chrome-Manage-Accounts header
  AccountReconcilor* account_reconcilor_;
  // Cookie settings currently in use for |browser_state_|, used to check if
  // setting CHROME_CONNECTED cookies is valid.
  scoped_refptr<content_settings::CookieSettings> cookie_settings_;
  // Service managing the Gaia cookies, observed to be notified of the state of
  // reconciliation.
  GaiaCookieManagerService* gaia_cookie_manager_service_;
  // Signin client, used to access prefs.
  SigninClient* signin_client_;
  // Signin manager, observed to be notified of signin and signout events.
  SigninManager* signin_manager_;

  // Whether a CHROME_CONNECTED cookie request is currently being applied.
  bool applying_cookie_requests_;
  // The queue of CHROME_CONNECTED cookie requests to be applied.
  std::deque<CookieRequest> cookie_requests_;
  // The map between domains where a CHROME_CONNECTED cookie is present and
  // the time when the cookie was last updated.
  std::map<std::string, base::Time> last_cookie_update_map_;

  // Web view used to apply the CHROME_CONNECTED cookie requests.
  base::scoped_nsobject<WKWebView> web_view_;
  // Navigation delegate of |web_view_| that informs the service when a cookie
  // request has been applied.
  base::scoped_nsobject<AccountConsistencyNavigationDelegate>
      navigation_delegate_;

  // Handlers reacting on GAIA responses with the X-Chrome-Manage-Accounts
  // header set.
  std::map<web::WebState*, std::unique_ptr<web::WebStatePolicyDecider>>
      web_state_handlers_;

  DISALLOW_COPY_AND_ASSIGN(AccountConsistencyService);
};

#endif  // COMPONENTS_SIGNIN_IOS_BROWSER_ACCOUNT_CONSISTENCY_SERVICE_H_
