// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "chrome/browser/policy/messaging_layer/upload/upload_provider.h"

#include <list>
#include <memory>
#include <utility>

#include "base/memory/ref_counted.h"
#include "base/task/thread_pool.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/task_environment.h"
#include "chrome/browser/policy/messaging_layer/upload/fake_upload_client.h"
#include "chrome/browser/policy/messaging_layer/upload/upload_client.h"
#include "chrome/browser/policy/messaging_layer/util/reporting_server_connector.h"
#include "chrome/browser/policy/messaging_layer/util/reporting_server_connector_test_util.h"
#include "chrome/browser/policy/messaging_layer/util/test_request_payload.h"
#include "chrome/browser/policy/messaging_layer/util/test_response_payload.h"
#include "chrome/browser/policy/messaging_layer/util/upload_declarations.h"
#include "components/policy/core/common/cloud/dm_token.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/reporting/resources/resource_manager.h"
#include "components/reporting/util/status.h"
#include "components/reporting/util/status_macros.h"
#include "components/reporting/util/test_support_callbacks.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::base::test::EqualsProto;
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::SizeIs;
using ::testing::WithArgs;

namespace reporting {
namespace {

// CloudPolicyClient and UploadClient are not usable outside of a managed
// environment, to sidestep this we override the functions that normally build
// and retrieve these clients and provide a MockCloudPolicyClient and a
// FakeUploadClient.
class TestEncryptedReportingUploadProvider
    : public EncryptedReportingUploadProvider {
 public:
  TestEncryptedReportingUploadProvider(
      ReportSuccessfulUploadCallback report_successful_upload_cb,
      EncryptionKeyAttachedCallback encryption_key_attached_cb,
      UpdateConfigInMissiveCallback update_config_in_missive_cb)
      : EncryptedReportingUploadProvider(
            report_successful_upload_cb,
            encryption_key_attached_cb,
            update_config_in_missive_cb,
            /*upload_client_builder_cb=*/
            base::BindRepeating(&FakeUploadClient::Create)) {}
};

class EncryptedReportingUploadProviderTest : public ::testing::Test {
 public:
  MOCK_METHOD(void, ReportSuccessfulUpload, (SequenceInformation, bool), ());
  MOCK_METHOD(void, EncryptionKeyCallback, (SignedEncryptionInfo), ());
  MOCK_METHOD(void,
              UpdateConfigInMissiveCallback,
              (ListOfBlockedDestinations),
              ());

 protected:
  void SetUp() override {
    memory_resource_ =
        base::MakeRefCounted<ResourceManager>(4u * 1024LLu * 1024LLu);  // 4 MiB
    test_env_ = std::make_unique<ReportingServerConnector::TestEnvironment>();
    service_provider_ = std::make_unique<TestEncryptedReportingUploadProvider>(
        base::BindRepeating(
            &EncryptedReportingUploadProviderTest::ReportSuccessfulUpload,
            base::Unretained(this)),
        base::BindRepeating(
            &EncryptedReportingUploadProviderTest::EncryptionKeyCallback,
            base::Unretained(this)),
        base::BindRepeating(&EncryptedReportingUploadProviderTest::
                                UpdateConfigInMissiveCallback,
                            base::Unretained(this)));

    record_.set_encrypted_wrapped_record("TEST_DATA");

    auto* sequence_information = record_.mutable_sequence_information();
    sequence_information->set_sequencing_id(42);
    sequence_information->set_generation_id(1701);
    sequence_information->set_priority(Priority::SLOW_BATCH);
  }

  void TearDown() override {
    test_env_.reset();
    EXPECT_THAT(memory_resource_->GetUsed(), Eq(0uL));
  }

  StatusOr<std::list<int64_t>> CallRequestUploadEncryptedRecord(
      bool need_encryption_key,
      std::vector<EncryptedRecord> records,
      ScopedReservation scoped_reservation) {
    test::TestEvent<StatusOr<std::list<int64_t>>> enqueued_event;
    service_provider_->RequestUploadEncryptedRecords(
        need_encryption_key, std::move(records), std::move(scoped_reservation),
        enqueued_event.cb());
    return enqueued_event.result();
  }

  // Must be initialized before any other class member.
  content::BrowserTaskEnvironment task_environment_;

  std::unique_ptr<ReportingServerConnector::TestEnvironment> test_env_;
  EncryptedRecord record_;

  scoped_refptr<ResourceManager> memory_resource_;

  std::unique_ptr<TestEncryptedReportingUploadProvider> service_provider_;
};

// TODO(b/289375752): Test is flaky across platforms.
TEST_F(EncryptedReportingUploadProviderTest,
       DISABLED_SuccessfullyUploadsRecord) {
  test::TestMultiEvent<SequenceInformation, bool /*force*/> uploaded_event;
  EXPECT_CALL(*this, ReportSuccessfulUpload(_, _))
      .WillOnce([&uploaded_event](SequenceInformation seq_info, bool force) {
        std::move(uploaded_event.cb()).Run(std::move(seq_info), force);
      });

  std::vector<EncryptedRecord> records;
  records.emplace_back(record_);
  ScopedReservation record_reservation(records.back().ByteSizeLong(),
                                       memory_resource_);
  EXPECT_TRUE(record_reservation.reserved());
  const auto enqueued_result = CallRequestUploadEncryptedRecord(
      /*need_encryption_key=*/false, std::move(records),
      std::move(record_reservation));

  EXPECT_OK(enqueued_result) << enqueued_result.error();
  EXPECT_THAT(enqueued_result.value(),
              ElementsAre(record_.sequence_information().sequencing_id()));

  task_environment_.RunUntilIdle();

  ASSERT_THAT(*test_env_->url_loader_factory()->pending_requests(), SizeIs(1));
  base::DictValue request_body = test_env_->request_body(0);
  EXPECT_THAT(request_body, IsDataUploadRequestValid());

  test_env_->SimulateResponseForRequest(0);

  auto uploaded_result = uploaded_event.result();
  EXPECT_THAT(std::get<0>(uploaded_result),
              EqualsProto(record_.sequence_information()));
  EXPECT_FALSE(std::get<1>(uploaded_result));  // !force
}

}  // namespace
}  // namespace reporting
