// 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/notifications/stub_notification_display_service.h"

#include <algorithm>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "chrome/browser/notifications/notification_handler.h"
#include "chrome/browser/profiles/profile.h"
#include "url/origin.h"

// static
std::unique_ptr<KeyedService> StubNotificationDisplayService::FactoryForTests(
    content::BrowserContext* context) {
  return std::make_unique<StubNotificationDisplayService>(
      Profile::FromBrowserContext(context));
}

StubNotificationDisplayService::StubNotificationDisplayService(Profile* profile)
    : NotificationDisplayServiceImpl(profile), profile_(profile) {}

StubNotificationDisplayService::~StubNotificationDisplayService() = default;

void StubNotificationDisplayService::SetNotificationAddedClosure(
    base::RepeatingClosure closure) {
  notification_added_closure_ = std::move(closure);
}

void StubNotificationDisplayService::SetNotificationClosedClosure(
    base::RepeatingClosure closure) {
  notification_closed_closure_ = std::move(closure);
}

std::vector<message_center::Notification>
StubNotificationDisplayService::GetDisplayedNotificationsForType(
    NotificationHandler::Type type) const {
  std::vector<message_center::Notification> notifications;
  for (const auto& data : notifications_) {
    if (data.type != type)
      continue;

    notifications.push_back(data.notification);
  }

  return notifications;
}

std::optional<message_center::Notification>
StubNotificationDisplayService::GetNotification(
    const std::string& notification_id) {
  auto iter = std::ranges::find(
      notifications_, notification_id,
      [](const NotificationData& data) { return data.notification.id(); });

  if (iter == notifications_.end())
    return std::nullopt;

  return iter->notification;
}

const NotificationCommon::Metadata*
StubNotificationDisplayService::GetMetadataForNotification(
    const message_center::Notification& notification) {
  auto iter = std::ranges::find(
      notifications_, notification.id(),
      [](const NotificationData& data) { return data.notification.id(); });

  if (iter == notifications_.end())
    return nullptr;

  return iter->metadata.get();
}

void StubNotificationDisplayService::SimulateClick(
    NotificationHandler::Type notification_type,
    const std::string& notification_id,
    std::optional<int> action_index,
    std::optional<std::u16string> reply) {
  auto iter = FindNotification(notification_type, notification_id);
  if (iter == notifications_.end())
    return;

  NotificationHandler* handler = GetNotificationHandler(notification_type);
  if (notification_type == NotificationHandler::Type::TRANSIENT) {
    DCHECK(!handler);

    auto* delegate = iter->notification.delegate();
    if (delegate)
      delegate->Click(action_index, reply);
    return;
  }

  DCHECK(handler);
  base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
  handler->OnClick(profile_, iter->notification.origin_url(), notification_id,
                   action_index, reply, run_loop.QuitClosure());
  run_loop.Run();
}

void StubNotificationDisplayService::SimulateSettingsClick(
    NotificationHandler::Type notification_type,
    const std::string& notification_id) {
  auto iter = FindNotification(notification_type, notification_id);
  if (iter == notifications_.end())
    return;

  NotificationHandler* handler = GetNotificationHandler(notification_type);
  if (notification_type == NotificationHandler::Type::TRANSIENT) {
    DCHECK(!handler);
    if (iter->notification.delegate())
      iter->notification.delegate()->SettingsClick();
  } else {
    DCHECK(handler);
    handler->OpenSettings(profile_, iter->notification.origin_url());
  }
}

void StubNotificationDisplayService::RemoveNotification(
    NotificationHandler::Type notification_type,
    const std::string& notification_id,
    bool by_user,
    bool silent) {
  auto iter = FindNotification(notification_type, notification_id);
  if (iter == notifications_.end())
    return;
  NotificationData data = std::move(*iter);
  notifications_.erase(iter);

  if (!silent) {
    NotificationHandler* handler = GetNotificationHandler(notification_type);
    if (notification_type == NotificationHandler::Type::TRANSIENT) {
      DCHECK(!handler);
      if (data.notification.delegate())
        data.notification.delegate()->Close(by_user);
    } else {
      base::RunLoop run_loop;
      handler->OnClose(profile_, data.notification.origin_url(),
                       notification_id, by_user, run_loop.QuitClosure());
      run_loop.Run();
    }
  }
}

void StubNotificationDisplayService::RemoveAllNotifications(
    NotificationHandler::Type notification_type,
    bool by_user) {
  NotificationHandler* handler = GetNotificationHandler(notification_type);
  DCHECK_NE(!!handler,
            notification_type == NotificationHandler::Type::TRANSIENT);
  for (auto iter = notifications_.begin(); iter != notifications_.end();) {
    if (iter->type == notification_type) {
      NotificationData data = std::move(*iter);
      iter = notifications_.erase(iter);
      if (handler) {
        base::RunLoop run_loop;
        handler->OnClose(profile_, data.notification.origin_url(),
                         data.notification.id(), by_user,
                         run_loop.QuitClosure());
        run_loop.Run();
      } else if (data.notification.delegate()) {
        data.notification.delegate()->Close(by_user);
      }
    } else {
      iter++;
    }
  }
}

void StubNotificationDisplayService::SetProcessNotificationOperationDelegate(
    const ProcessNotificationOperationCallback& delegate) {
  process_notification_operation_delegate_ = delegate;
}

void StubNotificationDisplayService::Display(
    NotificationHandler::Type notification_type,
    const message_center::Notification& notification,
    std::unique_ptr<NotificationCommon::Metadata> metadata) {
  // This mimics notification replacement behaviour; the Close() method on a
  // notification's delegate is not meant to be invoked in this situation.
  RemoveNotification(notification_type, notification.id(), false /* by_user */,
                     true /* silent */);

  NotificationHandler* handler = GetNotificationHandler(notification_type);
  if (notification_type == NotificationHandler::Type::TRANSIENT) {
    CHECK(!handler);
    CHECK(notification.delegate());
  } else {
    handler->OnShow(profile_, notification.id());
  }

  notifications_.emplace_back(notification_type, notification,
                              std::move(metadata));

  if (notification_added_closure_)
    notification_added_closure_.Run();
}

void StubNotificationDisplayService::Close(
    NotificationHandler::Type notification_type,
    const std::string& notification_id) {
  // Close the notification silently only for non-transient notifications,
  // because some tests of transient (non-web/extension) notifications rely on
  // the close event being dispatched, e.g. tests in WebUsbDetectorTest.
  RemoveNotification(
      notification_type, notification_id, false /* by_user */,
      notification_type != NotificationHandler::Type::TRANSIENT /* silent */);

  if (notification_closed_closure_)
    notification_closed_closure_.Run();
}

void StubNotificationDisplayService::GetDisplayed(
    DisplayedNotificationsCallback callback) {
  std::set<std::string> notifications;

  for (const auto& notification_data : notifications_) {
    notifications.insert(notification_data.notification.id());
  }

  std::move(callback).Run(std::move(notifications),
                          true /* supports_synchronization */);
}

void StubNotificationDisplayService::GetDisplayedForOrigin(
    const GURL& origin,
    DisplayedNotificationsCallback callback) {
  std::set<std::string> notifications;

  for (const auto& notification_data : notifications_) {
    if (url::IsSameOriginWith(notification_data.notification.origin_url(),
                              origin)) {
      notifications.insert(notification_data.notification.id());
    }
  }

  std::move(callback).Run(std::move(notifications),
                          true /* supports_synchronization */);
}

void StubNotificationDisplayService::ProcessNotificationOperation(
    NotificationOperation operation,
    NotificationHandler::Type notification_type,
    const GURL& origin,
    const std::string& notification_id,
    const std::optional<int>& action_index,
    const std::optional<std::u16string>& reply,
    const std::optional<bool>& by_user,
    const std::optional<bool>& is_suspicious,
    base::OnceClosure on_complete_cb) {
  if (process_notification_operation_delegate_) {
    // TODO(b/375547360): run `on_complete_cb` when notification processing
    // finishes.
    process_notification_operation_delegate_.Run(
        operation, notification_type, origin, notification_id, action_index,
        reply, by_user, is_suspicious);
    return;
  }

  NotificationDisplayServiceImpl::ProcessNotificationOperation(
      operation, notification_type, origin, notification_id, action_index,
      reply, by_user, is_suspicious, std::move(on_complete_cb));
}

StubNotificationDisplayService::NotificationData::NotificationData(
    NotificationHandler::Type type,
    const message_center::Notification& notification,
    std::unique_ptr<NotificationCommon::Metadata> metadata)
    : type(type), notification(notification), metadata(std::move(metadata)) {}

StubNotificationDisplayService::NotificationData::NotificationData(
    NotificationData&& other)
    : type(other.type),
      notification(std::move(other.notification)),
      metadata(std::move(other.metadata)) {}

StubNotificationDisplayService::NotificationData::~NotificationData() = default;

StubNotificationDisplayService::NotificationData&
StubNotificationDisplayService::NotificationData::operator=(
    NotificationData&& other) {
  type = other.type;
  notification = std::move(other.notification);
  metadata = std::move(other.metadata);
  return *this;
}

std::vector<StubNotificationDisplayService::NotificationData>::iterator
StubNotificationDisplayService::FindNotification(
    NotificationHandler::Type notification_type,
    const std::string& notification_id) {
  return std::ranges::find_if(
      notifications_,
      [notification_type, &notification_id](const NotificationData& data) {
        return data.type == notification_type &&
               data.notification.id() == notification_id;
      });
}
