// Copyright 2024 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/ash/policy/skyvault/migration_notification_manager.h"

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/notification_utils.h"
#include "base/callback_list.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/sequence_checker.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/file_manager/open_util.h"
#include "chrome/browser/ash/policy/skyvault/policy_utils.h"
#include "chrome/browser/ash/policy/skyvault/signin_notification_helper.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_selections.h"
#include "chrome/browser/ui/webui/ash/skyvault/local_files_migration_dialog.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/browser_context.h"
#include "net/base/filename_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_features.h"
#include "ui/message_center/public/cpp/notification.h"

namespace policy::local_user_files {

namespace {

// Creates a notification with `kSkyvaultNotificationId`, `title` and `message`,
// that invokes `callback` when clicked on.
std::unique_ptr<message_center::Notification> CreateNotificationPtr(
    const std::u16string title,
    const std::u16string message,
    base::RepeatingCallback<void(std::optional<int>)> callback =
        base::DoNothing()) {
  message_center::RichNotificationData optional_fields;
  optional_fields.never_timeout = true;
  return ash::CreateSystemNotificationPtr(
      message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE,
      kSkyVaultMigrationNotificationId, title, message,
      /*display_source=*/std::u16string(), /*origin_url=*/GURL(),
      message_center::NotifierId(), optional_fields,
      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
          callback),
      features::IsRoundedIconsEnabled() ? vector_icons::kDomainIcon
                                        : vector_icons::kBusinessOldIcon,
      message_center::SystemNotificationWarningLevel::NORMAL);
}

// Closes the notification with `kSkyVaultMigrationNotificationId`.
void CloseNotification(Profile* profile) {
  NotificationDisplayServiceFactory::GetForProfile(profile)->Close(
      NotificationHandler::Type::TRANSIENT, kSkyVaultMigrationNotificationId);
}

// Closes the notification and, if the button is clicked, opens `path` in the
// Files App.
void HandleCompletedNotificationClick(Profile* profile,
                                      const base::FilePath& path,
                                      std::optional<int> button) {
  if (button.has_value() && button == 0) {
    file_manager::util::ShowItemInFolder(profile, path, base::DoNothing());
    ash::NewWindowDelegate::GetInstance()->OpenUrl(
        net::FilePathToFileURL(path),
        ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
        ash::NewWindowDelegate::Disposition::kNewForegroundTab);
  }
  CloseNotification(profile);
}

// Closes the notification and, if the button is clicked, opens `path` in the
// browser.
void HandleErrorNotificationClick(Profile* profile,
                                  const base::FilePath& path,
                                  std::optional<int> button) {
  if (button.has_value() && button == 0) {
    ash::NewWindowDelegate::GetInstance()->OpenUrl(
        net::FilePathToFileURL(path),
        ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
        ash::NewWindowDelegate::Disposition::kNewForegroundTab);
  }
  CloseNotification(profile);
}

// Returns the translation string corresponding to `destination`.
std::u16string CloudProviderToString(MigrationDestination destination) {
  switch (destination) {
    case MigrationDestination::kGoogleDrive:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_CLOUD_PROVIDER_GOOGLE_DRIVE);
    case MigrationDestination::kOneDrive:
      return l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_CLOUD_PROVIDER_ONEDRIVE);
    case MigrationDestination::kNotSpecified:
    case MigrationDestination::kDelete:
      NOTREACHED();
  }
}

}  // namespace

MigrationNotificationManager::MigrationNotificationManager(
    content::BrowserContext* context)
    : context_(context) {}

MigrationNotificationManager::~MigrationNotificationManager() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void MigrationNotificationManager::ShowMigrationInfoDialog(
    MigrationDestination destination,
    base::Time migration_start_time,
    base::OnceClosure migration_callback) {
  if (destination == MigrationDestination::kDelete &&
      !base::FeatureList::IsEnabled(ash::features::kSkyVaultV3)) {
    LOG(ERROR) << "Destination set to MigrationDestination::kDelete, but the "
                  "flag is disabled; ignoring.";
    return;
  }
  LocalFilesMigrationDialog::Show(destination, migration_start_time,
                                  std::move(migration_callback));
}

void MigrationNotificationManager::ShowMigrationProgressNotification(
    MigrationDestination destination) {
  DCHECK(IsCloudDestination(destination));

  std::u16string provider_str = CloudProviderToString(destination);

  std::u16string title = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_PROGRESS_TITLE),
      provider_str,
      /*offset=*/nullptr);
  std::u16string message = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_PROGRESS_MESSAGE),
      provider_str,
      /*offset=*/nullptr);

  auto notification = CreateNotificationPtr(title, message);

  NotificationDisplayServiceFactory::GetForProfile(profile())->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

void MigrationNotificationManager::ShowMigrationCompletedNotification(
    MigrationDestination destination,
    const base::FilePath& destination_path) {
  DCHECK(IsCloudDestination(destination));

  std::u16string provider_str = CloudProviderToString(destination);
  std::u16string folder_name = destination_path.BaseName().AsUTF16Unsafe();

  std::u16string title = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_COMPLETED_TITLE),
      {folder_name, provider_str},
      /*offsets=*/nullptr);
  std::u16string message = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_MIGRATION_COMPLETED_MESSAGE),
      provider_str,
      /*offset=*/nullptr);
  std::u16string button = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_COMPLETED_BUTTON),
      provider_str,
      /*offset=*/nullptr);

  auto notification = CreateNotificationPtr(
      title, message,
      base::BindRepeating(&HandleCompletedNotificationClick, profile(),
                          destination_path));
  notification->set_buttons({message_center::ButtonInfo(button)});

  NotificationDisplayServiceFactory::GetForProfile(profile())->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

void MigrationNotificationManager::ShowDeletionCompletedNotification() {
  std::u16string title =
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_DELETION_COMPLETED_TITLE);
  std::u16string message =
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_DELETION_COMPLETED_MESSAGE);
  auto notification = CreateNotificationPtr(title, message);

  NotificationDisplayServiceFactory::GetForProfile(profile())->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

void MigrationNotificationManager::ShowMigrationErrorNotification(
    MigrationDestination destination,
    const std::string& folder_name,
    const base::FilePath& error_log_path) {
  DCHECK(!error_log_path.empty());
  DCHECK(IsCloudDestination(destination));

  std::u16string provider_str = CloudProviderToString(destination);

  std::u16string title = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_ERROR_TITLE),
      provider_str,
      /*offset=*/nullptr);
  std::u16string message = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_ERROR_MESSAGE),
      {base::UTF8ToUTF16(folder_name), provider_str},
      /*offsets=*/nullptr);
  std::u16string button =
      l10n_util::GetStringUTF16(IDS_POLICY_SKYVAULT_MIGRATION_ERROR_BUTTON);

  auto notification =
      CreateNotificationPtr(title, message,
                            base::BindRepeating(&HandleErrorNotificationClick,
                                                profile(), error_log_path));
  notification->set_buttons({message_center::ButtonInfo(button)});

  NotificationDisplayServiceFactory::GetForProfile(profile())->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

void MigrationNotificationManager::ShowConfigurationErrorNotification(
    MigrationDestination destination) {
  DCHECK(IsCloudDestination(destination));

  std::u16string provider_str = CloudProviderToString(destination);

  std::u16string title = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_MIGRATION_CONFIG_ERROR_TITLE),
      provider_str,
      /*offset=*/nullptr);
  std::u16string message = base::ReplaceStringPlaceholders(
      l10n_util::GetStringUTF16(
          IDS_POLICY_SKYVAULT_MIGRATION_CONFIG_ERROR_MESSAGE),
      provider_str,
      /*offset=*/nullptr);

  auto notification = CreateNotificationPtr(title, message,
                                            /*callback=*/base::DoNothing());

  NotificationDisplayServiceFactory::GetForProfile(profile())->Display(
      NotificationHandler::Type::TRANSIENT, *notification,
      /*metadata=*/nullptr);
}

base::CallbackListSubscription
MigrationNotificationManager::ShowOneDriveSignInNotification(
    SignInCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (sign_in_callbacks_.empty()) {
    policy::skyvault_ui_utils::ShowSignInNotification(
        Profile::FromBrowserContext(context_), /*id=*/0,
        UploadTrigger::kMigration,
        /*file_path=*/base::FilePath(),
        base::BindOnce(&MigrationNotificationManager::OnSignInResponse,
                       weak_factory_.GetWeakPtr()));
  }

  // sign_in_callback_subscriptions_.emplace_back(
  return sign_in_callbacks_.Add(std::move(callback));
}

void MigrationNotificationManager::CloseNotifications() {
  // TODO(b/349097807): Potential race condition. When migration stopping is
  // fully implemented, make sure this runs after uploads were already stopped
  // (otherwise upload might fail before it's cancelled) and/or post this to
  // same sequence & fail new requests that come in (if closing exactly when an
  // upload job was getting paused for sign in).
  CloseNotification(profile());
}

void MigrationNotificationManager::CloseDialog() {
  LocalFilesMigrationDialog* dialog = LocalFilesMigrationDialog::GetDialog();
  if (dialog) {
    dialog->Close();
  }
}

Profile* MigrationNotificationManager::profile() {
  return Profile::FromBrowserContext(context_);
}

void MigrationNotificationManager::OnSignInResponse(base::File::Error error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (error == base::File::Error::FILE_OK) {
    // This is only reached for OneDrive.
    ShowMigrationProgressNotification(MigrationDestination::kOneDrive);
  }
  // If there was an error, the notification will be shown when migration fails.
  sign_in_callbacks_.Notify(error);
}

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

MigrationNotificationManager*
MigrationNotificationManagerFactory::GetForBrowserContext(
    content::BrowserContext* context) {
  return static_cast<MigrationNotificationManager*>(
      GetInstance()->GetServiceForBrowserContext(context, /*create=*/true));
}

MigrationNotificationManagerFactory::MigrationNotificationManagerFactory()
    : ProfileKeyedServiceFactory(
          "MigrationNotificationManager",
          ProfileSelections::Builder()
              .WithRegular(ProfileSelection::kOriginalOnly)
              // TODO(crbug.com/41488885): Check if this service is needed for
              // Ash Internals.
              .WithAshInternals(ProfileSelection::kOriginalOnly)
              .Build()) {
  DependsOn(NotificationDisplayServiceFactory::GetInstance());
}

MigrationNotificationManagerFactory::~MigrationNotificationManagerFactory() =
    default;

bool MigrationNotificationManagerFactory::ServiceIsNULLWhileTesting() const {
  return true;
}

std::unique_ptr<KeyedService>
MigrationNotificationManagerFactory::BuildServiceInstanceForBrowserContext(
    content::BrowserContext* context) const {
  return std::make_unique<MigrationNotificationManager>(context);
}

}  // namespace policy::local_user_files
