// 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_INVALIDATION_IMPL_PER_USER_TOPIC_SUBSCRIPTION_REQUEST_H_
#define COMPONENTS_INVALIDATION_IMPL_PER_USER_TOPIC_SUBSCRIPTION_REQUEST_H_

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "components/invalidation/impl/status.h"
#include "components/invalidation/public/invalidation_util.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "url/gurl.h"

namespace invalidation {

// A single request to subscribe to a topic on the per-user-topic service.
class PerUserTopicSubscriptionRequest {
 public:
  // The request result consists of the request status and name of the private
  // topic. The |topic_name| will be empty in the case of error.
  using CompletedCallback =
      base::OnceCallback<void(const Status& status,
                              const std::string& topic_name)>;
  enum class RequestType { kSubscribe, kUnsubscribe };

  // Builds authenticated PerUserTopicSubscriptionRequests.
  class Builder {
   public:
    Builder();
    Builder(const Builder& other) = delete;
    Builder& operator=(const Builder& other) = delete;
    ~Builder();

    // Builds a Request object in order to perform the subscription.
    std::unique_ptr<PerUserTopicSubscriptionRequest> Build() const;

    Builder& SetInstanceIdToken(const std::string& token);
    Builder& SetScope(const std::string& scope);
    Builder& SetAuthenticationHeader(const std::string& auth_header);

    Builder& SetPublicTopicName(const Topic& topic);
    Builder& SetProjectId(const std::string& project_id);

    Builder& SetType(RequestType type);

    Builder& SetTopicIsPublic(bool topic_is_public);

   private:
    net::HttpRequestHeaders BuildHeaders() const;
    std::string BuildBody() const;
    std::unique_ptr<network::SimpleURLLoader> BuildURLFetcher(
        const net::HttpRequestHeaders& headers,
        const std::string& body,
        const GURL& url) const;

    // GCM subscription token obtained from GCM driver (instanceID::getToken()).
    std::string instance_id_token_;
    Topic topic_;
    std::string project_id_;

    std::string scope_;
    std::string auth_header_;
    RequestType type_;
    bool topic_is_public_ = false;
  };

  PerUserTopicSubscriptionRequest(
      const PerUserTopicSubscriptionRequest& other) = delete;
  PerUserTopicSubscriptionRequest& operator=(
      const PerUserTopicSubscriptionRequest& other) = delete;
  ~PerUserTopicSubscriptionRequest();

  // Starts an async request. The callback is invoked when the request succeeds
  // or fails. The callback is not called if the request is destroyed.
  void Start(CompletedCallback callback,
             network::mojom::URLLoaderFactory* loader_factory);

  GURL GetUrlForTesting() const { return url_; }

 private:
  PerUserTopicSubscriptionRequest();

  // The methods below may end up calling RunCompletedCallbackAndMaybeDie(),
  // which potentially lead to destroying |this|. Hence, |this| object must
  // assume that it is dead after invoking any of these methods and must not
  // run any more code.
  void OnURLFetchComplete(std::optional<std::string> response_body);
  void OnURLFetchCompleteInternal(int net_error,
                                  int response_code,
                                  std::optional<std::string> response_body);

  // Invokes |request_completed_callback_| with (|status|, |topic_name|). Per
  // the contract of this class, it is allowed for clients to delete this
  // object as part of the invocation of |request_completed_callback_|. Hence,
  // this object must assume that it is dead after invoking this method and
  // must not run any more code. See crbug.com/1054590 as sample issue for
  // violation of this rule.
  // |status| and |topic_name| are intentionally taken by value to avoid
  // references to members.
  void RunCompletedCallbackAndMaybeDie(Status status, std::string topic_name);
  // The fetcher for subscribing.
  std::unique_ptr<network::SimpleURLLoader> simple_loader_;

  // The callback to notify when URLFetcher finished and results are available.
  // When the request is finished/aborted/destroyed, it's called in the dtor!
  // Note: This callback should only be invoked from
  // RunCompletedCallbackAndMaybeDie(), as invoking it has the potential to
  // destroy this object per this class's contract.
  // TODO(crbug.com/40675891): find a way to avoid this fragile logic.
  CompletedCallback request_completed_callback_;

  // Full URL. Used in tests only.
  GURL url_;
  RequestType type_;
  std::string topic_;

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

}  // namespace invalidation

#endif  // COMPONENTS_INVALIDATION_IMPL_PER_USER_TOPIC_SUBSCRIPTION_REQUEST_H_
