// 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.

#import "ios/chrome/browser/tips_notifications/model/tips_notification_client.h"

#import <UserNotifications/UserNotifications.h>

#import "base/functional/callback_helpers.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/scoped_feature_list.h"
#import "base/test/scoped_mock_clock_override.h"
#import "base/test/simple_test_clock.h"
#import "base/threading/thread_restrictions.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/prefs/scoped_user_pref_update.h"
#import "components/safe_browsing/core/common/safe_browsing_prefs.h"
#import "components/sync/test/test_sync_service.h"
#import "ios/chrome/browser/content_suggestions/ui/content_suggestions_commands.h"
#import "ios/chrome/browser/default_browser/model/promo_source.h"
#import "ios/chrome/browser/default_browser/model/utils.h"
#import "ios/chrome/browser/default_browser/model/utils_test_support.h"
#import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
#import "ios/chrome/browser/first_run/model/first_run.h"
#import "ios/chrome/browser/push_notification/model/constants.h"
#import "ios/chrome/browser/push_notification/model/push_notification_util.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_manager_ios.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h"
#import "ios/chrome/browser/shared/public/commands/promos_manager_commands.h"
#import "ios/chrome/browser/shared/public/commands/scene_commands.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/whats_new_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/fake_authentication_service_delegate.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/signin/model/fake_system_identity_manager.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/sync/model/test_sync_service_utils.h"
#import "ios/chrome/browser/tips_notifications/model/utils.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/testing/scoped_block_swizzler.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#import "ui/base/device_form_factor.h"

using startup_metric_utils::FirstRunSentinelCreationResult;

class TipsNotificationClientTest : public PlatformTest {
 protected:
  TipsNotificationClientTest() {
    [[NSUserDefaults standardUserDefaults]
        removeObjectForKey:@"TipsNotificationTrigger"];
    SetupMockNotificationCenter();
    TestProfileIOS::Builder builder;
    builder.AddTestingFactory(
        AuthenticationServiceFactory::GetInstance(),
        AuthenticationServiceFactory::GetFactoryWithDelegateForTesting(
            std::make_unique<FakeAuthenticationServiceDelegate>()));
    builder.AddTestingFactory(SyncServiceFactory::GetInstance(),
                              base::BindRepeating(&CreateTestSyncService));
    profile_ = profile_manager_.AddProfileWithBuilder(std::move(builder));
    BrowserList* list = BrowserListFactory::GetForProfile(profile_);
    scene_state_ = [[SceneState alloc] init];
    scene_state_.activationLevel = SceneActivationLevelForegroundActive;
    browser_ = std::make_unique<TestBrowser>(profile_, scene_state_);
    list->AddBrowser(browser_.get());
    client_ = std::make_unique<TipsNotificationClient>();
    ScopedDictPrefUpdate update(GetApplicationContext()->GetLocalState(),
                                prefs::kAppLevelPushNotificationPermissions);
    update->Set(kTipsNotificationKey, true);

    // Create the feature engagement tracker and wait to initialize.
    tracker_ =
        feature_engagement::TrackerFactory::GetForProfile(profile_.get());
    test_clock_.SetNow(base::Time::Now());
    tracker_->SetClockForTesting(test_clock_, test_clock_.Now());
    base::RunLoop run_loop;
    tracker_->AddOnInitializedCallback(
        base::IgnoreArgs<bool>(run_loop.QuitClosure()));
    run_loop.Run();
  }

  // Sets up a mock notification center, so notification requests can be
  // tested.
  void SetupMockNotificationCenter() {
    mock_notification_center_ = OCMClassMock([UNUserNotificationCenter class]);
    // Swizzle in the mock notification center.
    UNUserNotificationCenter* (^swizzle_block)() =
        ^UNUserNotificationCenter*() {
          return mock_notification_center_;
        };
    notification_center_swizzler_ = std::make_unique<ScopedBlockSwizzler>(
        [UNUserNotificationCenter class], @selector(currentNotificationCenter),
        swizzle_block);
  }

  std::string_view GetProfileName() {
    return browser_->GetProfile()->GetProfileName();
  }

  // Writes the first run sentinel file, to allow notifications to be
  // registered.
  void WriteFirstRunSentinel() {
    base::ScopedAllowBlockingForTesting allow_blocking;
    FirstRun::RemoveSentinel();
    base::File::Error file_error = base::File::FILE_OK;
    FirstRunSentinelCreationResult sentinel_created =
        FirstRun::CreateSentinel(&file_error);
    ASSERT_EQ(sentinel_created, FirstRunSentinelCreationResult::kSuccess)
        << "Error creating FirstRun sentinel: "
        << base::File::ErrorToString(file_error);
    FirstRun::LoadSentinelInfo();
    FirstRun::ClearStateForTesting();
  }

  // Returns an OCMArg that verifies a UNNotificationRequest was passed for the
  // given notification `type`.
  id NotificationRequestArg(TipsNotificationType type) {
    return [OCMArg checkWithBlock:^BOOL(UNNotificationRequest* request) {
      TipsNotificationType requested_type =
          ParseTipsNotificationType(request).value();
      EXPECT_TRUE(IsTipsNotification(request));
      EXPECT_EQ(requested_type, type);
      return YES;
    }];
  }

  // Returns a mock UNNotificationResponse for the given notification `type`.
  id MockRequestResponse(TipsNotificationType type,
                         bool for_reactivation = false) {
    EXPECT_OCMOCK_VERIFY((id)mock_notification_response_);
    mock_notification_response_ = OCMClassMock([UNNotificationResponse class]);
    OCMStub([mock_notification_response_ notification])
        .andReturn(MockNotification(type, for_reactivation));
    return mock_notification_response_;
  }

  // Returns a mock UNNotification for the given notification `type`.
  id MockNotification(TipsNotificationType type, bool for_reactivation) {
    UNNotificationRequest* request = [UNNotificationRequest
        requestWithIdentifier:kTipsNotificationId
                      content:ContentForTipsNotificationType(
                                  type, for_reactivation, GetProfileName())
                      trigger:[UNTimeIntervalNotificationTrigger
                                  triggerWithTimeInterval:
                                      TipsNotificationTriggerDelta(
                                          for_reactivation,
                                          TipsNotificationUserType::kUnknown)
                                          .InSecondsF()
                                                  repeats:NO]];
    id mock_notification = OCMClassMock([UNNotification class]);
    OCMStub([mock_notification request]).andReturn(request);
    return mock_notification;
  }

  // Stubs the notification center's completion callback for
  // getPendingNotificationRequestsWithCompletionHandler.
  void StubGetPendingRequests(NSArray<UNNotificationRequest*>* requests) {
    auto completionCaller =
        ^BOOL(void (^completion)(NSArray<UNNotificationRequest*>* requests)) {
          completion(requests);
          return YES;
        };
    OCMStub([mock_notification_center_
        getPendingNotificationRequestsWithCompletionHandler:
            [OCMArg checkWithBlock:completionCaller]]);
  }

  // Stubs the notification center's completion callback for
  // getPendingNotificationRequestsWithCompletionHandler.
  void StubGetDeliveredNotifications(NSArray<UNNotification*>* notifications) {
    auto completionCaller =
        ^BOOL(void (^completion)(NSArray<UNNotification*>* notifications)) {
          completion(notifications);
          return YES;
        };
    OCMStub([mock_notification_center_
        getDeliveredNotificationsWithCompletionHandler:
            [OCMArg checkWithBlock:completionCaller]]);
  }

  // Clears the pref used to store which notification types have already been
  // sent.
  void ClearSentNotifications() {
    GetApplicationContext()->GetLocalState()->SetInteger(
        kTipsNotificationsSentPref, 0);
  }

  // Sets the pref used to store which notification types have been sent.
  void SetSentNotifications(std::vector<TipsNotificationType> types) {
    int bits = 0;
    for (TipsNotificationType type : types) {
      bits |= 1 << int(type);
    }
    GetApplicationContext()->GetLocalState()->SetInteger(
        kTipsNotificationsSentPref, bits);
  }

  // Stubs the `-prepareToPresentModalWithSnackbarDismissal:` method from
  // `SceneCommands` so that it immediately calls the completion block.
  id StubPrepareToPresentModal() {
    id mock_handler = MockHandler(@protocol(SceneCommands));
    OCMStub([mock_handler
        prepareToPresentModalWithSnackbarDismissal:YES
                                        completion:[OCMArg invokeBlock]]);
    OCMStub([mock_handler
        prepareToPresentModalWithSnackbarDismissal:NO
                                        completion:[OCMArg invokeBlock]]);
    return mock_handler;
  }

  // Sets up an OCMock expectation that a notification will be requested.
  void ExpectNotificationRequest(TipsNotificationType type) {
    ExpectNotificationRequest(NotificationRequestArg(type));
  }

  void ExpectNotificationRequest(id request) {
    auto completionCaller = ^BOOL(void (^completion)(NSError* error)) {
      completion(nil);
      return YES;
    };
    OCMExpect([mock_notification_center_
        addNotificationRequest:request
         withCompletionHandler:[OCMArg checkWithBlock:completionCaller]]);
  }

  // Rejects all notification requests sent to the mock notification center.
  void RejectAllNotificationRequests() {
    OCMReject([mock_notification_center_ addNotificationRequest:[OCMArg any]
                                          withCompletionHandler:[OCMArg any]]);
  }

  // Ensures that Chrome is considered as default browser.
  void SetTrueChromeLikelyDefaultBrowser() { LogOpenHTTPURLFromExternalURL(); }

  // Ensures that Chrome is not considered as default browser.
  void SetFalseChromeLikelyDefaultBrowser() { ClearDefaultBrowserPromoData(); }

  // Clears the pref that stores the last action the user took with a Default
  // Browser promo.
  void ClearDefaultBrowserPromoLastAction() {
    GetApplicationContext()->GetLocalState()->ClearPref(
        prefs::kIosDefaultBrowserPromoLastAction);
  }

  // Creates a mock command handler and starts dispatching to it.
  id MockHandler(Protocol* protocol) {
    id mock_handler = OCMProtocolMock(protocol);
    [browser_->GetCommandDispatcher() startDispatchingToTarget:mock_handler
                                                   forProtocol:protocol];
    return mock_handler;
  }

  // Simulates foregrounding the app by calling the client's
  // OnSceneActiveForegroundBrowserReady method.
  void SimulateForegroundingApp() {
    base::RunLoop run_loop;
    client_->OnSceneActiveForegroundBrowserReady(run_loop.QuitClosure());
    run_loop.Run();
  }

  // Returns the user's type stored in local state prefs.
  TipsNotificationUserType GetUserType() {
    PrefService* local_state = GetApplicationContext()->GetLocalState();
    return static_cast<TipsNotificationUserType>(
        local_state->GetInteger(kTipsNotificationsUserType));
  }

  // Signs in with `fakeIdentity1`.
  void SigninWithFakeIdentity() {
    FakeSystemIdentityManager* identity_manager =
        FakeSystemIdentityManager::FromSystemIdentityManager(
            GetApplicationContext()->GetSystemIdentityManager());
    AuthenticationService* authentication_service =
        AuthenticationServiceFactory::GetForProfile(profile_.get());
    identity_manager->AddIdentity([FakeSystemIdentity fakeIdentity1]);
    authentication_service->SignIn([FakeSystemIdentity fakeIdentity1],
                                   signin_metrics::AccessPoint::kStartPage);
  }

  web::WebTaskEnvironment task_environment_;
  const base::HistogramTester histogram_tester_;
  IOSChromeScopedTestingLocalState scoped_testing_local_state_;
  TestProfileManagerIOS profile_manager_;
  raw_ptr<feature_engagement::Tracker> tracker_;
  base::SimpleTestClock test_clock_;
  SceneState* scene_state_;
  UNNotificationResponse* mock_notification_response_;
  std::unique_ptr<TestBrowser> browser_;
  std::unique_ptr<TipsNotificationClient> client_;
  id mock_notification_center_;
  std::unique_ptr<ScopedBlockSwizzler> notification_center_swizzler_;
  raw_ptr<ProfileIOS> profile_;
  UNNotificationResponse* mock_response_;
};

#pragma mark - Test cases

// Tests that HandleNotificationReception does nothing and returns "NoData".
TEST_F(TipsNotificationClientTest, HandleNotificationReception) {
  EXPECT_EQ(client_->HandleNotificationReception(nil), std::nullopt);
  NSDictionary* user_info = UserInfoForTipsNotificationType(
      TipsNotificationType::kWhatsNew, false, GetProfileName());
  EXPECT_EQ(client_->HandleNotificationReception(user_info),
            UIBackgroundFetchResultNoData);
}

// Tests that RegisterActionalableNotifications returns an empty array.
TEST_F(TipsNotificationClientTest, RegisterActionableNotifications) {
  EXPECT_EQ(client_->RegisterActionableNotifications().count, 0u);
}

// Tests that the client handles a Default Browser notification response.
TEST_F(TipsNotificationClientTest, DefaultBrowserHandle) {
  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(SettingsCommands));
  OCMExpect([mock_handler
      showDefaultBrowserSettingsFromViewController:nil
                                      sourceForUMA:
                                          DefaultBrowserSettingsPageSource::
                                              kTipsNotification]);

  mock_response_ = MockRequestResponse(TipsNotificationType::kDefaultBrowser);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Interaction",
                                       TipsNotificationType::kDefaultBrowser,
                                       1);
}

// Tests that the client handles a SignIn notification response.
TEST_F(TipsNotificationClientTest, SigninHandle) {
  id mock_handler = StubPrepareToPresentModal();
  OCMExpect([mock_handler showSignin:[OCMArg any] baseViewController:nil]);

  mock_response_ = MockRequestResponse(TipsNotificationType::kSignin);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Interaction",
                                       TipsNotificationType::kSignin, 1);

  SigninWithFakeIdentity();
  mock_handler = MockHandler(@protocol(SettingsCommands));
  OCMExpect([mock_handler showAccountsSettingsFromViewController:nil
                                            skipIfUINotAvailable:NO]);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Interaction",
                                       TipsNotificationType::kSignin, 2);
}

// Tests that the client can request a Proactive Whats New notification.
TEST_F(TipsNotificationClientTest, WhatsNewProactiveRequest) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kIOSReactivationNotifications);
  SetSentNotifications({TipsNotificationType::kLens,
                        TipsNotificationType::kEnhancedSafeBrowsing});

  [PushNotificationUtil
      updateAuthorizationStatusPref:UNAuthorizationStatusProvisional];
  // Simulate that the user has not opted-in.
  {
    ScopedDictPrefUpdate update(GetApplicationContext()->GetLocalState(),
                                prefs::kAppLevelPushNotificationPermissions);
    update->Remove(kTipsNotificationKey);
  }

  WriteFirstRunSentinel();
  SetTrueChromeLikelyDefaultBrowser();

  StubGetPendingRequests(nil);
  ExpectNotificationRequest(TipsNotificationType::kWhatsNew);

  base::RunLoop run_loop;
  client_->OnSceneActiveForegroundBrowserReady(run_loop.QuitClosure());
  run_loop.Run();

  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Proactive.Sent",
                                       TipsNotificationType::kWhatsNew, 1);

  // Run again, but this time simulating a delivered notification.
  NSMutableArray<UNNotification*>* delivered_notifications = [NSMutableArray
      arrayWithObject:MockNotification(TipsNotificationType::kWhatsNew, true)];
  StubGetDeliveredNotifications(delivered_notifications);
  base::RunLoop run_loop_2;
  client_->OnSceneActiveForegroundBrowserReady(run_loop_2.QuitClosure());
  run_loop_2.Run();
  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
  histogram_tester_.ExpectUniqueSample(
      "IOS.Notifications.Tips.Proactive.Triggered",
      TipsNotificationType::kWhatsNew, 1);

  // Run again, but this time the delivered notification is gone.
  [delivered_notifications removeAllObjects];
  base::RunLoop run_loop_3;
  client_->OnSceneActiveForegroundBrowserReady(run_loop_3.QuitClosure());
  run_loop_3.Run();
  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
  histogram_tester_.ExpectUniqueSample(
      "IOS.Notifications.Tips.Proactive.Dismissed",
      TipsNotificationType::kWhatsNew, 1);
}

// Tests that the client will not request a Proactive Whats New notification if
// provisional notifications are disallowed by policy.
TEST_F(TipsNotificationClientTest, ProvisionalDisallowedByPolicy) {
  profile_->GetPrefs()->SetBoolean(
      prefs::kProvisionalNotificationsAllowedByPolicy, false);
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kIOSReactivationNotifications);
  SetSentNotifications({TipsNotificationType::kLens,
                        TipsNotificationType::kEnhancedSafeBrowsing});
  [PushNotificationUtil
      updateAuthorizationStatusPref:UNAuthorizationStatusProvisional];
  // Simulate that the user has not opted-in.
  GetApplicationContext()->GetLocalState()->ClearPref(
      prefs::kAppLevelPushNotificationPermissions);

  WriteFirstRunSentinel();
  StubGetPendingRequests(nil);
  RejectAllNotificationRequests();

  base::RunLoop run_loop;
  client_->OnSceneActiveForegroundBrowserReady(run_loop.QuitClosure());
  run_loop.Run();

  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Proactive.Sent",
                                       TipsNotificationType::kWhatsNew, 0);
}

// Tests that the client handles a Whats New notification response.
TEST_F(TipsNotificationClientTest, WhatsNewHandle) {
  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(WhatsNewCommands));
  OCMExpect([mock_handler showWhatsNew]);

  mock_response_ = MockRequestResponse(TipsNotificationType::kWhatsNew);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Interaction",
                                       TipsNotificationType::kWhatsNew, 1);
}

// Tests that the client handles a SetUpList Continuation notification response.
TEST_F(TipsNotificationClientTest, SetUpListContinuationHandle) {
  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(ContentSuggestionsCommands));
  OCMExpect([mock_handler showSetUpListSeeMoreMenuExpanded:YES]);

  mock_response_ =
      MockRequestResponse(TipsNotificationType::kSetUpListContinuation);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample(
      "IOS.Notifications.Tips.Interaction",
      TipsNotificationType::kSetUpListContinuation, 1);
}

// Tests that the client handles a Docking promo notification response.
TEST_F(TipsNotificationClientTest, DockingHandle) {
  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(PromosManagerCommands));
  OCMExpect([mock_handler showDockingPromo]);

  mock_response_ = MockRequestResponse(TipsNotificationType::kDocking);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Interaction",
                                       TipsNotificationType::kDocking, 1);
}

// Tests that the client handles an Omnibox Position promo notification
// response.
TEST_F(TipsNotificationClientTest, OmniboxPositionHandle) {
  // OmniboxPositionChoice is only available on phones.
  if (ui::GetDeviceFormFactor() != ui::DEVICE_FORM_FACTOR_PHONE) {
    return;
  }

  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(BrowserCoordinatorCommands));
  OCMExpect([mock_handler showOmniboxPositionChoice]);

  mock_response_ = MockRequestResponse(TipsNotificationType::kOmniboxPosition);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Interaction",
                                       TipsNotificationType::kOmniboxPosition,
                                       1);
}

// Tests that the client can register an Enhanced Safe Browsing promo
// notification.
TEST_F(TipsNotificationClientTest, EnhancedSafeBrowsingRequest) {
  WriteFirstRunSentinel();
  StubGetPendingRequests(nil);
  ExpectNotificationRequest(TipsNotificationType::kEnhancedSafeBrowsing);

  base::RunLoop run_loop;
  client_->OnSceneActiveForegroundBrowserReady(run_loop.QuitClosure());
  run_loop.Run();

  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
  histogram_tester_.ExpectUniqueSample(
      "IOS.Notifications.Tips.Sent",
      TipsNotificationType::kEnhancedSafeBrowsing, 1);
}

// Tests that the client will not register an Enhanced Safe Browsing promo
// notification if ESB is disabled by policy.
TEST_F(TipsNotificationClientTest,
       EnhancedSafeBrowsingRequestWhenDisabledByPolicy) {
  // Disable pref that stores the policy setting.
  profile_->GetPrefs()->SetBoolean(prefs::kAdvancedProtectionAllowed, false);

  SetFalseChromeLikelyDefaultBrowser();
  ClearDefaultBrowserPromoLastAction();
  WriteFirstRunSentinel();
  StubGetPendingRequests(nil);
  // Expect to skip over ESB and send the next notification.
  ExpectNotificationRequest(TipsNotificationType::kWhatsNew);

  base::RunLoop run_loop;
  client_->OnSceneActiveForegroundBrowserReady(run_loop.QuitClosure());
  run_loop.Run();

  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
}

// Tests that the client handles an Enhanced Safe Browsing promo notification
// response.
TEST_F(TipsNotificationClientTest, EnhancedSafeBrowsingHandle) {
  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(BrowserCoordinatorCommands));
  OCMExpect([mock_handler showEnhancedSafeBrowsingPromo]);

  mock_response_ =
      MockRequestResponse(TipsNotificationType::kEnhancedSafeBrowsing);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample(
      "IOS.Notifications.Tips.Interaction",
      TipsNotificationType::kEnhancedSafeBrowsing, 1);
}

// Tests that the client handles a Lens promo notification response.
TEST_F(TipsNotificationClientTest, LensHandle) {
  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(BrowserCoordinatorCommands));
  OCMExpect([mock_handler showLensPromo]);

  mock_response_ = MockRequestResponse(TipsNotificationType::kLens);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Interaction",
                                       TipsNotificationType::kLens, 1);
}

// Tests that the client handles a Lens promo proactive notification response.
TEST_F(TipsNotificationClientTest, LensProactiveHandle) {
  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(BrowserCoordinatorCommands));
  OCMExpect([mock_handler showLensPromo]);

  mock_response_ = MockRequestResponse(TipsNotificationType::kLens, true);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample(
      "IOS.Notifications.Tips.Proactive.Interaction",
      TipsNotificationType::kLens, 1);
}

// Tests the the user can be classified as an "Active Seeker" of Tips.
TEST_F(TipsNotificationClientTest, ClassifyUserActiveSeeker) {
  base::ScopedMockClockOverride clock;
  WriteFirstRunSentinel();
  StubPrepareToPresentModal();
  EXPECT_EQ(GetUserType(), TipsNotificationUserType::kUnknown);

  StubGetPendingRequests(nil);
  ExpectNotificationRequest(TipsNotificationType::kEnhancedSafeBrowsing);
  base::RunLoop run_loop;
  client_->OnSceneActiveForegroundBrowserReady(run_loop.QuitClosure());
  run_loop.Run();
  EXPECT_OCMOCK_VERIFY(mock_notification_center_);

  clock.Advance(base::Hours(1));
  SimulateForegroundingApp();
  EXPECT_EQ(GetUserType(), TipsNotificationUserType::kUnknown);

  clock.Advance(base::Hours(24));
  SimulateForegroundingApp();
  EXPECT_EQ(GetUserType(), TipsNotificationUserType::kActiveSeeker);
}

// Tests the the user can be classified as "Less Engaged".
TEST_F(TipsNotificationClientTest, ClassifyUserLessEngaged) {
  base::ScopedMockClockOverride clock;
  WriteFirstRunSentinel();
  StubPrepareToPresentModal();

  EXPECT_EQ(GetUserType(), TipsNotificationUserType::kUnknown);

  StubGetPendingRequests(nil);
  ExpectNotificationRequest(TipsNotificationType::kEnhancedSafeBrowsing);
  base::RunLoop run_loop;
  client_->OnSceneActiveForegroundBrowserReady(run_loop.QuitClosure());
  run_loop.Run();
  EXPECT_OCMOCK_VERIFY(mock_notification_center_);

  clock.Advance(base::Hours(73));
  SimulateForegroundingApp();
  EXPECT_EQ(GetUserType(), TipsNotificationUserType::kLessEngaged);
}

// Tests that the correct trigger time is used, depending on the user's
// classification and notification type.
TEST_F(TipsNotificationClientTest, TestTriggerTimeDeltas) {
  EXPECT_EQ(
      TipsNotificationTriggerDelta(false, TipsNotificationUserType::kUnknown),
      base::Days(3));
  EXPECT_EQ(TipsNotificationTriggerDelta(
                false, TipsNotificationUserType::kLessEngaged),
            base::Days(21));
  EXPECT_EQ(TipsNotificationTriggerDelta(
                false, TipsNotificationUserType::kActiveSeeker),
            base::Days(7));
  EXPECT_EQ(TipsNotificationTriggerDelta(
                true, TipsNotificationUserType::kUnknown,
                TipsNotificationType::kTrustedVaultKeyRetrieval),
            base::Minutes(5));

  // Verify that the experimental settings can override the trigger time.
  [[NSUserDefaults standardUserDefaults] setInteger:111
                                             forKey:@"TipsNotificationTrigger"];
  EXPECT_EQ(
      TipsNotificationTriggerDelta(false, TipsNotificationUserType::kUnknown),
      base::Seconds(111));
  [[NSUserDefaults standardUserDefaults]
      removeObjectForKey:@"TipsNotificationTrigger"];

  // Verify that the Reactivation feature param can set the trigger delta.
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeatureWithParameters(
      kIOSReactivationNotifications,
      {
          {kIOSReactivationNotificationsTriggerTimeParam, "30s"},
      });
  EXPECT_EQ(
      TipsNotificationTriggerDelta(true, TipsNotificationUserType::kUnknown),
      base::Seconds(30));
  EXPECT_EQ(TipsNotificationTriggerDelta(
                true, TipsNotificationUserType::kUnknown,
                TipsNotificationType::kTrustedVaultKeyRetrieval),
            base::Seconds(30));
}

// Tests that the order of notification types changes correctly when the feature
// param is set.
TEST_F(TipsNotificationClientTest, TestOrderParam) {
  std::vector<TipsNotificationType> order = TipsNotificationsTypesOrder(false);
  EXPECT_EQ(order[0], TipsNotificationType::kEnhancedSafeBrowsing);
  EXPECT_EQ(order[1], TipsNotificationType::kWhatsNew);

  // Test Reactivation notifications order, default order.
  order = TipsNotificationsTypesOrder(true);
  EXPECT_EQ(order.size(), 3u);
  EXPECT_EQ(order[0], TipsNotificationType::kLens);
  EXPECT_EQ(order[1], TipsNotificationType::kEnhancedSafeBrowsing);
  EXPECT_EQ(order[2], TipsNotificationType::kWhatsNew);

  // Test Reactivation notifications order, alternate order.
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeatureWithParameters(
      kIOSReactivationNotifications,
      {
          // The alternate order: ESB, Lens, What's New.
          {kIOSReactivationNotificationsOrderParam, "8,7,1"},
      });
  order = TipsNotificationsTypesOrder(true);
  EXPECT_EQ(order.size(), 3u);
  EXPECT_EQ(order[0], TipsNotificationType::kEnhancedSafeBrowsing);
  EXPECT_EQ(order[1], TipsNotificationType::kLens);
  EXPECT_EQ(order[2], TipsNotificationType::kWhatsNew);

  // Test Expanded Tips order param.
  feature_list.Reset();
  feature_list.InitAndEnableFeatureWithParameters(
      kIOSExpandedTips, {
                            {kIOSExpandedTipsOrderParam, "4,5,6"},
                        });
  order = TipsNotificationsTypesOrder(false);
  EXPECT_EQ(order.size(), 3u);
  EXPECT_EQ(order[0], TipsNotificationType::kSetUpListContinuation);
  EXPECT_EQ(order[1], TipsNotificationType::kDocking);
  EXPECT_EQ(order[2], TipsNotificationType::kOmniboxPosition);
}

// Tests the order of reactivation notifications when the trusted vault
// notification is enabled.
TEST_F(TipsNotificationClientTest,
       TestOrderParam_WhenTrustedVaultNotificationIsEnabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(kIOSTrustedVaultNotification);

  std::vector<TipsNotificationType> order = TipsNotificationsTypesOrder(true);
  EXPECT_EQ(order.size(), 4u);
  EXPECT_EQ(order[0], TipsNotificationType::kTrustedVaultKeyRetrieval);
  EXPECT_EQ(order[1], TipsNotificationType::kLens);
  EXPECT_EQ(order[2], TipsNotificationType::kEnhancedSafeBrowsing);
  EXPECT_EQ(order[3], TipsNotificationType::kWhatsNew);
}

// Tests that the client handles a CPE Promo notification response.
TEST_F(TipsNotificationClientTest, CPEHandle) {
  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(CredentialProviderPromoCommands));
  OCMExpect(
      [mock_handler showCredentialProviderPromoWithTrigger:
                        CredentialProviderPromoTrigger::TipsNotification]);

  mock_response_ = MockRequestResponse(TipsNotificationType::kCPE);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Interaction",
                                       TipsNotificationType::kCPE, 1);
}

// Tests that the client handles a LensOverlay promo notification response.
TEST_F(TipsNotificationClientTest, LensOverlayHandle) {
  StubPrepareToPresentModal();
  id mock_handler = MockHandler(@protocol(BrowserCoordinatorCommands));
  OCMExpect([mock_handler showSearchWhatYouSeePromo]);

  mock_response_ = MockRequestResponse(TipsNotificationType::kLensOverlay);
  client_->HandleNotificationInteraction(mock_response_);

  EXPECT_OCMOCK_VERIFY(mock_handler);
  histogram_tester_.ExpectUniqueSample("IOS.Notifications.Tips.Interaction",
                                       TipsNotificationType::kLensOverlay, 1);
}

// Tests that the client can request a one-time default browser notification.
TEST_F(TipsNotificationClientTest, OneTimeDefaultBrowserNotification) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kIOSOneTimeDefaultBrowserNotification);
  SetFalseChromeLikelyDefaultBrowser();
  RecordDefaultBrowserPromoLastAction(IOSDefaultBrowserPromoAction::kCancel);

  StubGetPendingRequests(nil);
  ExpectNotificationRequest(TipsNotificationType::kDefaultBrowser);

  SimulateForegroundingApp();

  EXPECT_OCMOCK_VERIFY(mock_notification_center_);

  // Verify that it sends the next available notification afterwards.
  ExpectNotificationRequest(TipsNotificationType::kEnhancedSafeBrowsing);
  SimulateForegroundingApp();
  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
}

// Tests that a pending notification is cleared for the one-time default
// browser notification.
TEST_F(TipsNotificationClientTest,
       OneTimeDefaultBrowserNotificationClearsPending) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kIOSOneTimeDefaultBrowserNotification);
  SetFalseChromeLikelyDefaultBrowser();

  UNNotificationRequest* request = [UNNotificationRequest
      requestWithIdentifier:kTipsNotificationId
                    content:ContentForTipsNotificationType(
                                TipsNotificationType::kWhatsNew, false,
                                GetProfileName())
                    trigger:nil];
  StubGetPendingRequests(@[ request ]);
  ExpectNotificationRequest(TipsNotificationType::kDefaultBrowser);
  OCMExpect([mock_notification_center_
      removePendingNotificationRequestsWithIdentifiers:@[
        kTipsNotificationId
      ]]);

  SimulateForegroundingApp();

  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
}

// Tests that the client will not request a one-time default browser
// notification within 7 days of FRE.
TEST_F(TipsNotificationClientTest,
       OneTimeDefaultBrowserNotificationWithinFRECooldown) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kIOSOneTimeDefaultBrowserNotification);
  SetFalseChromeLikelyDefaultBrowser();
  tracker_->NotifyEvent(feature_engagement::events::kIOSDefaultBrowserFREShown);
  RecordDefaultBrowserPromoLastAction(IOSDefaultBrowserPromoAction::kCancel);

  StubGetPendingRequests(nil);

  // Default browser notification should be skippd and next in line should be
  // scheduled.
  ExpectNotificationRequest(TipsNotificationType::kEnhancedSafeBrowsing);
  SimulateForegroundingApp();
  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
}

// Tests that the client will request a one-time default browser notification 7
// days after the FRE.
TEST_F(TipsNotificationClientTest,
       OneTimeDefaultBrowserNotificationAfterFRECooldown) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(kIOSOneTimeDefaultBrowserNotification);
  SetFalseChromeLikelyDefaultBrowser();
  tracker_->NotifyEvent(feature_engagement::events::kIOSDefaultBrowserFREShown);
  test_clock_.Advance(base::Days(8));
  RecordDefaultBrowserPromoLastAction(IOSDefaultBrowserPromoAction::kCancel);

  StubGetPendingRequests(nil);

  // Default browser notification should be scheduled.
  ExpectNotificationRequest(TipsNotificationType::kDefaultBrowser);
  SimulateForegroundingApp();
  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
}

// Tests that a pending notification is cleared if trigger criteria is not valid
// anymore.
TEST_F(TipsNotificationClientTest, ClearInvalidPendingNotification) {
  // Enable Enhanced Safe Browsing, which should invalidate the pending
  // notification.
  profile_->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnhanced, true);

  UNNotificationRequest* request = [UNNotificationRequest
      requestWithIdentifier:kTipsNotificationId
                    content:ContentForTipsNotificationType(
                                TipsNotificationType::kEnhancedSafeBrowsing,
                                false, GetProfileName())
                    trigger:nil];
  StubGetPendingRequests(@[ request ]);
  OCMExpect([mock_notification_center_
      removePendingNotificationRequestsWithIdentifiers:@[
        kTipsNotificationId
      ]]);

  SimulateForegroundingApp();

  EXPECT_OCMOCK_VERIFY(mock_notification_center_);
}
