// 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 "chrome/browser/media/webrtc/webrtc_event_log_manager_local.h"

#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread.h"

#if BUILDFLAG(IS_WIN)
#define NumberToStringType base::NumberToString16
#else
#define NumberToStringType base::NumberToString
#endif

namespace webrtc_event_logging {

#if BUILDFLAG(IS_ANDROID)
const size_t kDefaultMaxLocalEventLogFileSizeBytes = 10'000'000;
const size_t kMaxNumberLocalWebRtcEventLogFiles = 3;
const size_t kDefaultMaxLocalDataChannelFileSizeBytes = 10'000'000;
const size_t kMaxNumberLocalWebRtcDataChannelLogFiles = 3;
#else
const size_t kDefaultMaxLocalEventLogFileSizeBytes = 60'000'000;
const size_t kMaxNumberLocalWebRtcEventLogFiles = 5;
const size_t kDefaultMaxLocalDataChannelFileSizeBytes = 100'000'000;
const size_t kMaxNumberLocalWebRtcDataChannelLogFiles = 5;
#endif

struct WebRtcLocalEventLogManager::LogFiles {
  std::unique_ptr<LogFileWriter> event_log;
  std::unique_ptr<LogFileWriter> data_channel_log;
};

WebRtcLocalEventLogManager::WebRtcLocalEventLogManager(
    WebRtcLocalEventLogsObserver* observer)
    : observer_(observer), clock_for_testing_(nullptr) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DETACH_FROM_SEQUENCE(io_task_sequence_checker_);
}

WebRtcLocalEventLogManager::~WebRtcLocalEventLogManager() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}

bool WebRtcLocalEventLogManager::OnPeerConnectionAdded(
    const PeerConnectionKey& key) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
  const auto insertion_result = log_files_.try_emplace(key);
  if (!insertion_result.second) {
    return false;  // Attempt to re-add the PeerConnection.
  }

  MaybeStartEventLogFile(insertion_result.first);
  MaybeStartDataChannelLogFile(insertion_result.first);
  return true;
}

bool WebRtcLocalEventLogManager::OnPeerConnectionRemoved(
    const PeerConnectionKey& key) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);

  auto it = log_files_.find(key);
  if (it == log_files_.end()) {
    return false;
  }

  StopEventLogFileIfStarted(it);
  StopDataChannelLogFileIfStarted(it);
  log_files_.erase(it);
  return true;
}

bool WebRtcLocalEventLogManager::EnableEventLogging(
    const base::FilePath& base_path,
    size_t max_file_size_bytes) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);

  if (event_log_state_.has_value()) {
    return false;
  }

  std::optional<size_t> max_size_bytes =
      (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize)
          ? std::optional<size_t>()
          : std::optional<size_t>(max_file_size_bytes);

  event_log_state_.emplace(
      base_path, max_size_bytes,
      /*max_logs_active=*/kMaxNumberLocalWebRtcEventLogFiles,
      /*max_logs_created=*/std::nullopt);

  for (auto it = log_files_.begin(); it != log_files_.end(); ++it) {
    MaybeStartEventLogFile(it);
  }

  return true;
}

bool WebRtcLocalEventLogManager::DisableEventLogging() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);

  if (!event_log_state_.has_value()) {
    return false;
  }

  for (auto it = log_files_.begin(); it != log_files_.end(); ++it) {
    StopEventLogFileIfStarted(it);
  }
  CHECK_EQ(event_log_state_->logs_active, 0U);

  event_log_state_.reset();
  return true;
}

bool WebRtcLocalEventLogManager::EventLogWrite(const PeerConnectionKey& key,
                                               const std::string& message) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
  auto it = log_files_.find(key);
  if (it == log_files_.end() || it->second.event_log == nullptr) {
    return false;
  }

  const bool write_successful = it->second.event_log->Write(message);

  if (!write_successful || it->second.event_log->MaxSizeReached()) {
    StopEventLogFileIfStarted(it);
  }

  return write_successful;
}

bool WebRtcLocalEventLogManager::EnableDataChannelLogging(
    const base::FilePath& base_path,
    size_t max_file_size_bytes) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);

  if (data_channel_log_state_.has_value()) {
    return false;
  }

  std::optional<size_t> max_size_bytes =
      (max_file_size_bytes == kWebRtcEventLogManagerUnlimitedFileSize)
          ? std::optional<size_t>()
          : std::optional<size_t>(max_file_size_bytes);

  data_channel_log_state_.emplace(
      base_path, max_size_bytes,
      /*max_logs_active=*/std::nullopt,
      /*max_logs_created=*/kMaxNumberLocalWebRtcDataChannelLogFiles);

  for (auto it = log_files_.begin(); it != log_files_.end(); ++it) {
    MaybeStartDataChannelLogFile(it);
  }

  return true;
}

bool WebRtcLocalEventLogManager::DisableDataChannelLogging() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);

  if (!data_channel_log_state_.has_value()) {
    return false;
  }

  for (auto it = log_files_.begin(); it != log_files_.end(); ++it) {
    StopDataChannelLogFileIfStarted(it);
  }
  CHECK_EQ(data_channel_log_state_->logs_active, 0U);

  data_channel_log_state_.reset();
  return true;
}

bool WebRtcLocalEventLogManager::DataChannelLogWrite(
    const PeerConnectionKey& key,
    const std::string& message) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
  auto it = log_files_.find(key);
  if (it == log_files_.end() || it->second.data_channel_log == nullptr) {
    return false;
  }

  const bool write_successful = it->second.data_channel_log->Write(message);

  if (!write_successful || it->second.data_channel_log->MaxSizeReached()) {
    StopDataChannelLogFileIfStarted(it);
  }

  return write_successful;
}

void WebRtcLocalEventLogManager::RenderProcessHostExitedDestroyed(
    int render_process_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
  StopLogging(render_process_id, StopLoggingAction::kStore, base::DoNothing());
}

void WebRtcLocalEventLogManager::StopLogging(int render_process_id,
                                             StopLoggingAction action,
                                             base::OnceClosure callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);

  for (auto it = log_files_.begin(); it != log_files_.end();) {
    if (it->first.render_process_id == render_process_id) {
      if (action == StopLoggingAction::kDelete) {
        if (it->second.event_log) {
          it->second.event_log->Delete();
        }
        // Data channel logs are not deleted because the only case for deleting
        // logs is via the Diagnostic Logging Cancel API, which does not cover
        // data channels.
      }
      StopEventLogFileIfStarted(it);
      StopDataChannelLogFileIfStarted(it);
      it = log_files_.erase(it);
    } else {
      ++it;
    }
  }

  if (callback) {
    std::move(callback).Run();
  }
}

void WebRtcLocalEventLogManager::SetClockForTesting(base::Clock* clock) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);
  clock_for_testing_ = clock;
}

void WebRtcLocalEventLogManager::MaybeStartEventLogFile(
    LogFilesMap::iterator log_it) {
  if (!event_log_state_.has_value()) {
    return;
  }

  LogTypeState& state = *event_log_state_;
  if (state.logs_active >= state.max_logs_active ||
      log_it->second.event_log != nullptr) {
    return;
  }

  std::unique_ptr<LogFileWriter> log_file =
      CreateLogFile(log_it->first, state.base_path, state.max_size_bytes);
  if (!log_file) {
    return;
  }

  log_it->second.event_log = std::move(log_file);
  ++state.logs_active;
  if (observer_) {
    observer_->OnLocalEventLogStarted(log_it->first,
                                      log_it->second.event_log->path());
  }
}

void WebRtcLocalEventLogManager::StopEventLogFileIfStarted(
    LogFilesMap::iterator log_it) {
  if (!log_it->second.event_log) {
    return;
  }

  log_it->second.event_log.reset();
  --event_log_state_->logs_active;
  if (observer_) {
    observer_->OnLocalEventLogStopped(log_it->first);
  }
}

void WebRtcLocalEventLogManager::MaybeStartDataChannelLogFile(
    LogFilesMap::iterator log_it) {
  if (!data_channel_log_state_.has_value()) {
    return;
  }

  LogTypeState& state = *data_channel_log_state_;
  if (state.logs_created >= state.max_logs_created ||
      log_it->second.data_channel_log != nullptr) {
    return;
  }

  std::unique_ptr<LogFileWriter> log_file =
      CreateLogFile(log_it->first, state.base_path, state.max_size_bytes);
  if (!log_file) {
    return;
  }

  log_it->second.data_channel_log = std::move(log_file);
  ++state.logs_created;
  if (observer_) {
    observer_->OnLocalDataChannelLogStarted(
        log_it->first, log_it->second.data_channel_log->path());
  }
}

void WebRtcLocalEventLogManager::StopDataChannelLogFileIfStarted(
    LogFilesMap::iterator log_it) {
  if (!log_it->second.data_channel_log) {
    return;
  }

  log_it->second.data_channel_log.reset();
  if (observer_) {
    observer_->OnLocalDataChannelLogStopped(log_it->first);
  }
}

std::unique_ptr<LogFileWriter> WebRtcLocalEventLogManager::CreateLogFile(
    const PeerConnectionKey& key,
    const base::FilePath& base_path,
    std::optional<size_t> max_log_size_bytes) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);

  // Add some information to the name given by the caller.
  base::FilePath file_path = GetFilePath(base_path, key);
  CHECK(!file_path.empty()) << "Couldn't set path for local log file.";

  // In the unlikely case that this filename is already taken, find a unique
  // number to append to the filename, if possible.
  file_path = base::GetUniquePath(file_path);
  if (file_path.empty()) {
    return nullptr;  // No available file path was found.
  }

  auto log_file =
      log_file_writer_factory_.Create(file_path, max_log_size_bytes);
  if (!log_file) {
    LOG(WARNING) << "Couldn't open " << file_path << " for logging.";
    return nullptr;
  }

  return log_file;
}

base::FilePath WebRtcLocalEventLogManager::GetFilePath(
    const base::FilePath& base_path,
    const PeerConnectionKey& key) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(io_task_sequence_checker_);

  // [user_defined]_[date]_[time]_[render_process_id]_[lid].[extension]
  const base::Time now =
      clock_for_testing_ ? clock_for_testing_->Now() : base::Time::Now();
  base::Time::Exploded exploded;
  now.LocalExplode(&exploded);
  const std::string timestamp =
      base::StringPrintf("%04d%02d%02d_%02d%02d", exploded.year, exploded.month,
                         exploded.day_of_month, exploded.hour, exploded.minute);
  return base_path.InsertBeforeExtension(FILE_PATH_LITERAL("_"))
      .AddExtension(log_file_writer_factory_.Extension())
      .InsertBeforeExtensionASCII(base::StringPrintf(
          "%s_%d_%d", timestamp.c_str(), key.render_process_id, key.lid));
}

}  // namespace webrtc_event_logging
