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

#include "components/feed/core/v2/image_fetcher.h"

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

#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "components/feed/core/v2/metrics_reporter.h"
#include "components/feed/core/v2/public/types.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"

namespace feed {

ImageFetcher::ImageFetcher(
    scoped_refptr<::network::SharedURLLoaderFactory> url_loader_factory)
    : url_loader_factory_(url_loader_factory) {}

ImageFetcher::~ImageFetcher() = default;

ImageFetchId ImageFetcher::Fetch(const GURL& url, ImageCallback callback) {
  ImageFetchId id = id_generator_.GenerateNextId();
  TRACE_EVENT_BEGIN("android.ui.jank", "FeedImage",
                    perfetto::Track(GetTrackId(id)), "url", url);
  net::NetworkTrafficAnnotationTag traffic_annotation =
      net::DefineNetworkTrafficAnnotation("interest_feedv2_image_send", R"(
        semantics {
          sender: "Feed Library"
          description: "Images for articles in the feed."
          trigger: "Triggered when viewing the feed on the NTP."
          data: "Request for an image containing an ID for the image and "
          "device specs (e.g. screen size) for resizing images."
          destination: GOOGLE_OWNED_SERVICE
        }
        policy {
          cookies_allowed: NO
          setting: "This can be disabled from the New Tab Page by collapsing "
          "the articles section."
          chrome_policy {
            NTPContentSuggestionsEnabled {
              policy_options {mode: MANDATORY}
              NTPContentSuggestionsEnabled: false
            }
          }
        })");
  auto resource_request = std::make_unique<::network::ResourceRequest>();
  resource_request->url = url;
  resource_request->method = net::HttpRequestHeaders::kGetMethod;
  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;

  auto simple_loader = network::SimpleURLLoader::Create(
      std::move(resource_request), traffic_annotation);
  auto* const simple_loader_ptr = simple_loader.get();

  bool inserted =
      pending_requests_
          .try_emplace(id, std::move(simple_loader), std::move(callback))
          .second;
  DCHECK(inserted);

  simple_loader_ptr->DownloadToString(
      url_loader_factory_.get(),
      base::BindOnce(&ImageFetcher::OnFetchComplete, weak_factory_.GetWeakPtr(),
                     id, url),
      network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
  return id;
}

void ImageFetcher::OnFetchComplete(ImageFetchId id,
                                   const GURL& url,
                                   std::optional<std::string> response_data) {
  TRACE_EVENT_END("android.ui.jank", perfetto::Track(GetTrackId(id)), "bytes",
                  response_data ? response_data->size() : 0);
  std::optional<PendingRequest> request = RemovePending(id);
  if (!request)
    return;

  NetworkResponse response;
  if (request->loader->ResponseInfo() &&
      request->loader->ResponseInfo()->headers) {
    response.status_code =
        request->loader->ResponseInfo()->headers->response_code();
  } else {
    response.status_code = request->loader->NetError();
  }
  MetricsReporter::OnImageFetched(url, response.status_code);

  if (response_data)
    response.response_bytes = std::move(response_data).value();
  std::move(request->callback).Run(std::move(response));
}

void ImageFetcher::Cancel(ImageFetchId id) {
  std::optional<PendingRequest> request = RemovePending(id);
  if (!request)
    return;

  // Cancel the fetch before running the callback.
  request->loader.reset();
  std::move(request->callback)
      .Run({/*response_bytes=*/std::string(), net::Error::ERR_ABORTED});
}

std::optional<ImageFetcher::PendingRequest> ImageFetcher::RemovePending(
    ImageFetchId id) {
  auto iterator = pending_requests_.find(id);
  if (iterator == pending_requests_.end())
    return std::nullopt;

  auto request = std::make_optional(std::move(iterator->second));
  pending_requests_.erase(iterator);
  return request;
}

uint64_t ImageFetcher::GetTrackId(ImageFetchId id) const {
  return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this)) +
         id.GetUnsafeValue();
}

ImageFetcher::PendingRequest::PendingRequest(
    std::unique_ptr<network::SimpleURLLoader> loader,
    ImageCallback callback)
    : loader(std::move(loader)), callback(std::move(callback)) {}
ImageFetcher::PendingRequest::PendingRequest(
    ImageFetcher::PendingRequest&& other) = default;
ImageFetcher::PendingRequest& ImageFetcher::PendingRequest::operator=(
    ImageFetcher::PendingRequest&& other) = default;
ImageFetcher::PendingRequest::~PendingRequest() = default;

}  // namespace feed
