// 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_PASSWORD_MANAGER_CORE_BROWSER_ANDROID_AFFILIATION_AFFILIATED_MATCH_HELPER_H_
#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_ANDROID_AFFILIATION_AFFILIATED_MATCH_HELPER_H_

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

#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
#include "components/password_manager/core/browser/password_store.h"
#include "components/password_manager/core/browser/password_store_consumer.h"

namespace autofill {
struct PasswordForm;
}  // namespace autofill

namespace password_manager {

class AffiliationService;

// Interacts with the AffiliationService on behalf of the PasswordStore.
// For each GetLogins() request, it provides the PasswordStore with a list of
// additional realms that are affiliation-based matches to the observed realm.
//
// Currently, the only supported use-case is obtaining Android applications
// affiliated with the web site containing the observed form. This is achieved
// by implementing the "proactive fetching" strategy for interacting with the
// AffiliationService (see affiliation_service.h for details), with Android
// applications playing the role of facet Y.
//
// More specifically, this class prefetches affiliation information on start-up
// for all Android applications that the PasswordStore has credentials stored
// for. Then, the actual GetLogins() can be restricted to the cache, so that
// realms of the observed web forms will never be looked up against the
// Affiliation API.
class AffiliatedMatchHelper : public PasswordStore::Observer,
                              public PasswordStoreConsumer {
 public:
  // Callback to returns the list of affiliated signon_realms (as per defined in
  // autofill::PasswordForm) to the caller.
  typedef base::Callback<void(const std::vector<std::string>&)>
      AffiliatedRealmsCallback;

  typedef base::Callback<void(
      std::vector<std::unique_ptr<autofill::PasswordForm>>)>
      PasswordFormsCallback;

  // The |password_store| must outlive |this|. Both arguments must be non-NULL,
  // except in tests which do not Initialize() the object.
  AffiliatedMatchHelper(
      PasswordStore* password_store,
      std::unique_ptr<AffiliationService> affiliation_service);
  ~AffiliatedMatchHelper() override;

  // Schedules deferred initialization.
  void Initialize();

  // Retrieves realms of Android applications affiliated with the realm of the
  // |observed_form| if it is web-based. Otherwise, yields the empty list. The
  // |result_callback| will be invoked in both cases, on the same thread.
  virtual void GetAffiliatedAndroidRealms(
      const PasswordStore::FormDigest& observed_form,
      const AffiliatedRealmsCallback& result_callback);

  // Retrieves realms of web sites affiliated with the Android application that
  // |android_form| belongs to and invokes |result_callback| on the same thread;
  // or yields the empty list if  |android_form| is not an Android credential.
  // NOTE: This will issue an on-demand network request against the Affiliation
  // API if affiliations of the Android application are not cached. However, as
  // long as the |android_form| is from the PasswordStore, this should rarely
  // happen as affiliation information for those applications are prefetched.
  virtual void GetAffiliatedWebRealms(
      const PasswordStore::FormDigest& android_form,
      const AffiliatedRealmsCallback& result_callback);

  // Retrieves affiliation and branding information about the Android
  // credentials in |forms|, sets |affiliated_web_realm|, |app_display_name| and
  // |app_icon_url| of forms, and invokes |result_callback|.
  // NOTE: This will not issue an on-demand network request. If a request to
  // cache fails, no affiliation and branding information will be injected into
  // corresponding form.
  virtual void InjectAffiliationAndBrandingInformation(
      std::vector<std::unique_ptr<autofill::PasswordForm>> forms,
      const PasswordFormsCallback& result_callback);

  // Returns whether or not |form| represents an Android credential.
  static bool IsValidAndroidCredential(const PasswordStore::FormDigest& form);

  // Returns whether or not |form| represents a valid Web credential for the
  // purposes of affiliation-based matching.
  static bool IsValidWebCredential(const PasswordStore::FormDigest& form);

  // I/O heavy initialization on start-up will be delayed by this long.
  // This should be high enough not to exacerbate start-up I/O contention too
  // much, but also low enough that the user be able log-in shortly after
  // browser start-up into web sites using Android credentials.
  // TODO(engedy): See if we can tie this instead to some meaningful event.
  static constexpr base::TimeDelta kInitializationDelayOnStartup =
      base::TimeDelta::FromSeconds(8);

 private:
  // Reads all autofillable credentials from the password store and starts
  // observing the store for future changes.
  void DoDeferredInitialization();

  // Called back by AffiliationService to supply the list of facets affiliated
  // with |original_facet_uri| so that a GetAffiliatedAndroidRealms() call can
  // be completed.
  void CompleteGetAffiliatedAndroidRealms(
      const FacetURI& original_facet_uri,
      const AffiliatedRealmsCallback& result_callback,
      const AffiliatedFacets& results,
      bool success);

  // Called back by AffiliationService to supply the list of facets affiliated
  // with the Android application that GetAffiliatedWebRealms() was called with,
  // so that the call can be completed.
  void CompleteGetAffiliatedWebRealms(
      const AffiliatedRealmsCallback& result_callback,
      const AffiliatedFacets& results,
      bool success);

  // Called back by AffiliationService to supply the list of facets affiliated
  // with the Android credential in |form|. Injects affiliation and branding
  // information by setting |affiliated_web_realm|, |app_display_name| and
  // |app_icon_url| on |form| if |success| is true and |results| is non-empty.
  // Invokes |barrier_closure|.
  void CompleteInjectAffiliationAndBrandingInformation(
      autofill::PasswordForm* form,
      base::Closure barrier_closure,
      const AffiliatedFacets& results,
      bool success);

  // PasswordStore::Observer:
  void OnLoginsChanged(const PasswordStoreChangeList& changes) override;

  // PasswordStoreConsumer:
  void OnGetPasswordStoreResults(
      std::vector<std::unique_ptr<autofill::PasswordForm>> results) override;

  PasswordStore* const password_store_;

  // Being the sole consumer of AffiliationService, |this| owns the service.
  std::unique_ptr<AffiliationService> affiliation_service_;

  base::WeakPtrFactory<AffiliatedMatchHelper> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(AffiliatedMatchHelper);
};

}  // namespace password_manager

#endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_ANDROID_AFFILIATION_AFFILIATED_MATCH_HELPER_H_
