// Copyright 2017 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_OMNIBOX_BROWSER_REMOTE_SUGGESTIONS_SERVICE_H_
#define COMPONENTS_OMNIBOX_BROWSER_REMOTE_SUGGESTIONS_SERVICE_H_

#include <map>
#include <memory>
#include <optional>
#include <string>

#include "base/functional/callback_forward.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/types/optional_ref.h"
#include "base/unguessable_token.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/enterprise_search_aggregator_suggestions_service.h"
#include "components/search_engines/template_url.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "url/gurl.h"

class DocumentSuggestionsService;
class EnterpriseSearchAggregatorSuggestionsService;

using EnterpriseSearchAggregatorSuggestionType =
    AutocompleteMatch::EnterpriseSearchAggregatorType;

namespace network {
class SharedURLLoaderFactory;
class SimpleURLLoader;
struct ResourceRequest;
}  // namespace network

// The types of requests for remote suggestions.
// These values are written to logs. New enum values can be added, but existing
// enums must never be renumbered or deleted and reused.
// Must be kept in sync with RemoteRequestType enum and variant.
// LINT.IfChange(RemoteRequestType)
enum class RemoteRequestType {
  // Search suggestion requests.
  kSearch = 0,
  // Search suggestion warm-up requests.
  kSearchWarmup = 1,
  // Search suggestion requests to obtain images.
  kImages = 2,
  // Zero-prefix suggestion requests.
  kZeroSuggest = 3,
  // Zero-prefix suggestion prefetching requests.
  kZeroSuggestPrefetch = 4,
  // Document suggestion requests.
  kDocumentSuggest = 5,
  // Suggestion deletion requests.
  kDeletion = 6,
  // Enterprise Search Aggregator suggestion requests.
  kEnterpriseSearchAggregatorSuggest = 7,
  kMaxValue = kEnterpriseSearchAggregatorSuggest,
};
// LINT.ThenChange(
//     //tools/metrics/histograms/metadata/omnibox/enums.xml:RemoteRequestType,
//     //tools/metrics/histograms/metadata/omnibox/histograms.xml:RemoteRequestType
// )

// The event types recorded by the providers for remote suggestions. Each event
// must be logged at most once from when the provider is started until it is
// stopped.
// These values are written to logs. New enum values can be added, but existing
// enums must never be renumbered or deleted and reused.
enum class RemoteRequestEvent {
  // Cached response was synchronously converted to displayed matches.
  // Recorded for non-prefetch requests only.
  kCachedResponseConvertedToMatches = 0,
  // Request was sent.
  kRequestSent = 1,
  // Request was invalidated.
  kRequestInvalidated = 2,
  // Response was received asynchronously.
  kResponseReceived = 3,
  // Response was cached.
  kResponseCached = 4,
  // Response ended up being converted to displayed matches. This may happen due
  // to an empty displayed result set or an empty remote result set.
  // Recorded for non-prefetch requests only.
  kResponseConvertedToMatches = 5,
  kMaxValue = kResponseConvertedToMatches,
};

// A service to fetch suggestions from a search provider's suggest endpoint.
// Used by ZeroSuggestProvider, SearchProvider, DocumentProvider, and
// ImageService.
//
// This service is always sent the user's authentication state, so the
// suggestions always can be personalized. This service is also sometimes sent
// the user's current URL, so the suggestions are sometimes also contextual.
class RemoteSuggestionsService : public KeyedService {
 public:
  class Observer : public base::CheckedObserver {
   public:
    // Called when the request has been created. `request_id` identifies the
    // request. `request` is deleted after this call once the transfer starts.
    virtual void OnRequestCreated(const base::UnguessableToken& request_id,
                                  const network::ResourceRequest* request) {}
    // Called when the transfer has started. `request_id` identifies the
    // request. `request_body` is the HTTP POST upload body, if applicable.
    virtual void OnRequestStarted(const base::UnguessableToken& request_id,
                                  network::SimpleURLLoader* loader,
                                  const std::string& request_body) {}
    // Called when the transfer is done. `request_id` identifies the request.
    // `response_code` is the response status code. A status code of 200
    // indicates that the request has succeeded and `response_body` is
    // populated.
    virtual void OnRequestCompleted(
        const base::UnguessableToken& request_id,
        const int response_code,
        base::optional_ref<std::string> response_body) {}
  };

  // Called when the transfer has started asynchronously, e.g., after obtaining
  // an OAuth token.
  using StartCallback = base::OnceCallback<void(
      std::unique_ptr<network::SimpleURLLoader> loader)>;
  // Called when the transfer is done. `response_code` is the response status
  // code. A status code of 200 indicates that the request has succeeded and
  // `response_body` is populated.
  using CompletionCallback =
      base::OnceCallback<void(const network::SimpleURLLoader* source,
                              const int response_code,
                              std::optional<std::string> response_body)>;
  // Same as `StartCallback` but for requests that are associated with a
  // `request_index`.
  using IndexedStartCallback = base::RepeatingCallback<void(
      const int request_index,
      std::unique_ptr<network::SimpleURLLoader> loader)>;
  // Same as `CompletionCallback` but for requests that are associated with a
  // `request_index`.
  using IndexedCompletionCallback =
      base::RepeatingCallback<void(const int request_index,
                                   const network::SimpleURLLoader* source,
                                   const int response_code,
                                   std::optional<std::string> response_body)>;

  class Delegate {
   public:
    Delegate();
    virtual ~Delegate();
    Delegate(const Delegate&) = delete;
    Delegate& operator=(const Delegate&) = delete;

    // Called when the transfer is done. Delegates invocation of
    // `completion_callback`
    virtual void OnRequestCompleted(const network::SimpleURLLoader* source,
                                    const int response_code,
                                    std::optional<std::string> response_body,
                                    CompletionCallback completion_callback) = 0;

    virtual void OnIndexedRequestCompleted(
        const int request_index,
        const network::SimpleURLLoader* source,
        const int response_code,
        std::optional<std::string> response_body,
        IndexedCompletionCallback completion_callback) = 0;

   protected:
    base::WeakPtrFactory<Delegate> weak_ptr_factory_{this};
  };

  RemoteSuggestionsService(
      DocumentSuggestionsService* document_suggestions_service,
      EnterpriseSearchAggregatorSuggestionsService*
          enterprise_search_aggregator_suggestions_service,
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
  ~RemoteSuggestionsService() override;
  RemoteSuggestionsService(const RemoteSuggestionsService&) = delete;
  RemoteSuggestionsService& operator=(const RemoteSuggestionsService&) = delete;

  // Helper to set the time request of type `request_type` has started in
  // `time_request_sent_`.
  void SetTimeRequestSent(RemoteRequestType request_type, base::TimeTicks time);

  // Returns the suggest endpoint URL for `template_url`.
  // `search_terms_args` is used to build the endpoint URL.
  // `search_terms_data` is used to build the endpoint URL.
  static GURL EndpointUrl(
      const TemplateURL& template_url,
      const TemplateURLRef::SearchTermsArgs& search_terms_args,
      const SearchTermsData& search_terms_data);

  // Creates and returns a loader for remote suggestions for `template_url`.
  // It uses a number of signals to create the loader, including field trial
  // parameters.
  //
  // `template_url` must not be nullptr.
  // `search_terms_args` is used to build the endpoint URL.
  // `search_terms_data` is used to build the endpoint URL.
  // `completion_callback` will be invoked when the transfer is done.
  std::unique_ptr<network::SimpleURLLoader> StartSuggestionsRequest(
      RemoteRequestType request_type,
      bool is_off_the_record,
      const TemplateURL* template_url,
      TemplateURLRef::SearchTermsArgs search_terms_args,
      const SearchTermsData& search_terms_data,
      CompletionCallback completion_callback);

  // Creates and returns a loader for remote zero-prefix suggestions for
  // `template_url`. It uses a number of signals to create the loader, including
  // field trial parameters.
  //
  // `template_url` must not be nullptr.
  // `search_terms_args` is used to build the endpoint URL.
  // `search_terms_data` is used to build the endpoint URL.
  // `completion_callback` will be invoked when the transfer is done.
  //
  // When `timeout` is not empty, its value is set in `SimpleURLLoadaer` used
  // internally. When the timeout occurs, a nullptr is passed to the callback:
  // https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/simple_url_loader.cc;l=758-764;drc=128f35b3fb019f2fa67e1e798e113ed95f766096
  std::unique_ptr<network::SimpleURLLoader> StartZeroPrefixSuggestionsRequest(
      RemoteRequestType request_type,
      bool is_off_the_record,
      const TemplateURL* template_url,
      TemplateURLRef::SearchTermsArgs search_terms_args,
      const SearchTermsData& search_terms_data,
      CompletionCallback completion_callback,
      base::optional_ref<const base::TimeDelta> timeout = std::nullopt);

  // Creates and starts a document suggestion request for `query` asynchronously
  // after obtaining an OAuth2 token for the signed-in users.
  void CreateDocumentSuggestionsRequest(
      const std::u16string& query,
      bool is_off_the_record,
      metrics::OmniboxEventProto::PageClassification page_classification,
      StartCallback start_callback,
      CompletionCallback completion_callback);

  // Stops creating the request. Already created requests aren't affected.
  void StopCreatingDocumentSuggestionsRequest();

  // Creates and starts an enterprise search aggregator suggestion request using
  //  `suggest_url` and `response_body` asynchronously after obtaining an OAuth2
  //  token for signed-in enterprise users.
  void CreateEnterpriseSearchAggregatorSuggestionsRequest(
      const std::u16string& query,
      const GURL& suggest_url,
      metrics::OmniboxEventProto::PageClassification page_classification,
      std::vector<int> callback_indexes,
      std::vector<std::vector<int>> suggestion_types,
      IndexedStartCallback start_callback,
      IndexedCompletionCallback completion_callback);

  // Stops creating the request. Already created requests aren't affected.
  void StopCreatingEnterpriseSearchAggregatorSuggestionsRequest();

  // Creates and returns a loader to delete personalized suggestions.
  //
  // `deletion_url` must be a valid URL.
  // `completion_callback` will be invoked when the transfer is done.
  std::unique_ptr<network::SimpleURLLoader> StartDeletionRequest(
      const std::string& deletion_url,
      bool is_off_the_record,
      CompletionCallback completion_callback);

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  void SetDelegate(base::WeakPtr<Delegate> delegate);

  // Exposed for testing.
  void set_url_loader_factory_for_testing(
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);

 private:
  // Called when the request has been created, before the transfer has started.
  // Notifies `observers_`.
  void OnRequestCreated(const base::UnguessableToken& request_id,
                        network::ResourceRequest* request);
  // Called when the transfer has started. Notifies `observers_`.
  void OnRequestStarted(
      const base::UnguessableToken& request_id,
      RemoteRequestType request_type,
      metrics::OmniboxEventProto::PageClassification page_classification,
      network::SimpleURLLoader* loader,
      const std::string& request_body);
  // Called when the transfer has started asynchronously, e.g., after obtaining
  // an OAuth token. Notifies `observers_` and calls `start_callback` to
  // transfer the ownership of `loader` to the caller.
  void OnRequestStartedAsync(
      const base::UnguessableToken& request_id,
      RemoteRequestType request_type,
      metrics::OmniboxEventProto::PageClassification page_classification,
      StartCallback start_callback,
      std::unique_ptr<network::SimpleURLLoader> loader,
      const std::string& request_body);
  void OnIndexedRequestStartedAsync(
      const base::UnguessableToken& request_id,
      RemoteRequestType request_type,
      metrics::OmniboxEventProto::PageClassification page_classification,
      IndexedStartCallback start_callback,
      int request_index,
      std::unique_ptr<network::SimpleURLLoader> loader,
      const std::string& request_body);
  // Called when the transfer is done. Notifies `observers_` and calls
  // `completion_callback` passing the response to the caller.
  void OnRequestCompleted(
      const base::UnguessableToken& request_id,
      RemoteRequestType request_type,
      base::ElapsedTimer request_timer,
      metrics::OmniboxEventProto::PageClassification page_classification,
      CompletionCallback completion_callback,
      const network::SimpleURLLoader* source,
      std::optional<std::string> response_body);

  void OnIndexedRequestCompleted(
      const base::UnguessableToken& request_id,
      RemoteRequestType request_type,
      metrics::OmniboxEventProto::PageClassification page_classification,
      base::TimeTicks start_time,
      IndexedCompletionCallback completion_callback,
      const network::SimpleURLLoader* source,
      int request_index,
      std::optional<std::string> response_body);

  // May be nullptr in OTR profiles. Otherwise guaranteed to outlive this due to
  // the factories' dependency.
  raw_ptr<DocumentSuggestionsService> document_suggestions_service_;
  // May be nullptr in OTR profiles. Otherwise guaranteed to outlive this due to
  // the factories' dependency.
  raw_ptr<EnterpriseSearchAggregatorSuggestionsService>
      enterprise_search_aggregator_suggestions_service_;
  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
  // Time request sent for each RemoteRequestType. Used for histogram logging.
  std::map<RemoteRequestType, base::TimeTicks> time_request_sent_;
  // Observers being notified of request start and completion events.
  base::ObserverList<Observer> observers_;
  // Delegate to which invocation of completion callback is delegated.
  base::WeakPtr<Delegate> delegate_;
  // Used to bind `OnURLLoadComplete` to the network loader's callback as the
  // loader is no longer owned by `this` once returned.
  base::WeakPtrFactory<RemoteSuggestionsService> weak_ptr_factory_{this};
};

#endif  // COMPONENTS_OMNIBOX_BROWSER_REMOTE_SUGGESTIONS_SERVICE_H_
