// 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.

#include "net/reporting/reporting_test_util.h"

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

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/test/simple_test_clock.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/timer/mock_timer.h"
#include "net/base/isolation_info.h"
#include "net/base/network_anonymization_key.h"
#include "net/reporting/reporting_cache.h"
#include "net/reporting/reporting_context.h"
#include "net/reporting/reporting_delegate.h"
#include "net/reporting/reporting_delivery_agent.h"
#include "net/reporting/reporting_endpoint.h"
#include "net/reporting/reporting_garbage_collector.h"
#include "net/reporting/reporting_policy.h"
#include "net/reporting/reporting_uploader.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace net {

namespace {

class PendingUploadImpl : public TestReportingUploader::PendingUpload {
 public:
  PendingUploadImpl(const url::Origin& report_origin,
                    const GURL& url,
                    const IsolationInfo& isolation_info,
                    const std::string& json,
                    ReportingUploader::UploadCallback callback,
                    base::OnceCallback<void(PendingUpload*)> complete_callback)
      : report_origin_(report_origin),
        url_(url),
        isolation_info_(isolation_info),
        json_(json),
        callback_(std::move(callback)),
        complete_callback_(std::move(complete_callback)) {}

  ~PendingUploadImpl() override = default;

  // PendingUpload implementation:
  const url::Origin& report_origin() const override { return report_origin_; }
  const GURL& url() const override { return url_; }
  const std::string& json() const override { return json_; }
  absl::optional<base::Value> GetValue() const override {
    return base::JSONReader::Read(json_);
  }

  void Complete(ReportingUploader::Outcome outcome) override {
    std::move(callback_).Run(outcome);
    // Deletes |this|.
    std::move(complete_callback_).Run(this);
  }

 private:
  url::Origin report_origin_;
  GURL url_;
  IsolationInfo isolation_info_;
  std::string json_;
  ReportingUploader::UploadCallback callback_;
  base::OnceCallback<void(PendingUpload*)> complete_callback_;
};

void ErasePendingUpload(
    std::vector<std::unique_ptr<TestReportingUploader::PendingUpload>>* uploads,
    TestReportingUploader::PendingUpload* upload) {
  for (auto it = uploads->begin(); it != uploads->end(); ++it) {
    if (it->get() == upload) {
      uploads->erase(it);
      return;
    }
  }
  NOTREACHED();
}

}  // namespace

RandIntCallback TestReportingRandIntCallback() {
  return base::BindRepeating(
      [](int* rand_counter, int min, int max) {
        DCHECK_LE(min, max);
        return min + ((*rand_counter)++ % (max - min + 1));
      },
      base::Owned(std::make_unique<int>(0)));
}

TestReportingUploader::PendingUpload::~PendingUpload() = default;
TestReportingUploader::PendingUpload::PendingUpload() = default;

TestReportingUploader::TestReportingUploader() = default;
TestReportingUploader::~TestReportingUploader() = default;

void TestReportingUploader::StartUpload(const url::Origin& report_origin,
                                        const GURL& url,
                                        const IsolationInfo& isolation_info,
                                        const std::string& json,
                                        int max_depth,
                                        bool eligible_for_credentials,
                                        UploadCallback callback) {
  pending_uploads_.push_back(std::make_unique<PendingUploadImpl>(
      report_origin, url, isolation_info, json, std::move(callback),
      base::BindOnce(&ErasePendingUpload, &pending_uploads_)));
}

void TestReportingUploader::OnShutdown() {
  pending_uploads_.clear();
}

int TestReportingUploader::GetPendingUploadCountForTesting() const {
  return pending_uploads_.size();
}

TestReportingDelegate::TestReportingDelegate() = default;

TestReportingDelegate::~TestReportingDelegate() = default;

bool TestReportingDelegate::CanQueueReport(const url::Origin& origin) const {
  return true;
}

void TestReportingDelegate::CanSendReports(
    std::set<url::Origin> origins,
    base::OnceCallback<void(std::set<url::Origin>)> result_callback) const {
  if (pause_permissions_check_) {
    saved_origins_ = std::move(origins);
    permissions_check_callback_ = std::move(result_callback);
    return;
  }

  if (disallow_report_uploads_)
    origins.clear();
  std::move(result_callback).Run(std::move(origins));
}

bool TestReportingDelegate::PermissionsCheckPaused() const {
  return !permissions_check_callback_.is_null();
}

void TestReportingDelegate::ResumePermissionsCheck() {
  if (disallow_report_uploads_)
    saved_origins_.clear();
  std::move(permissions_check_callback_).Run(std::move(saved_origins_));
}

bool TestReportingDelegate::CanSetClient(const url::Origin& origin,
                                         const GURL& endpoint) const {
  return true;
}

bool TestReportingDelegate::CanUseClient(const url::Origin& origin,
                                         const GURL& endpoint) const {
  return true;
}

TestReportingContext::TestReportingContext(
    base::Clock* clock,
    const base::TickClock* tick_clock,
    const ReportingPolicy& policy,
    ReportingCache::PersistentReportingStore* store)
    : ReportingContext(policy,
                       clock,
                       tick_clock,
                       TestReportingRandIntCallback(),
                       std::make_unique<TestReportingUploader>(),
                       std::make_unique<TestReportingDelegate>(),
                       store) {
  auto delivery_timer = std::make_unique<base::MockOneShotTimer>();
  delivery_timer_ = delivery_timer.get();
  auto garbage_collection_timer = std::make_unique<base::MockOneShotTimer>();
  garbage_collection_timer_ = garbage_collection_timer.get();
  garbage_collector()->SetTimerForTesting(std::move(garbage_collection_timer));
  delivery_agent()->SetTimerForTesting(std::move(delivery_timer));
}

TestReportingContext::~TestReportingContext() {
  delivery_timer_ = nullptr;
  garbage_collection_timer_ = nullptr;
}

ReportingTestBase::ReportingTestBase() {
  // For tests, disable jitter.
  ReportingPolicy policy;
  policy.endpoint_backoff_policy.jitter_factor = 0.0;

  CreateContext(policy, base::Time::Now(), base::TimeTicks::Now());
}

ReportingTestBase::~ReportingTestBase() = default;

void ReportingTestBase::UsePolicy(const ReportingPolicy& new_policy) {
  CreateContext(new_policy, clock()->Now(), tick_clock()->NowTicks());
}

void ReportingTestBase::UseStore(
    ReportingCache::PersistentReportingStore* store) {
  store_ = store;
  CreateContext(policy(), clock()->Now(), tick_clock()->NowTicks());
}

const ReportingEndpoint ReportingTestBase::FindEndpointInCache(
    const ReportingEndpointGroupKey& group_key,
    const GURL& url) {
  return cache()->GetEndpointForTesting(group_key, url);
}

bool ReportingTestBase::SetEndpointInCache(
    const ReportingEndpointGroupKey& group_key,
    const GURL& url,
    base::Time expires,
    OriginSubdomains include_subdomains,
    int priority,
    int weight) {
  cache()->SetEndpointForTesting(group_key, url, include_subdomains, expires,
                                 priority, weight);
  const ReportingEndpoint endpoint = FindEndpointInCache(group_key, url);
  return endpoint.is_valid();
}

void ReportingTestBase::SetV1EndpointInCache(
    const ReportingEndpointGroupKey& group_key,
    const base::UnguessableToken& reporting_source,
    const IsolationInfo& isolation_info,
    const GURL& url) {
  cache()->SetV1EndpointForTesting(group_key, reporting_source, isolation_info,
                                   url);
}

bool ReportingTestBase::EndpointExistsInCache(
    const ReportingEndpointGroupKey& group_key,
    const GURL& url) {
  ReportingEndpoint endpoint = cache()->GetEndpointForTesting(group_key, url);
  return endpoint.is_valid();
}

ReportingEndpoint::Statistics ReportingTestBase::GetEndpointStatistics(
    const ReportingEndpointGroupKey& group_key,
    const GURL& url) {
  ReportingEndpoint endpoint;
  if (group_key.IsDocumentEndpoint()) {
    endpoint = cache()->GetV1EndpointForTesting(
        group_key.reporting_source.value(), group_key.group_name);
  } else {
    endpoint = cache()->GetEndpointForTesting(group_key, url);
  }
  if (endpoint)
    return endpoint.stats;
  return ReportingEndpoint::Statistics();
}

bool ReportingTestBase::EndpointGroupExistsInCache(
    const ReportingEndpointGroupKey& group_key,
    OriginSubdomains include_subdomains,
    base::Time expires) {
  return cache()->EndpointGroupExistsForTesting(group_key, include_subdomains,
                                                expires);
}

bool ReportingTestBase::ClientExistsInCacheForOrigin(
    const url::Origin& origin) {
  std::set<url::Origin> all_origins = cache()->GetAllOrigins();
  return all_origins.find(origin) != all_origins.end();
}

GURL ReportingTestBase::MakeURL(size_t index) {
  return GURL(base::StringPrintf("https://example%zd.test", index));
}

void ReportingTestBase::SimulateRestart(base::TimeDelta delta,
                                        base::TimeDelta delta_ticks) {
  CreateContext(policy(), clock()->Now() + delta,
                tick_clock()->NowTicks() + delta_ticks);
}

void ReportingTestBase::CreateContext(const ReportingPolicy& policy,
                                      base::Time now,
                                      base::TimeTicks now_ticks) {
  context_ = std::make_unique<TestReportingContext>(&clock_, &tick_clock_,
                                                    policy, store_);
  clock()->SetNow(now);
  tick_clock()->SetNowTicks(now_ticks);
}

base::TimeTicks ReportingTestBase::yesterday() {
  return tick_clock()->NowTicks() - base::Days(1);
}

base::TimeTicks ReportingTestBase::now() {
  return tick_clock()->NowTicks();
}

base::TimeTicks ReportingTestBase::tomorrow() {
  return tick_clock()->NowTicks() + base::Days(1);
}

TestReportingService::Report::Report() = default;

TestReportingService::Report::Report(Report&& other) = default;

TestReportingService::Report::Report(
    const GURL& url,
    const NetworkAnonymizationKey& network_anonymization_key,
    const std::string& user_agent,
    const std::string& group,
    const std::string& type,
    std::unique_ptr<const base::Value> body,
    int depth)
    : url(url),
      network_anonymization_key(network_anonymization_key),
      user_agent(user_agent),
      group(group),
      type(type),
      body(std::move(body)),
      depth(depth) {}

TestReportingService::Report::~Report() = default;

TestReportingService::TestReportingService() = default;

TestReportingService::~TestReportingService() = default;

void TestReportingService::QueueReport(
    const GURL& url,
    const absl::optional<base::UnguessableToken>& reporting_source,
    const NetworkAnonymizationKey& network_anonymization_key,
    const std::string& user_agent,
    const std::string& group,
    const std::string& type,
    base::Value::Dict body,
    int depth) {
  reports_.emplace_back(
      Report(url, network_anonymization_key, user_agent, group, type,
             std::make_unique<base::Value>(std::move(body)), depth));
}

void TestReportingService::ProcessReportToHeader(
    const url::Origin& origin,
    const NetworkAnonymizationKey& network_anonymization_key,
    const std::string& header_value) {
  NOTREACHED();
}

void TestReportingService::RemoveBrowsingData(
    uint64_t data_type_mask,
    const base::RepeatingCallback<bool(const url::Origin&)>& origin_filter) {
  NOTREACHED();
}

void TestReportingService::RemoveAllBrowsingData(uint64_t data_type_mask) {
  NOTREACHED();
}

void TestReportingService::OnShutdown() {}

const ReportingPolicy& TestReportingService::GetPolicy() const {
  NOTREACHED();
  return dummy_policy_;
}

ReportingContext* TestReportingService::GetContextForTesting() const {
  NOTREACHED();
  return nullptr;
}

std::vector<const ReportingReport*> TestReportingService::GetReports() const {
  NOTREACHED();
  return std::vector<const ReportingReport*>();
}

base::flat_map<url::Origin, std::vector<ReportingEndpoint>>
TestReportingService::GetV1ReportingEndpointsByOrigin() const {
  NOTREACHED();
  return base::flat_map<url::Origin, std::vector<ReportingEndpoint>>();
}

void TestReportingService::AddReportingCacheObserver(
    ReportingCacheObserver* observer) {
  NOTREACHED();
}

void TestReportingService::RemoveReportingCacheObserver(
    ReportingCacheObserver* observer) {
  NOTREACHED();
}

}  // namespace net
