// 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 "content/browser/background_fetch/background_fetch_event_dispatcher.h"

#include <map>
#include <sstream>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/stringprintf.h"
#include "content/browser/background_fetch/background_fetch_registration_id.h"
#include "content/browser/background_fetch/background_fetch_registration_service_impl.h"
#include "content/browser/devtools/devtools_background_services_context_impl.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/common/background_fetch/background_fetch_types.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"

namespace content {

namespace {

// Returns a human-readable string for the given |event| type.
std::string EventTypeToString(ServiceWorkerMetrics::EventType event) {
  switch (event) {
    case ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_ABORT:
      return "BackgroundFetchAbortEvent";
    case ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_CLICK:
      return "BackgroundFetchClickEvent";
    case ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_FAIL:
      return "BackgroundFetchFailEvent";
    case ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_SUCCESS:
      return "BackgroundFetchSuccessEvent";
    default:
      NOTREACHED();
  }
}

}  // namespace

BackgroundFetchEventDispatcher::BackgroundFetchEventDispatcher(
    BackgroundFetchContext* background_fetch_context,
    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
    DevToolsBackgroundServicesContextImpl& devtools_context)
    : background_fetch_context_(background_fetch_context),
      service_worker_context_(std::move(service_worker_context)),
      devtools_context_(&devtools_context) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M158);
  CHECK(background_fetch_context_, base::NotFatalUntil::M158);
}

BackgroundFetchEventDispatcher::~BackgroundFetchEventDispatcher() {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M158);
}

void BackgroundFetchEventDispatcher::DispatchBackgroundFetchCompletionEvent(
    const BackgroundFetchRegistrationId& registration_id,
    blink::mojom::BackgroundFetchRegistrationDataPtr registration_data,
    base::OnceClosure finished_closure) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M158);
  CHECK(registration_data, base::NotFatalUntil::M158);

  auto registration = blink::mojom::BackgroundFetchRegistration::New(
      std::move(registration_data),
      BackgroundFetchRegistrationServiceImpl::CreateInterfaceInfo(
          registration_id, background_fetch_context_->GetWeakPtr()));

  switch (registration->registration_data->failure_reason) {
    case blink::mojom::BackgroundFetchFailureReason::NONE:
      DCHECK_EQ(registration->registration_data->result,
                blink::mojom::BackgroundFetchResult::SUCCESS);
      DispatchBackgroundFetchSuccessEvent(registration_id,
                                          std::move(registration),
                                          std::move(finished_closure));
      return;
    case blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI:
    case blink::mojom::BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER:
      DCHECK_EQ(registration->registration_data->result,
                blink::mojom::BackgroundFetchResult::FAILURE);
      DispatchBackgroundFetchAbortEvent(registration_id,
                                        std::move(registration),
                                        std::move(finished_closure));
      return;
    case blink::mojom::BackgroundFetchFailureReason::BAD_STATUS:
    case blink::mojom::BackgroundFetchFailureReason::FETCH_ERROR:
    case blink::mojom::BackgroundFetchFailureReason::SERVICE_WORKER_UNAVAILABLE:
    case blink::mojom::BackgroundFetchFailureReason::QUOTA_EXCEEDED:
    case blink::mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED:
      DCHECK_EQ(registration->registration_data->result,
                blink::mojom::BackgroundFetchResult::FAILURE);
      DispatchBackgroundFetchFailEvent(registration_id, std::move(registration),
                                       std::move(finished_closure));
      return;
  }
  NOTREACHED();
}

void BackgroundFetchEventDispatcher::DispatchBackgroundFetchAbortEvent(
    const BackgroundFetchRegistrationId& registration_id,
    blink::mojom::BackgroundFetchRegistrationPtr registration,
    base::OnceClosure finished_closure) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  LogBackgroundFetchCompletionForDevTools(
      registration_id, ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_ABORT,
      registration->registration_data->failure_reason);

  LoadServiceWorkerRegistrationForDispatch(
      registration_id, ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_ABORT,
      std::move(finished_closure),
      base::BindOnce(
          &BackgroundFetchEventDispatcher::DoDispatchBackgroundFetchAbortEvent,
          std::move(registration)));
}

void BackgroundFetchEventDispatcher::DoDispatchBackgroundFetchAbortEvent(
    blink::mojom::BackgroundFetchRegistrationPtr registration,
    scoped_refptr<ServiceWorkerVersion> service_worker_version,
    int request_id) {
  DCHECK(service_worker_version);
  DCHECK(registration);
  service_worker_version->endpoint()->DispatchBackgroundFetchAbortEvent(
      std::move(registration),
      service_worker_version->CreateSimpleEventCallback(request_id));
}

void BackgroundFetchEventDispatcher::DispatchBackgroundFetchClickEvent(
    const BackgroundFetchRegistrationId& registration_id,
    blink::mojom::BackgroundFetchRegistrationDataPtr registration_data,
    base::OnceClosure finished_closure) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(registration_data);

  auto registration = blink::mojom::BackgroundFetchRegistration::New(
      std::move(registration_data),
      BackgroundFetchRegistrationServiceImpl::CreateInterfaceInfo(
          registration_id, background_fetch_context_->GetWeakPtr()));

  LoadServiceWorkerRegistrationForDispatch(
      registration_id, ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_CLICK,
      std::move(finished_closure),
      base::BindOnce(
          &BackgroundFetchEventDispatcher::DoDispatchBackgroundFetchClickEvent,
          std::move(registration)));
}

void BackgroundFetchEventDispatcher::DoDispatchBackgroundFetchClickEvent(
    blink::mojom::BackgroundFetchRegistrationPtr registration,
    scoped_refptr<ServiceWorkerVersion> service_worker_version,
    int request_id) {
  DCHECK(service_worker_version);
  DCHECK(registration);
  service_worker_version->endpoint()->DispatchBackgroundFetchClickEvent(
      std::move(registration),
      service_worker_version->CreateSimpleEventCallback(request_id));
}

void BackgroundFetchEventDispatcher::DispatchBackgroundFetchFailEvent(
    const BackgroundFetchRegistrationId& registration_id,
    blink::mojom::BackgroundFetchRegistrationPtr registration,
    base::OnceClosure finished_closure) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  LogBackgroundFetchCompletionForDevTools(
      registration_id, ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_FAIL,
      registration->registration_data->failure_reason);

  LoadServiceWorkerRegistrationForDispatch(
      registration_id, ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_FAIL,
      std::move(finished_closure),
      base::BindOnce(
          &BackgroundFetchEventDispatcher::DoDispatchBackgroundFetchFailEvent,
          std::move(registration)));
}

void BackgroundFetchEventDispatcher::DoDispatchBackgroundFetchFailEvent(
    blink::mojom::BackgroundFetchRegistrationPtr registration,
    scoped_refptr<ServiceWorkerVersion> service_worker_version,
    int request_id) {
  DCHECK(service_worker_version);
  DCHECK(registration);
  service_worker_version->endpoint()->DispatchBackgroundFetchFailEvent(
      std::move(registration),
      service_worker_version->CreateSimpleEventCallback(request_id));
}

void BackgroundFetchEventDispatcher::DispatchBackgroundFetchSuccessEvent(
    const BackgroundFetchRegistrationId& registration_id,
    blink::mojom::BackgroundFetchRegistrationPtr registration,
    base::OnceClosure finished_closure) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  LogBackgroundFetchCompletionForDevTools(
      registration_id,
      ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_SUCCESS,
      registration->registration_data->failure_reason);

  LoadServiceWorkerRegistrationForDispatch(
      registration_id,
      ServiceWorkerMetrics::EventType::BACKGROUND_FETCH_SUCCESS,
      std::move(finished_closure),
      base::BindOnce(&BackgroundFetchEventDispatcher::
                         DoDispatchBackgroundFetchSuccessEvent,
                     std::move(registration)));
}

void BackgroundFetchEventDispatcher::DoDispatchBackgroundFetchSuccessEvent(
    blink::mojom::BackgroundFetchRegistrationPtr registration,
    scoped_refptr<ServiceWorkerVersion> service_worker_version,
    int request_id) {
  DCHECK(service_worker_version);
  DCHECK(registration);
  service_worker_version->endpoint()->DispatchBackgroundFetchSuccessEvent(
      std::move(registration),
      service_worker_version->CreateSimpleEventCallback(request_id));
}

void BackgroundFetchEventDispatcher::LoadServiceWorkerRegistrationForDispatch(
    const BackgroundFetchRegistrationId& registration_id,
    ServiceWorkerMetrics::EventType event,
    base::OnceClosure finished_closure,
    ServiceWorkerLoadedCallback loaded_callback) {
  service_worker_context_->FindReadyRegistrationForId(
      registration_id.service_worker_registration_id(),
      registration_id.storage_key(),
      base::BindOnce(
          &BackgroundFetchEventDispatcher::StartActiveWorkerForDispatch, event,
          std::move(finished_closure), std::move(loaded_callback)));
}

void BackgroundFetchEventDispatcher::StartActiveWorkerForDispatch(
    ServiceWorkerMetrics::EventType event,
    base::OnceClosure finished_closure,
    ServiceWorkerLoadedCallback loaded_callback,
    blink::ServiceWorkerStatusCode service_worker_status,
    scoped_refptr<ServiceWorkerRegistration> registration) {
  if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
    DidDispatchEvent(event, std::move(finished_closure), DispatchPhase::FINDING,
                     service_worker_status);
    return;
  }

  ServiceWorkerVersion* service_worker_version = registration->active_version();
  DCHECK(service_worker_version);

  service_worker_version->RunAfterStartWorker(
      event,
      base::BindOnce(&BackgroundFetchEventDispatcher::DispatchEvent, event,
                     std::move(finished_closure), std::move(loaded_callback),
                     base::WrapRefCounted(service_worker_version)));
}

void BackgroundFetchEventDispatcher::DispatchEvent(
    ServiceWorkerMetrics::EventType event,
    base::OnceClosure finished_closure,
    ServiceWorkerLoadedCallback loaded_callback,
    scoped_refptr<ServiceWorkerVersion> service_worker_version,
    blink::ServiceWorkerStatusCode start_worker_status) {
  if (start_worker_status != blink::ServiceWorkerStatusCode::kOk) {
    DidDispatchEvent(event, std::move(finished_closure),
                     DispatchPhase::STARTING, start_worker_status);
    return;
  }

  int request_id = service_worker_version->StartRequest(
      event,
      base::BindOnce(&BackgroundFetchEventDispatcher::DidDispatchEvent, event,
                     std::move(finished_closure), DispatchPhase::DISPATCHING));

  std::move(loaded_callback).Run(std::move(service_worker_version), request_id);
}

void BackgroundFetchEventDispatcher::DidDispatchEvent(
    ServiceWorkerMetrics::EventType event,
    base::OnceClosure finished_closure,
    DispatchPhase dispatch_phase,
    blink::ServiceWorkerStatusCode service_worker_status) {
  std::move(finished_closure).Run();
}

void BackgroundFetchEventDispatcher::LogBackgroundFetchCompletionForDevTools(
    const BackgroundFetchRegistrationId& registration_id,
    ServiceWorkerMetrics::EventType event_type,
    blink::mojom::BackgroundFetchFailureReason failure_reason) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  CHECK(devtools_context_);

  if (!devtools_context_->IsRecording(
          DevToolsBackgroundService::kBackgroundFetch)) {
    return;
  }

  std::map<std::string, std::string> metadata = {
      {"Event Type", EventTypeToString(event_type)}};
  if (failure_reason != blink::mojom::BackgroundFetchFailureReason::NONE) {
    std::stringstream stream;
    stream << failure_reason;
    metadata["Failure Reason"] = stream.str();
  }

  devtools_context_->LogBackgroundServiceEvent(
      registration_id.service_worker_registration_id(),
      registration_id.storage_key(),
      DevToolsBackgroundService::kBackgroundFetch,
      /* event_name= */ "Background Fetch completed",
      /* instance_id= */ registration_id.developer_id(), metadata);
}

void BackgroundFetchEventDispatcher::Shutdown() {
  devtools_context_ = nullptr;
}

}  // namespace content
