// Copyright 2019 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/enterprise/browser/reporting/report_request_queue_generator.h"

#include <utility>
#include <vector>

#include "base/barrier_callback.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_functions.h"
#include "components/enterprise/browser/reporting/report_type.h"

namespace enterprise_reporting {

using IndexedProfileReport =
    std::pair<int,
              std::unique_ptr<enterprise_management::ChromeUserProfileInfo>>;

namespace {

const size_t kMaximumReportSize =
    5000000;  // The report size limitation is 5mb.

constexpr char kRequestCountMetricsName[] =
    "Enterprise.CloudReportingRequestCount";
constexpr char kDroppedReportSizeMetricsName[] =
    "Enterprise.CloudReporting.DroppedReportSize";
constexpr char kBasicRequestSizeMetricsName[] =
    "Enterprise.CloudReportingBasicRequestSize";

// Because server only stores 20 profiles for each report and when report is
// separated into requests, there is at least one profile per request. It means
// server will truncate the report when there are more than 20 requests. Actions
// are needed if there are many reports exceed this limitation.
const int kRequestCountMetricMaxValue = 21;

IndexedProfileReport ToIndexedReportPair(
    int index,
    std::unique_ptr<enterprise_management::ChromeUserProfileInfo>
        profile_report) {
  return std::pair(index, std::move(profile_report));
}

}  // namespace

ReportRequestQueueGenerator::ReportRequestQueueGenerator(
    ReportingDelegateFactory* delegate_factory)
    : maximum_report_size_(kMaximumReportSize),
      profile_report_generator_(delegate_factory) {
#if BUILDFLAG(IS_CHROMEOS)
  // For Chrome OS, policy information needn't be uploaded to DM server.
  profile_report_generator_.set_policies_enabled(false);
#endif
}

ReportRequestQueueGenerator::~ReportRequestQueueGenerator() = default;

size_t ReportRequestQueueGenerator::GetMaximumReportSizeForTesting() const {
  return maximum_report_size_;
}

void ReportRequestQueueGenerator::SetMaximumReportSizeForTesting(
    size_t maximum_report_size) {
  maximum_report_size_ = maximum_report_size;
}

void ReportRequestQueueGenerator::Generate(
    std::unique_ptr<ReportRequest> basic_request,
    base::OnceCallback<void(
        base::expected<ReportRequestQueue, ReportGenerationError>)> callback) {
  if (!basic_request) {
    OnAllProfileReportsGenerated(std::move(basic_request), std::move(callback),
                                 std::vector<IndexedProfileReport>());
    return;
  }

  size_t basic_request_size =
      basic_request->GetDeviceReportRequest().ByteSizeLong();
  base::UmaHistogramMemoryKB(kBasicRequestSizeMetricsName,
                             basic_request_size / 1024);

  if (basic_request_size > maximum_report_size_) {
    OnAllProfileReportsGenerated(std::move(basic_request), std::move(callback),
                                 std::vector<IndexedProfileReport>());
    return;
  }

  int profile_infos_size = basic_request->GetDeviceReportRequest()
                               .browser_report()
                               .chrome_user_profile_infos_size();
  const auto* basic_request_ptr = basic_request.get();
  auto barrier_callback = base::BarrierCallback<IndexedProfileReport>(
      profile_infos_size,
      base::BindOnce(&ReportRequestQueueGenerator::OnAllProfileReportsGenerated,
                     weak_factory_.GetWeakPtr(), std::move(basic_request),
                     std::move(callback)));

  for (int index = 0; index < profile_infos_size; index++) {
    GenerateProfileReportWithIndex(index, basic_request_ptr, barrier_callback);
  }
}

void ReportRequestQueueGenerator::GenerateProfileReportWithIndex(
    int profile_index,
    const ReportRequest* basic_request,
    base::OnceCallback<void(IndexedProfileReport)> callback) {
  DCHECK_LT(profile_index, basic_request->GetDeviceReportRequest()
                               .browser_report()
                               .chrome_user_profile_infos_size());

  auto basic_profile = basic_request->GetDeviceReportRequest()
                           .browser_report()
                           .chrome_user_profile_infos(profile_index);
  profile_report_generator_.MaybeGenerate(
      base::FilePath::FromUTF8Unsafe(basic_profile.id()), ReportType::kBrowser,
      SecuritySignalsMode::kNoSignals,
      base::BindOnce(ToIndexedReportPair, profile_index)
          .Then(std::move(callback)));
}

void ReportRequestQueueGenerator::OnAllProfileReportsGenerated(
    std::unique_ptr<ReportRequest> basic_request,
    base::OnceCallback<void(
        base::expected<ReportRequestQueue, ReportGenerationError>)> callback,
    std::vector<IndexedProfileReport> indexed_reports) {
  ReportRequestQueue requests;

  size_t basic_request_size =
      basic_request->GetDeviceReportRequest().ByteSizeLong();
  if (basic_request_size <= maximum_report_size_) {
    requests.push(basic_request->Clone());
  }

  for (auto& indexed_report : indexed_reports) {
    auto profile_index = std::get<int>(indexed_report);
    auto profile_report = std::move(
        std::get<std::unique_ptr<enterprise_management::ChromeUserProfileInfo>>(
            indexed_report));

    // Skip if Profile is not loaded and there is no full report.
    if (!profile_report) {
      continue;
    }

    auto basic_profile = basic_request->GetDeviceReportRequest()
                             .browser_report()
                             .chrome_user_profile_infos(profile_index);
    // Use size diff to calculate estimated request size after full profile
    // report is added. There are still few bytes difference but close enough.
    size_t profile_report_incremental_size =
        profile_report->ByteSizeLong() - basic_profile.ByteSizeLong();
    size_t current_request_size =
        requests.back()->GetDeviceReportRequest().ByteSizeLong();

    if (current_request_size + profile_report_incremental_size <=
        maximum_report_size_) {
      // The new full Profile report can be appended into the current request.
      requests.back()
          ->GetDeviceReportRequest()
          .mutable_browser_report()
          ->mutable_chrome_user_profile_infos(profile_index)
          ->Swap(profile_report.get());
    } else if (basic_request_size + profile_report_incremental_size <=
               maximum_report_size_) {
      // The new full Profile report is too big to be appended into the current
      // request, move it to the next request if possible.
      requests.push(basic_request->Clone());
      requests.back()
          ->GetDeviceReportRequest()
          .mutable_browser_report()
          ->mutable_chrome_user_profile_infos(profile_index)
          ->Swap(profile_report.get());
    } else {
      base::UmaHistogramMemoryKB(
          kDroppedReportSizeMetricsName,
          (basic_request_size + profile_report_incremental_size) / 1024);
    }
  }

  base::UmaHistogramExactLinear(kRequestCountMetricsName, requests.size(),
                                kRequestCountMetricMaxValue);

  std::move(callback).Run(std::move(requests));
}

}  // namespace enterprise_reporting
