// 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 "components/metrics/metrics_log_store.h"

#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_service_client.h"
#include "components/metrics/unsent_log_store_metrics_impl.h"
#include "components/prefs/pref_registry_simple.h"

namespace metrics {

// static
void MetricsLogStore::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterListPref(prefs::kMetricsInitialLogs);
  registry->RegisterListPref(prefs::kMetricsOngoingLogs);
  registry->RegisterDictionaryPref(prefs::kMetricsInitialLogsMetadata);
  registry->RegisterDictionaryPref(prefs::kMetricsOngoingLogsMetadata);
}

MetricsLogStore::MetricsLogStore(PrefService* local_state,
                                 StorageLimits storage_limits,
                                 const std::string& signing_key,
                                 MetricsLogsEventManager* logs_event_manager)
    : unsent_logs_loaded_(false),
      logs_event_manager_(logs_event_manager),
      initial_log_queue_(
          std::make_unique<UnsentLogStoreMetricsImpl>(),
          local_state,
          prefs::kMetricsInitialLogs,
          prefs::kMetricsInitialLogsMetadata,
          UnsentLogStore::UnsentLogStoreLimits{
              storage_limits.initial_log_queue_limits.min_log_count,
              storage_limits.initial_log_queue_limits.min_queue_size_bytes,
              // Each individual initial log can be any size.
              /*max_log_size_bytes=*/0},
          signing_key,
          logs_event_manager),
      ongoing_log_queue_(std::make_unique<UnsentLogStoreMetricsImpl>(),
                         local_state,
                         prefs::kMetricsOngoingLogs,
                         prefs::kMetricsOngoingLogsMetadata,
                         storage_limits.ongoing_log_queue_limits,
                         signing_key,
                         logs_event_manager) {}

MetricsLogStore::~MetricsLogStore() {}

void MetricsLogStore::LoadPersistedUnsentLogs() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  {
    MetricsLogsEventManager::ScopedNotifyLogType scoped_log_type(
        logs_event_manager_, MetricsLog::LogType::INITIAL_STABILITY_LOG);
    initial_log_queue_.LoadPersistedUnsentLogs();
  }

  {
    // Note that we assume that logs loaded from the persistent storage for
    // |ongoing_log_queue_| are of type "ongoing". They could, however, be
    // independent logs, but we unfortunately cannot determine this since we
    // don't persist the type of log.
    MetricsLogsEventManager::ScopedNotifyLogType scoped_log_type(
        logs_event_manager_, MetricsLog::LogType::ONGOING_LOG);
    ongoing_log_queue_.LoadPersistedUnsentLogs();
  }

  unsent_logs_loaded_ = true;
}

void MetricsLogStore::StoreLog(const std::string& log_data,
                               MetricsLog::LogType log_type,
                               const LogMetadata& log_metadata,
                               MetricsLogsEventManager::CreateReason reason) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  MetricsLogsEventManager::ScopedNotifyLogType scoped_log_type(
      logs_event_manager_, log_type);
  GetLogStoreForLogType(log_type)->StoreLog(log_data, log_metadata, reason);
}

void MetricsLogStore::StoreLogInfo(
    std::unique_ptr<UnsentLogStore::LogInfo> log_info,
    size_t uncompressed_log_size,
    MetricsLog::LogType log_type,
    MetricsLogsEventManager::CreateReason reason) {
  MetricsLogsEventManager::ScopedNotifyLogType scoped_log_type(
      logs_event_manager_, log_type);
  GetLogStoreForLogType(log_type)->StoreLogInfo(std::move(log_info),
                                                uncompressed_log_size, reason);
}

void MetricsLogStore::Purge() {
  initial_log_queue_.Purge();
  ongoing_log_queue_.Purge();
  if (has_alternate_ongoing_log_store()) {
    alternate_ongoing_log_queue_->Purge();
  }
}

const std::string& MetricsLogStore::GetSigningKeyForLogType(
    MetricsLog::LogType log_type) {
  return GetLogStoreForLogType(log_type)->signing_key();
}

void MetricsLogStore::SetAlternateOngoingLogStore(
    std::unique_ptr<UnsentLogStore> log_store) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  DCHECK(!has_alternate_ongoing_log_store());
  DCHECK(unsent_logs_loaded_);
  alternate_ongoing_log_queue_ = std::move(log_store);
  alternate_ongoing_log_queue_->SetLogsEventManager(logs_event_manager_);

  // Note that we assume that logs loaded from the persistent storage for
  // |alternate_ongoing_log_queue_| are of type "ongoing". They could, however,
  // be independent logs, but we unfortunately cannot determine this since we
  // don't persist the type of log.
  MetricsLogsEventManager::ScopedNotifyLogType scoped_log_type(
      logs_event_manager_, MetricsLog::LogType::ONGOING_LOG);
  alternate_ongoing_log_queue_->LoadPersistedUnsentLogs();
}

void MetricsLogStore::UnsetAlternateOngoingLogStore() {
  DCHECK(has_alternate_ongoing_log_store());
  alternate_ongoing_log_queue_->TrimAndPersistUnsentLogs(
      /*overwrite_in_memory_store=*/true);
  alternate_ongoing_log_queue_.reset();
}

bool MetricsLogStore::has_unsent_logs() const {
  return initial_log_queue_.has_unsent_logs() ||
         ongoing_log_queue_.has_unsent_logs() ||
         alternate_ongoing_log_store_has_unsent_logs();
}

bool MetricsLogStore::has_staged_log() const {
  return initial_log_queue_.has_staged_log() ||
         ongoing_log_queue_.has_staged_log() ||
         alternate_ongoing_log_store_has_staged_log();
}

const std::string& MetricsLogStore::staged_log() const {
  return get_staged_log_queue()->staged_log();
}

const std::string& MetricsLogStore::staged_log_hash() const {
  return get_staged_log_queue()->staged_log_hash();
}

const std::string& MetricsLogStore::staged_log_signature() const {
  return get_staged_log_queue()->staged_log_signature();
}

std::optional<uint64_t> MetricsLogStore::staged_log_user_id() const {
  return get_staged_log_queue()->staged_log_user_id();
}

const LogMetadata MetricsLogStore::staged_log_metadata() const {
  return get_staged_log_queue()->staged_log_metadata();
}

bool MetricsLogStore::has_alternate_ongoing_log_store() const {
  return alternate_ongoing_log_queue_ != nullptr;
}

const UnsentLogStore* MetricsLogStore::get_staged_log_queue() const {
  DCHECK(has_staged_log());

  // This is the order in which logs should be staged. Should be consistent with
  // StageNextLog.
  if (initial_log_queue_.has_staged_log())
    return &initial_log_queue_;
  else if (alternate_ongoing_log_store_has_staged_log())
    return alternate_ongoing_log_queue_.get();
  return &ongoing_log_queue_;
}

bool MetricsLogStore::alternate_ongoing_log_store_has_unsent_logs() const {
  return has_alternate_ongoing_log_store() &&
         alternate_ongoing_log_queue_->has_unsent_logs();
}

bool MetricsLogStore::alternate_ongoing_log_store_has_staged_log() const {
  return has_alternate_ongoing_log_store() &&
         alternate_ongoing_log_queue_->has_staged_log();
}

UnsentLogStore* MetricsLogStore::GetLogStoreForLogType(
    MetricsLog::LogType log_type) {
  switch (log_type) {
    case MetricsLog::INITIAL_STABILITY_LOG:
      return &initial_log_queue_;
    case MetricsLog::ONGOING_LOG:
    case MetricsLog::INDEPENDENT_LOG:
      return has_alternate_ongoing_log_store()
                 ? alternate_ongoing_log_queue_.get()
                 : &ongoing_log_queue_;
  }
}

void MetricsLogStore::StageNextLog() {
  DCHECK(!has_staged_log());
  if (initial_log_queue_.has_unsent_logs())
    initial_log_queue_.StageNextLog();
  else if (alternate_ongoing_log_store_has_unsent_logs())
    alternate_ongoing_log_queue_->StageNextLog();
  else if (ongoing_log_queue_.has_unsent_logs())
    ongoing_log_queue_.StageNextLog();
}

void MetricsLogStore::DiscardStagedLog(base::StringPiece reason) {
  DCHECK(has_staged_log());
  if (initial_log_queue_.has_staged_log())
    initial_log_queue_.DiscardStagedLog(reason);
  else if (alternate_ongoing_log_store_has_staged_log())
    alternate_ongoing_log_queue_->DiscardStagedLog(reason);
  else if (ongoing_log_queue_.has_staged_log())
    ongoing_log_queue_.DiscardStagedLog(reason);

  DCHECK(!has_staged_log());
}

void MetricsLogStore::MarkStagedLogAsSent() {
  DCHECK(has_staged_log());
  if (initial_log_queue_.has_staged_log())
    initial_log_queue_.MarkStagedLogAsSent();
  else if (alternate_ongoing_log_store_has_staged_log())
    alternate_ongoing_log_queue_->MarkStagedLogAsSent();
  else if (ongoing_log_queue_.has_staged_log())
    ongoing_log_queue_.MarkStagedLogAsSent();
}

void MetricsLogStore::TrimAndPersistUnsentLogs(bool overwrite_in_memory_store) {
  DCHECK(unsent_logs_loaded_);
  if (!unsent_logs_loaded_)
    return;

  initial_log_queue_.TrimAndPersistUnsentLogs(overwrite_in_memory_store);
  ongoing_log_queue_.TrimAndPersistUnsentLogs(overwrite_in_memory_store);
  if (has_alternate_ongoing_log_store())
    alternate_ongoing_log_queue_->TrimAndPersistUnsentLogs(
        overwrite_in_memory_store);
}

}  // namespace metrics
