// 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 "ash/constants/ash_features.h"
#include "ash/constants/webui_url_constants.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/notreached.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chrome/browser/ash/policy/skyvault/local_files_migration_constants.h"
#include "chrome/browser/ash/policy/skyvault/policy_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/webui/ash/skyvault/local_files_migration_dialog.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/base/filename_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace policy::local_user_files {

// Tests the MigrationNotificationManager class, which is in charge of most
// SkyVault migration notifications and dialogs.
class MigrationNotificationManagerTest : public InProcessBrowserTest {
 public:
  MigrationNotificationManagerTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kSkyVault, ash::features::kSkyVaultV2},
        /*disabled_features=*/{});
  }
  ~MigrationNotificationManagerTest() override = default;

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    tester_ = std::make_unique<NotificationDisplayServiceTester>(profile());
    ASSERT_TRUE(manager());
  }

 protected:
  Profile* profile() { return browser()->GetProfile(); }

  MigrationNotificationManager* manager() {
    return MigrationNotificationManagerFactory::GetInstance()
        ->GetForBrowserContext(profile());
  }

  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<NotificationDisplayServiceTester> tester_;
};

class MigrationNotificationManagerParamTest
    : public MigrationNotificationManagerTest,
      public ::testing::WithParamInterface<MigrationDestination> {
 public:
  MigrationNotificationManagerParamTest() {
    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
  }
  static std::string ParamToName(const testing::TestParamInfo<ParamType> info) {
    switch (info.param) {
      case MigrationDestination::kGoogleDrive:
        return "google_drive";
      case MigrationDestination::kOneDrive:
        return "one_drive";
      case MigrationDestination::kNotSpecified:
      case MigrationDestination::kDelete:
        NOTREACHED();
    }
  }

 protected:
  MigrationDestination CloudProvider() { return GetParam(); }

  base::ScopedTempDir temp_dir_;
};

// Tests that a progress notification is shown, and closed when
// CloseNotifications() is called.
IN_PROC_BROWSER_TEST_P(MigrationNotificationManagerParamTest,
                       ShowMigrationProgressNotification) {
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->ShowMigrationProgressNotification(CloudProvider());
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->CloseNotifications();
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));
}

// Tests that a completed notification is shown, and closed when
// CloseNotifications() is called.
IN_PROC_BROWSER_TEST_P(MigrationNotificationManagerParamTest,
                       ShowMigrationCompletedNotification) {
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->ShowMigrationCompletedNotification(
      CloudProvider(),
      /*destination_path=*/base::FilePath());
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->CloseNotifications();
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));
}

// Tests that a deletion completed notification is shown, and closed when
// CloseNotifications() is called.
IN_PROC_BROWSER_TEST_F(MigrationNotificationManagerTest,
                       ShowDeletionCompletedNotification) {
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->ShowDeletionCompletedNotification();
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->CloseNotifications();
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));
}

// Tests that an error notification is shown, and closed when
// CloseNotifications() is called.
IN_PROC_BROWSER_TEST_P(MigrationNotificationManagerParamTest,
                       ShowMigrationErrorNotification_CloseNotifications) {
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->ShowMigrationErrorNotification(
      CloudProvider(), kUploadRootPrefix,
      /*error_log_path=*/
      base::FilePath(kErrorLogFileBasePath).Append(kErrorLogFileName));
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->CloseNotifications();
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));
}

// Tests that clicking on the "Review error log" button on an error notification
// correctly openes the passed path in the browser and closes the notification.
IN_PROC_BROWSER_TEST_P(MigrationNotificationManagerParamTest,
                       ShowMigrationErrorNotification_ReviewErrorLog) {
  const base::FilePath error_log_path =
      temp_dir_.GetPath().Append(kErrorLogFileName);
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    CHECK(WriteFile(error_log_path,
                    "/home/chronos/user/MyFiles/Downloads/test_file.txt - "
                    "Something went wrong. Try again."));
    CHECK(base::PathExists(error_log_path));
  }

  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->ShowMigrationErrorNotification(CloudProvider(), kUploadRootPrefix,
                                            error_log_path);
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  const GURL error_log_url = net::FilePathToFileURL(error_log_path);
  EXPECT_NE(
      error_log_url,
      browser()->tab_strip_model()->GetActiveWebContents()->GetURL().spec());

  tester_->SimulateClick(NotificationHandler::Type::TRANSIENT,
                         kSkyVaultMigrationNotificationId, 0, std::nullopt);

  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));
  EXPECT_EQ(
      error_log_url,
      browser()->tab_strip_model()->GetActiveWebContents()->GetURL().spec());
}

// Tests that a policy configuration error notification is shown, and closed
// when CloseNotifications() is called.
IN_PROC_BROWSER_TEST_P(MigrationNotificationManagerParamTest,
                       ShowConfigurationErrorNotification) {
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->ShowConfigurationErrorNotification(CloudProvider());
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->CloseNotifications();
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));
}

// Tests that a sign in notification is shown once, even if multiple requests
// are called, and that closing it notifies all the requesters.
IN_PROC_BROWSER_TEST_F(MigrationNotificationManagerTest,
                       ShowSignInNotification_CloseByUser) {
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  // Check that only one notification is added.
  base::MockCallback<base::RepeatingClosure> cb;
  EXPECT_CALL(cb, Run).Times(1);
  tester_->SetNotificationAddedClosure(cb.Get());

  base::test::TestFuture<base::File::Error> sign_in_future_1;
  base::test::TestFuture<base::File::Error> sign_in_future_2;

  base::CallbackListSubscription subscription_1 =
      manager()->ShowOneDriveSignInNotification(sign_in_future_1.GetCallback());
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  base::CallbackListSubscription subscription_2 =
      manager()->ShowOneDriveSignInNotification(sign_in_future_2.GetCallback());
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  // Cancel the sign in.
  tester_->RemoveNotification(NotificationHandler::Type::TRANSIENT,
                              kSkyVaultMigrationNotificationId,
                              /*by_user=*/true,
                              /*silent=*/false);
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  // Both callbacks should run.
  EXPECT_EQ(sign_in_future_1.Get(), base::File::Error::FILE_ERROR_FAILED);
  EXPECT_EQ(sign_in_future_2.Get(), base::File::Error::FILE_ERROR_FAILED);
}

// Tests that when a sign in notification is closed by CloseNotifications(), all
// requesters to sign in are notified.
IN_PROC_BROWSER_TEST_F(MigrationNotificationManagerTest,
                       ShowSignInNotification_CloseNotifications) {
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  // Check that only one notification is added.
  base::MockCallback<base::RepeatingClosure> cb;
  EXPECT_CALL(cb, Run).Times(1);
  tester_->SetNotificationAddedClosure(cb.Get());

  base::test::TestFuture<base::File::Error> sign_in_future_1;
  base::test::TestFuture<base::File::Error> sign_in_future_2;

  base::CallbackListSubscription subscription_1 =
      manager()->ShowOneDriveSignInNotification(sign_in_future_1.GetCallback());
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  base::CallbackListSubscription subscription_2 =
      manager()->ShowOneDriveSignInNotification(sign_in_future_2.GetCallback());
  EXPECT_TRUE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  manager()->CloseNotifications();
  EXPECT_FALSE(tester_->GetNotification(kSkyVaultMigrationNotificationId));

  // Both callbacks should run.
  EXPECT_EQ(sign_in_future_1.Get(), base::File::Error::FILE_ERROR_FAILED);
  EXPECT_EQ(sign_in_future_2.Get(), base::File::Error::FILE_ERROR_FAILED);
}

// Tests that a migration dialog is shown, and closed when CloseDialog() is
// called.
IN_PROC_BROWSER_TEST_P(MigrationNotificationManagerParamTest, ShowDialog) {
  EXPECT_FALSE(LocalFilesMigrationDialog::GetDialog());

  content::TestNavigationObserver navigation_observer_dialog(
      (GURL(ash::kChromeUILocalFilesMigrationURL)));
  navigation_observer_dialog.StartWatchingNewWebContents();

  base::MockCallback<StartMigrationCallback> mock_cb;
  manager()->ShowMigrationInfoDialog(
      CloudProvider(), base::Time::Now() + base::Minutes(5), mock_cb.Get());

  navigation_observer_dialog.Wait();
  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());

  ash::SystemWebDialogDelegate* dialog = LocalFilesMigrationDialog::GetDialog();
  EXPECT_TRUE(dialog);

  content::WebUI* web_ui = dialog->GetWebUIForTest();
  EXPECT_TRUE(web_ui);
  content::WebContents* web_contents = web_ui->GetWebContents();
  content::WebContentsDestroyedWatcher watcher(web_contents);

  manager()->CloseDialog();
  watcher.Wait();

  EXPECT_FALSE(LocalFilesMigrationDialog::GetDialog());
}

INSTANTIATE_TEST_SUITE_P(LocalUserFiles,
                         MigrationNotificationManagerParamTest,
                         ::testing::Values(MigrationDestination::kGoogleDrive,
                                           MigrationDestination::kOneDrive),
                         MigrationNotificationManagerParamTest::ParamToName);

}  // namespace policy::local_user_files
