// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/notifications/platform_notification_service_proxy.h"

#include <memory>
#include <utility>

#include "base/check_op.h"
#include "base/task/post_task.h"
#include "content/browser/notifications/devtools_event_logging.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/notification_database_data.h"
#include "content/public/browser/platform_notification_service.h"
#include "content/public/common/content_client.h"

namespace content {

PlatformNotificationServiceProxy::PlatformNotificationServiceProxy(
    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
    BrowserContext* browser_context)
    : service_worker_context_(service_worker_context),
      browser_context_(browser_context),
      notification_service_(
          GetContentClient()->browser()->GetPlatformNotificationService(
              browser_context)) {}

PlatformNotificationServiceProxy::~PlatformNotificationServiceProxy() = default;

void PlatformNotificationServiceProxy::Shutdown() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  weak_ptr_factory_ui_.InvalidateWeakPtrs();
}

base::WeakPtr<PlatformNotificationServiceProxy>
PlatformNotificationServiceProxy::AsWeakPtr() {
  return weak_ptr_factory_ui_.GetWeakPtr();
}

void PlatformNotificationServiceProxy::DoDisplayNotification(
    const NotificationDatabaseData& data,
    const GURL& service_worker_scope,
    DisplayResultCallback callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (notification_service_) {
    notification_service_->DisplayPersistentNotification(
        data.notification_id, service_worker_scope, data.origin,
        data.notification_data,
        data.notification_resources.value_or(blink::NotificationResources()));
    notifications::LogNotificationDisplayedEventToDevTools(browser_context_,
                                                           data);
  }
  std::move(callback).Run(/* success= */ true, data.notification_id);
}

void PlatformNotificationServiceProxy::VerifyServiceWorkerScope(
    const NotificationDatabaseData& data,
    DisplayResultCallback callback,
    blink::ServiceWorkerStatusCode status,
    scoped_refptr<ServiceWorkerRegistration> registration) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  base::OnceClosure task;

  if (status == blink::ServiceWorkerStatusCode::kOk &&
      registration->scope().GetOrigin() == data.origin) {
    task = base::BindOnce(
        &PlatformNotificationServiceProxy::DoDisplayNotification, AsWeakPtr(),
        data, registration->scope(), std::move(callback));
  } else {
    task = base::BindOnce(std::move(callback), /* success= */ false,
                          /* notification_id= */ "");
  }

  if (ServiceWorkerContextWrapper::IsServiceWorkerOnUIEnabled()) {
    std::move(task).Run();
  } else {
    GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
        ->PostTask(FROM_HERE, std::move(task));
  }
}

void PlatformNotificationServiceProxy::DisplayNotification(
    const NotificationDatabaseData& data,
    DisplayResultCallback callback) {
  if (!service_worker_context_) {
    GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
        ->PostTask(FROM_HERE,
                   base::BindOnce(
                       &PlatformNotificationServiceProxy::DoDisplayNotification,
                       AsWeakPtr(), data, GURL(), std::move(callback)));
    return;
  }

  base::PostTask(
      FROM_HERE,
      {ServiceWorkerContext::GetCoreThreadId(),
       base::TaskPriority::USER_VISIBLE},
      base::BindOnce(
          &ServiceWorkerContextWrapper::FindReadyRegistrationForId,
          service_worker_context_, data.service_worker_registration_id,
          url::Origin::Create(data.origin),
          base::BindOnce(
              &PlatformNotificationServiceProxy::VerifyServiceWorkerScope,
              weak_ptr_factory_io_.GetWeakPtr(), data, std::move(callback))));
}

void PlatformNotificationServiceProxy::CloseNotification(
    const std::string& notification_id) {
  if (!notification_service_)
    return;
  GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
      ->PostTask(
          FROM_HERE,
          base::BindOnce(&PlatformNotificationServiceProxy::DoCloseNotification,
                         AsWeakPtr(), notification_id));
}

void PlatformNotificationServiceProxy::DoCloseNotification(
    const std::string& notification_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  notification_service_->ClosePersistentNotification(notification_id);
}

void PlatformNotificationServiceProxy::ScheduleTrigger(base::Time timestamp) {
  if (!notification_service_)
    return;
  GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
      ->PostTask(
          FROM_HERE,
          base::BindOnce(&PlatformNotificationServiceProxy::DoScheduleTrigger,
                         AsWeakPtr(), timestamp));
}

void PlatformNotificationServiceProxy::DoScheduleTrigger(base::Time timestamp) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  notification_service_->ScheduleTrigger(timestamp);
}

void PlatformNotificationServiceProxy::ScheduleNotification(
    const NotificationDatabaseData& data) {
  DCHECK(data.notification_data.show_trigger_timestamp.has_value());
  if (!notification_service_)
    return;
  GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
      ->PostTask(FROM_HERE,
                 base::BindOnce(
                     &PlatformNotificationServiceProxy::DoScheduleNotification,
                     AsWeakPtr(), data));
}

void PlatformNotificationServiceProxy::DoScheduleNotification(
    const NotificationDatabaseData& data) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  base::Time show_trigger_timestamp =
      data.notification_data.show_trigger_timestamp.value();
  notifications::LogNotificationScheduledEventToDevTools(
      browser_context_, data, show_trigger_timestamp);
  notification_service_->ScheduleTrigger(show_trigger_timestamp);
}

base::Time PlatformNotificationServiceProxy::GetNextTrigger() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!notification_service_)
    return base::Time::Max();
  return notification_service_->ReadNextTriggerTimestamp();
}

void PlatformNotificationServiceProxy::RecordNotificationUkmEvent(
    const NotificationDatabaseData& data) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!notification_service_)
    return;
  notification_service_->RecordNotificationUkmEvent(data);
}

bool PlatformNotificationServiceProxy::ShouldLogClose(const GURL& origin) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  return notifications::ShouldLogNotificationEventToDevTools(browser_context_,
                                                             origin);
}

void PlatformNotificationServiceProxy::LogClose(
    const NotificationDatabaseData& data) {
  GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
      ->PostTask(FROM_HERE,
                 base::BindOnce(&PlatformNotificationServiceProxy::DoLogClose,
                                AsWeakPtr(), data));
}

void PlatformNotificationServiceProxy::DoLogClose(
    const NotificationDatabaseData& data) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  notifications::LogNotificationClosedEventToDevTools(browser_context_, data);
}

}  // namespace content
