// Copyright 2022 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/optimization_guide/core/optimization_guide_logger.h"

#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/observer_list.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "components/optimization_guide/core/optimization_guide_switches.h"


OptimizationGuideLogger::LogMessageBuilder::LogMessageBuilder(
    optimization_guide_common::mojom::LogSource log_source,
    const std::string& source_file,
    int source_line,
    OptimizationGuideLogger* optimization_guide_logger)
    : log_source_(log_source),
      source_file_(source_file),
      source_line_(source_line),
      optimization_guide_logger_(optimization_guide_logger) {}

OptimizationGuideLogger::LogMessageBuilder::~LogMessageBuilder() {
  if (!optimization_guide_logger_) {
    // It is possible for this to not be available in tests, so just return
    // here.
    return;
  }

  std::string message = base::StrCat(messages_);
  optimization_guide_logger_->OnLogMessageAdded(
      base::Time::Now(), log_source_, source_file_, source_line_, message);
  DVLOG(1) << source_file_ << "(" << source_line_ << ") " << message;
}

OptimizationGuideLogger::LogMessageBuilder&
OptimizationGuideLogger::LogMessageBuilder::operator<<(const char* message) {
  messages_.push_back(message);
  return *this;
}

OptimizationGuideLogger::LogMessageBuilder&
OptimizationGuideLogger::LogMessageBuilder::operator<<(
    const std::string& message) {
  messages_.push_back(message);
  return *this;
}

OptimizationGuideLogger::LogMessageBuilder&
OptimizationGuideLogger::LogMessageBuilder::operator<<(
    std::string_view message) {
  messages_.emplace_back(message);
  return *this;
}

OptimizationGuideLogger::LogMessageBuilder&
OptimizationGuideLogger::LogMessageBuilder::operator<<(const GURL& url) {
  messages_.push_back(url.possibly_invalid_spec());
  return *this;
}

OptimizationGuideLogger::LogMessageBuilder&
OptimizationGuideLogger::LogMessageBuilder::operator<<(
    optimization_guide::proto::RequestContext request_context) {
  messages_.push_back(
      optimization_guide::proto::RequestContext_Name(request_context));
  return *this;
}

OptimizationGuideLogger::LogMessageBuilder&
OptimizationGuideLogger::LogMessageBuilder::operator<<(
    optimization_guide::proto::OptimizationTarget optimization_target) {
  messages_.push_back(
      optimization_guide::proto::OptimizationTarget_Name(optimization_target));
  return *this;
}

OptimizationGuideLogger::LogMessage::LogMessage(
    base::Time event_time,
    optimization_guide_common::mojom::LogSource log_source,
    const std::string& source_file,
    int source_line,
    const std::string& message)
    : event_time(event_time),
      log_source(log_source),
      source_file(source_file),
      source_line(source_line),
      message(message) {}

// static
OptimizationGuideLogger* OptimizationGuideLogger::GetInstance() {
  static base::NoDestructor<OptimizationGuideLogger> instance;
  return instance.get();
}

OptimizationGuideLogger::OptimizationGuideLogger()
    : command_line_flag_enabled_(
          optimization_guide::switches::IsDebugLogsEnabled()) {
  if (command_line_flag_enabled_) {
    recent_log_messages_.reserve(kMaxRecentLogMessages);
  }
}

OptimizationGuideLogger::~OptimizationGuideLogger() = default;

void OptimizationGuideLogger::MaybeEmitBufferOverflowWarning(
    OptimizationGuideLogger::Observer* observer) {
  if (recent_log_messages_dropped_count_ == 0) {
    return;
  }
  base::Time event_time =
      recent_log_messages_.empty()
          ? base::Time::Now()
          : recent_log_messages_.front().event_time - base::Microseconds(1);
  observer->OnLogMessageAdded(
      event_time,
      optimization_guide_common::mojom::LogSource::SERVICE_AND_SETTINGS,
      __FILE__, __LINE__,
      base::StringPrintf(
          "⚠️ [WARNING]: %zu earlier startup debug log message(s) were dropped "
          "because the buffer limit (%zu) was exceeded. Consider increasing "
          "kMaxRecentLogMessages in optimization_guide_logger.cc.",
          recent_log_messages_dropped_count_, kMaxRecentLogMessages));
}

void OptimizationGuideLogger::AddObserver(
    OptimizationGuideLogger::Observer* observer) {
  observers_.AddObserver(observer);
  if (command_line_flag_enabled_) {
    MaybeEmitBufferOverflowWarning(observer);
    for (const auto& message : recent_log_messages_) {
      observer->OnLogMessageAdded(message.event_time, message.log_source,
                                  message.source_file, message.source_line,
                                  message.message);
    }
  }
}

void OptimizationGuideLogger::RemoveObserver(
    OptimizationGuideLogger::Observer* observer) {
  observers_.RemoveObserver(observer);
}

void OptimizationGuideLogger::OnLogMessageAdded(
    base::Time event_time,
    optimization_guide_common::mojom::LogSource log_source,
    const std::string& source_file,
    int source_line,
    const std::string& message) {
  if (command_line_flag_enabled_) {
    recent_log_messages_.emplace_back(event_time, log_source, source_file,
                                      source_line, message);
    if (recent_log_messages_.size() > kMaxRecentLogMessages) {
      recent_log_messages_.pop_front();
      if (observers_.empty()) {
        recent_log_messages_dropped_count_++;
      }
    }
  }
  for (Observer& obs : observers_) {
    obs.OnLogMessageAdded(event_time, log_source, source_file, source_line,
                          message);
  }
}

bool OptimizationGuideLogger::ShouldEnableDebugLogs() const {
  return !observers_.empty() || command_line_flag_enabled_;
}
