// 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 "components/reporting/client/report_queue.h"

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

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/proto/synced/record_constants.pb.h"
#include "components/reporting/util/status.h"
#include "components/reporting/util/status_macros.h"
#include "components/reporting/util/statusor.h"

namespace reporting {

namespace {

StatusOr<std::string> ValueToJson(base::DictValue record) {
  std::string json_record;
  if (!base::JSONWriter::Write(record, &json_record)) {
    return base::unexpected(
        Status(error::INVALID_ARGUMENT,
               "Provided record was not convertable to a std::string"));
  }
  return json_record;
}

StatusOr<std::string> ProtoToString(
    std::unique_ptr<const google::protobuf::MessageLite> record) {
  std::string protobuf_record;
  if (!record->SerializeToString(&protobuf_record)) {
    return base::unexpected(
        Status(error::INVALID_ARGUMENT,
               "Unabled to serialize record to string. Most likely due to "
               "unset required fields."));
  }
  return protobuf_record;
}

void EnqueueResponded(ReportQueue::EnqueueCallback callback,
                      Destination destination,
                      Status status) {
  // Log the overall enqueue error status. Gives us insight to how the ERP is
  // performing in general.
  base::UmaHistogramEnumeration(ReportQueue::kEnqueueMetricsName, status.code(),
                                error::Code::MAX_VALUE);

  // Log the type of enqueue error (if any) for this destination. Gives us
  // insights as to what types of errors this destination experiences.
  base::UmaHistogramEnumeration(
      base::StrCat({ReportQueue::kEnqueueMetricsName, ".",
                    Destination_Name(destination)}),
      status.code(), error::Code::MAX_VALUE);

  // Log whether the enqueue succeeded or failed for this destination. Gives us
  // insight as to whether one destination experiences more failures than
  // others. If you find that a destination has more failures than other
  // destinations, it could mean several things:
  //
  // 1. That destination gets much more traffic and is naturally going to
  // experience more errors.
  //
  // 2. The code that enqueues records to that destination may be implemented
  // incorrectly.
  //
  // To dive further, look up the `Browser.ERP.EventEnqueueResult<Destination>`
  // UMA (logged above) to understand what types of errors the destination
  // experiences most often.
  const auto* const enqueue_destination_metrics_name =
      status.ok() ? ReportQueue::kEnqueueSuccessDestinationMetricsName
                  : ReportQueue::kEnqueueFailedDestinationMetricsName;
  base::UmaHistogramExactLinear(enqueue_destination_metrics_name,
                                static_cast<int>(destination),
                                Destination_ARRAYSIZE);
  std::move(callback).Run(std::move(status));
}
}  // namespace

ReportQueue::~ReportQueue() = default;

void ReportQueue::Enqueue(std::string record,
                          Priority priority,
                          ReportQueue::EnqueueCallback callback) const {
  AddProducedRecord(
      base::BindOnce([](std::string record)
                         -> StatusOr<std::string> { return std::move(record); },
                     std::move(record)),
      priority,
      base::BindOnce(&EnqueueResponded, std::move(callback), GetDestination()));
}

void ReportQueue::Enqueue(base::DictValue record,
                          Priority priority,
                          ReportQueue::EnqueueCallback callback) const {
  AddProducedRecord(
      base::BindOnce(&ValueToJson, std::move(record)), priority,
      base::BindOnce(&EnqueueResponded, std::move(callback), GetDestination()));
}

void ReportQueue::Enqueue(
    std::unique_ptr<const google::protobuf::MessageLite> record,
    Priority priority,
    ReportQueue::EnqueueCallback callback) const {
  AddProducedRecord(
      base::BindOnce(&ProtoToString, std::move(record)), priority,
      base::BindOnce(&EnqueueResponded, std::move(callback), GetDestination()));
}

}  // namespace reporting
