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

#import "base/feature_list.h"
#import "base/strings/strcat.h"
#import "components/signin/internal/identity_manager/account_capabilities_constants.h"
#import "components/supervised_user/core/common/supervised_user_constants.h"
#import "ios/chrome/browser/authentication/test/signin_earl_grey.h"
#import "ios/chrome/browser/authentication/test/signin_earl_grey_ui_test_util.h"
#import "ios/chrome/browser/metrics/model/metrics_app_interface.h"
#import "ios/chrome/browser/policy/model/policy_earl_grey_matchers.h"
#import "ios/chrome/browser/popup_menu/public/popup_menu_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_grid_constants.h"
#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_grid_metrics.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/public/toolbar_constants.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/chrome/test/earl_grey/test_switches.h"
#import "ios/testing/earl_grey/app_launch_configuration.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"

using chrome_test_util::ShowTabsButton;
using chrome_test_util::TabGridIncognitoTabsPanelButton;
using chrome_test_util::TabGridNewIncognitoTabButton;

namespace {

// Message shown in the disabled incognito tab page for supervised users.
NSString* const kTestSupervisedIncognitoMessage =
    @"Your account is managed by your parent.";

// Label used to find the 'Learn more' link.
NSString* const kTestLearnMoreLabel = @"Learn more";

id<GREYMatcher> SupervisedIncognitoMessage() {
  return chrome_test_util::ContainsPartialText(kTestSupervisedIncognitoMessage);
}

}  // namespace

// Tests that supervised users have incognito mode disabled.
@interface SupervisedUserIncognitoModeTestCase : ChromeTestCase

@property BOOL histogramTesterCreated;

@end

@implementation SupervisedUserIncognitoModeTestCase

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config = [super appConfigurationForTestCase];
  config.features_enabled.push_back(kChromeNextIa);
  return config;
}

- (void)setupAndRegisterHistogramTester {
  chrome_test_util::GREYAssertErrorNil(
      [MetricsAppInterface setupHistogramTester]);
  self.histogramTesterCreated = YES;
}

- (void)tearDownHelper {
  if (self.histogramTesterCreated) {
    chrome_test_util::GREYAssertErrorNil(
        [MetricsAppInterface releaseHistogramTester]);
    self.histogramTesterCreated = NO;
  }
  [super tearDownHelper];
}

// Signs in with a supervised account.
- (void)signInWithSupervisedAccount {
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity
                 withCapabilities:@{
                   @(kIsSubjectToParentalControlsCapabilityName) : @YES,
                 }];
  [SigninEarlGrey signinWithFakeIdentity:fakeIdentity];
  [SigninEarlGrey verifySignedInWithFakeIdentity:fakeIdentity];
}

// Tests that the tools menu item "New Incognito Tab" is disabled on signin and
// re-enabled on signout.
- (void)testToolsMenuIncognito {
  [self signInWithSupervisedAccount];

  [ChromeEarlGreyUI openToolsMenu];
  policy::AssertOverflowMenuElementEnabled(kToolsMenuNewTabId);
  policy::AssertOverflowMenuElementDisabled(kToolsMenuNewIncognitoTabId);
  [ChromeEarlGreyUI closeToolsMenu];

  [SigninEarlGrey signOut];

  [ChromeEarlGreyUI openToolsMenu];
  policy::AssertOverflowMenuElementEnabled(kToolsMenuNewTabId);
  policy::AssertOverflowMenuElementEnabled(kToolsMenuNewIncognitoTabId);
}

// Tests that the "New Incognito Tab" item in the popup menu (long-pressing the
// tab grid button) is disabled on signin and re-enabled on signout.
- (void)testTabGridButtonLongPressMenuIncognito {
  [self signInWithSupervisedAccount];

  [[EarlGrey selectElementWithMatcher:ShowTabsButton()]
      performAction:grey_longPress()];

  policy::AssertButtonInCollectionEnabled(IDS_IOS_TOOLS_MENU_NEW_TAB);
  policy::AssertButtonInCollectionDisabled(
      IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_TAB);

  // Dismiss the popup menu.
  [ChromeEarlGreyUI dismissContextMenuIfPresent];
  [ChromeEarlGrey waitForUIElementToDisappearWithMatcher:
                      grey_kindOfClassName(@"_UIContextMenuContainerView")];

  [SigninEarlGrey signOut];

  [[EarlGrey selectElementWithMatcher:ShowTabsButton()]
      performAction:grey_longPress()];

  policy::AssertButtonInCollectionEnabled(IDS_IOS_TOOLS_MENU_NEW_TAB);
  policy::AssertButtonInCollectionEnabled(IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_TAB);
}

// Tests that the disabled incognito tab grid shows a link to Family Link.
- (void)testTabGridIncognitoDisabled {
  [self setupAndRegisterHistogramTester];
  [self signInWithSupervisedAccount];

  // Open incognito tab grid.
  [ChromeEarlGrey showTabSwitcher];
  [[EarlGrey selectElementWithMatcher:TabGridIncognitoTabsPanelButton()]
      performAction:grey_tap()];

  GREYAssertNil(
      [MetricsAppInterface
          expectUniqueSampleWithCount:1
                            forBucket:static_cast<int>(
                                          IncognitoGridStatus::
                                              kDisabledForSupervisedUser)
                         forHistogram:@(kUMAIncognitoGridStatusHistogram)],
      @"Should record incognito grid status metrics for supervised user");

  // New Incognito Tab button `(+)` should be disabled.
  [[EarlGrey selectElementWithMatcher:TabGridNewIncognitoTabButton()]
      assertWithMatcher:grey_not(grey_enabled())];

  // The disabled incognito tab grid should display a message for supervised
  // users.
  [[EarlGrey selectElementWithMatcher:SupervisedIncognitoMessage()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Check that the "Learn more" link works.
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(
                                   grey_accessibilityLabel(kTestLearnMoreLabel),
                                   grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];

  // The Family Link url will load in a new tab.
  [ChromeEarlGrey waitForMainTabCount:2];

  GURL currentURL = [ChromeEarlGrey webStateVisibleURL];
  GURL expectedURL = GURL(supervised_user::kManagedByParentUiMoreInfoUrl);
  GREYAssertEqual(
      expectedURL, currentURL,
      @"Page navigated unexpectedly to %s, instead of the Family Link website",
      currentURL.spec().c_str());

  GREYAssertNil([MetricsAppInterface
                    expectTotalCount:1
                        forHistogram:@(kUMAIncognitoGridStatusHistogram)],
                @"Incognito grid metrics have incorrect total count.");
}

// Tests that the incognito tab grid is available after signout.
- (void)testTabGridIncognitoEnabledOnSignout {
  [self setupAndRegisterHistogramTester];
  [self signInWithSupervisedAccount];
  [SigninEarlGrey signOut];

  // Open the incognito tab grid.
  [ChromeEarlGrey showTabSwitcher];
  [[EarlGrey selectElementWithMatcher:TabGridIncognitoTabsPanelButton()]
      performAction:grey_tap()];

  GREYAssertNil(
      [MetricsAppInterface
          expectUniqueSampleWithCount:1
                            forBucket:static_cast<int>(
                                          IncognitoGridStatus::
                                              kEnabledForUnmanagedUser)
                         forHistogram:@(kUMAIncognitoGridStatusHistogram)],
      @"Should record incognito grid status metrics.");

  // New Incognito Tab button `(+)` should be re-enabled.
  [[EarlGrey selectElementWithMatcher:TabGridNewIncognitoTabButton()]
      assertWithMatcher:grey_enabled()];

  // Messages from the disabled incognito tab grid should not be displayed.
  [[EarlGrey selectElementWithMatcher:SupervisedIncognitoMessage()]
      assertWithMatcher:grey_nil()];

  GREYAssertNil([MetricsAppInterface
                    expectTotalCount:1
                        forHistogram:@(kUMAIncognitoGridStatusHistogram)],
                @"Incognito grid metrics have incorrect total count.");
}

// Tests that the incognito tab grid is available after signout with restart.
// Regression test for b/360787816.
- (void)testTabGridIncognitoEnabledOnSignoutWithRestart {
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity
                 withCapabilities:@{
                   @(kIsSubjectToParentalControlsCapabilityName) : @YES,
                 }];
  [SigninEarlGrey signinWithFakeIdentity:fakeIdentity];
  [SigninEarlGrey verifySignedInWithFakeIdentity:fakeIdentity];

  // Create the config to relaunch Chrome. Because fake identities are not
  // added on startup, manually configure fake identity cache to include the
  // supervised account.
  AppLaunchConfiguration config;
  config.relaunch_policy = ForceRelaunchByCleanShutdown;
  config.additional_args.push_back(base::StrCat({
    "-", test_switches::kAddFakeIdentitiesAtStartup, "=",
        [FakeSystemIdentity encodeIdentitiesToBase64:@[ fakeIdentity ]]
  }));
  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  // Set up the histogram tester after restarting.
  [self setupAndRegisterHistogramTester];

  [SigninEarlGrey signOut];

  // Open the incognito tab grid.
  [ChromeEarlGrey showTabSwitcher];
  [[EarlGrey selectElementWithMatcher:TabGridIncognitoTabsPanelButton()]
      performAction:grey_tap()];

  GREYAssertNil(
      [MetricsAppInterface
          expectUniqueSampleWithCount:1
                            forBucket:static_cast<int>(
                                          IncognitoGridStatus::
                                              kEnabledForUnmanagedUser)
                         forHistogram:@(kUMAIncognitoGridStatusHistogram)],
      @"Should record incognito grid status metrics.");

  // New Incognito Tab button `(+)` should be re-enabled.
  [[EarlGrey selectElementWithMatcher:TabGridNewIncognitoTabButton()]
      assertWithMatcher:grey_enabled()];

  // Messages from the disabled incognito tab grid should not be displayed.
  [[EarlGrey selectElementWithMatcher:SupervisedIncognitoMessage()]
      assertWithMatcher:grey_nil()];

  GREYAssertNil([MetricsAppInterface
                    expectTotalCount:1
                        forHistogram:@(kUMAIncognitoGridStatusHistogram)],
                @"Incognito grid metrics have incorrect total count.");
}

// Tests that incognito tabs are destroyed after supervised users sign in.
- (void)testIncognitoTabsDestroyedOnSignin {
  // Create new incognito tabs.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey openNewIncognitoTab];
  GREYAssertEqual(2UL, [ChromeEarlGrey incognitoTabCount],
                  @"Incognito tab count should be 2");

  // The latest incognito tab is displayed.
  GREYAssertTrue([ChromeEarlGrey isIncognitoMode],
                 @"Should stay in incognito mode");

  [self signInWithSupervisedAccount];

  // All incognito tabs should be destroyed.
  GREYAssertEqual(0UL, [ChromeEarlGrey incognitoTabCount],
                  @"Incognito tab count should be 0");

  // If the supervised user was previously on an incognito tab, the disabled
  // incognito tab grid should be displayed.
  [[EarlGrey selectElementWithMatcher:SupervisedIncognitoMessage()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Check that the overflow menu button is disabled.
  [[EarlGrey selectElementWithMatcher:
                 grey_allOf(chrome_test_util::TabGridOverflowMenuButton(),
                            grey_sufficientlyVisible(), nil)]
      assertWithMatcher:grey_not(grey_enabled())];
}

// Tests that incognito tabs are destroyed while supervised users stay in
// a regular tab.
- (void)testIncognitoTabsDestroyedOnSigninInBackground {
  // Create new incognito tabs.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey openNewIncognitoTab];
  GREYAssertEqual(2UL, [ChromeEarlGrey incognitoTabCount],
                  @"Incognito tab count should be 2");

  // Open a new regular tab.
  [ChromeEarlGrey openNewTab];
  GREYAssertFalse([ChromeEarlGrey isIncognitoMode],
                  @"Should stay in regular tab.");

  [self signInWithSupervisedAccount];

  // All incognito tabs should be destroyed.
  GREYAssertEqual(0UL, [ChromeEarlGrey incognitoTabCount],
                  @"Incognito tab count should be 0");

  // The user should stay on the new tab page.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
      assertWithMatcher:grey_notNil()];
}

@end
