// Copyright 2021 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/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/signin/internal/identity_manager/account_capabilities_constants.h"
#import "components/signin/public/base/signin_switches.h"
#import "google_apis/gaia/core_account_id.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/authentication/test/signin_matchers.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_constants.h"
#import "ios/chrome/browser/authentication/ui_bundled/views/views_constants.h"
#import "ios/chrome/browser/flags/chrome_switches.h"
#import "ios/chrome/browser/metrics/model/metrics_app_interface.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/capabilities_types.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/signin/model/test_constants.h"
#import "ios/chrome/common/ui/promo_style/constants.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_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/testing/earl_grey/matchers.h"

using chrome_test_util::GREYAssertErrorNil;

namespace {

void VerifySigninPromoSufficientlyVisible() {
  ConditionBlock condition = ^{
    NSError* error = nil;
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::SigninScreenPromoMatcher()]
        assertWithMatcher:grey_sufficientlyVisible()
                    error:&error];
    return error == nil;
  };
  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
                 base::test::ios::kWaitForUIElementTimeout, condition),
             @"Sign-in promo not visible");
}

void VerifyHistoryOptInPromoSufficientlyVisible() {
  ConditionBlock condition = ^{
    NSError* error = nil;
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::HistoryOptInPromoMatcher()]
        assertWithMatcher:grey_sufficientlyVisible()
                    error:&error];
    return error == nil;
  };
  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
                 base::test::ios::kWaitForUIElementTimeout, condition),
             @"History opt-in promo not visible");
}

// Opens an NTP, sends Chrome to background and brings it back to foreground.
// The fullscreen sign-in promo can be triggered when Chrome moves to foreground
// and nothing is displayed on the tab (so the tab grid must not be opened).
void OpenNTPAndBackgroundAndForegroundApp() {
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGreyUI waitForAppToIdle];
  [[AppLaunchManager sharedManager] backgroundAndForegroundApp];
  [ChromeEarlGreyUI waitForAppToIdle];
}

}  // namespace

// Tests the fullscreen sign-in promo restrictions.
@interface FullscreenSigninPromoTestCase : ChromeTestCase
@end

@implementation FullscreenSigninPromoTestCase

- (void)setUp {
  [super setUp];
  [[self class] testForStartup];
  chrome_test_util::GREYAssertErrorNil(
      [MetricsAppInterface setupHistogramTester]);
  [ChromeEarlGrey
      removeUserDefaultsObjectForKey:kDisplayedSSORecallPromoCountKey];
  [ChromeEarlGrey
      removeUserDefaultsObjectForKey:kDisplayedSSORecallForMajorVersionKey];
  [ChromeEarlGrey
      removeUserDefaultsObjectForKey:kLastShownAccountGaiaIdVersionKey];
  [ChromeEarlGrey
      removeUserDefaultsObjectForKey:kSigninPromoViewDisplayCountKey];
}

- (void)tearDownHelper {
  chrome_test_util::GREYAssertErrorNil(
      [MetricsAppInterface releaseHistogramTester]);
  [super tearDownHelper];
}

- (AppLaunchConfiguration)appConfigurationForTestCase {
  // Use commandline args to insert fake policy data into NSUserDefaults. To the
  // app, this policy data will appear under the
  // "com.apple.configuration.managed" key.
  AppLaunchConfiguration config;
  config.features_enabled.push_back(switches::kForceStartupSigninPromo);
  config.features_disabled.push_back(kAuthenticationFlowReauthFirstKillswitch);
  config.additional_args.push_back(std::string("--") +
                                   switches::kEnableFullscreenSigninPromo);
  config.additional_args.push_back(std::string("-") +
                                   test_switches::kEnableIPH +
                                   "=IPH_iOSPromoSigninFullscreen");
  // Without relaunch fullscreen signin promo will not be shown again.
  config.relaunch_policy = ForceRelaunchByCleanShutdown;
  return config;
}

// Tests that the sign-in promo is not visible at start-up with no identity.
- (void)testNoSigninPromoWithNoIdentity {
  OpenNTPAndBackgroundAndForegroundApp();

  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::SigninScreenPromoMatcher()]
      assertWithMatcher:grey_notVisible()];
}

// Tests that the history opt-in promo is shown if the user is signed in to
// an account without history sync.
- (void)testHistoryOptInPromoUserSignedIn {
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity enableHistorySync:NO];

  OpenNTPAndBackgroundAndForegroundApp();

  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::SigninScreenPromoMatcher()]
      assertWithMatcher:grey_notVisible()];
  VerifyHistoryOptInPromoSufficientlyVisible();
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::ButtonStackPrimaryButton()]
      performAction:grey_tap()];
  [ChromeEarlGreyUI waitForAppToIdle];
  [self expectFullscreenSigninPromoMetricsAndPreferences];
}

- (void)testHistoryOptInPromoNotShownWhenAlreadyGranted {
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity enableHistorySync:YES];

  OpenNTPAndBackgroundAndForegroundApp();

  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::SigninScreenPromoMatcher()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::HistoryOptInPromoMatcher()]
      assertWithMatcher:grey_notVisible()];
}

// Tests that the sign-in promo is visible at start-up for regular user, and
// followed by the history sync opt-in.
- (void)testStartupSigninPromoShown {
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];

  OpenNTPAndBackgroundAndForegroundApp();

  VerifySigninPromoSufficientlyVisible();
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::ButtonStackPrimaryButton()]
      performAction:grey_tap()];
  [ChromeEarlGreyUI waitForAppToIdle];
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::ButtonStackPrimaryButton()]
      performAction:grey_tap()];
  [ChromeEarlGreyUI waitForAppToIdle];
  [self expectFullscreenSigninPromoMetricsAndPreferences];
}

// Tests that the sign-in promo is visible at start-up for a user whose identity
// requires reauthentication, and triggers reauth prompt.
- (void)testStartupSigninPromoShownNeedsReauth {
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  [SigninEarlGrey setPersistentAuthErrorForAccount:CoreAccountId::FromGaiaId(
                                                       fakeIdentity.gaiaId)];

  OpenNTPAndBackgroundAndForegroundApp();

  VerifySigninPromoSufficientlyVisible();
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::ButtonStackPrimaryButton()]
      performAction:grey_tap()];
  // Confirm the fake reauthentication dialog.
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(
                                   grey_accessibilityID(
                                       kFakeAuthAddAccountButtonIdentifier),
                                   grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];

  VerifyHistoryOptInPromoSufficientlyVisible();
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::ButtonStackPrimaryButton()]
      performAction:grey_tap()];
  [ChromeEarlGreyUI waitForAppToIdle];

  [SigninEarlGrey verifySignedInWithFakeIdentity:fakeIdentity];
}

// Tests sign-in promo behavior in landscape. It should appears if and only if
// the device is an ipad.
// TODO(crbug.com/40266894): Need to enable this test.
- (void)DISABLED_testNoSignInPromoInLandscapeMode {
  [EarlGrey rotateInterfaceToOrientation:UIInterfaceOrientationLandscapeLeft
                                   error:nil];
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];

  OpenNTPAndBackgroundAndForegroundApp();

  if ([ChromeEarlGrey isIPadIdiom]) {
    VerifySigninPromoSufficientlyVisible();
  } else {
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::SigninScreenPromoMatcher()]
        assertWithMatcher:grey_notVisible()];
  }
}

#pragma mark - Helpers

- (void)expectFullscreenSigninPromoMetricsAndPreferences {
  NSError* error = [MetricsAppInterface
      expectUniqueSampleWithCount:1
                        forBucket:1
                     forHistogram:base::SysUTF8ToNSString(
                                      kUMASSORecallAccountsAvailable)];
  GREYAssertErrorNil(error, @"Failed to record show count histogram %s",
                     kUMASSORecallAccountsAvailable);
  error = [MetricsAppInterface
      expectUniqueSampleWithCount:1
                        forBucket:1
                     forHistogram:base::SysUTF8ToNSString(
                                      kUMASSORecallPromoSeenCount)];
  GREYAssertErrorNil(error, @"Failed to record show count histogram %s",
                     kUMASSORecallPromoSeenCount);
  error = [MetricsAppInterface
      expectUniqueSampleWithCount:1
                        forBucket:PromoActionEnabledSSOAccount
                     forHistogram:base::SysUTF8ToNSString(
                                      kUMASSORecallPromoAction)];
  GREYAssertErrorNil(error, @"Failed to record show count histogram %s",
                     kUMASSORecallPromoAction);
  NSNumber* value =
      [ChromeEarlGrey userDefaultsObjectForKey:kSigninPromoViewDisplayCountKey];
  GREYAssertEqual(1, value.integerValue, @"Failed to increase %@ pref",
                  kSigninPromoViewDisplayCountKey);
  NSArray<NSString*>* gaiaIds = [ChromeEarlGrey
      userDefaultsObjectForKey:kLastShownAccountGaiaIdVersionKey];
  // It is not possible to do `GREYAssertEqualObjects(expectedGaiaIds, gaiaIds),
  // since gaiaIds is EDOObject type (the object is in Chrome app).
  GREYAssertEqual(1UL, gaiaIds.count, @"Expect to have only one gaia id %@",
                  gaiaIds);
  GREYAssertEqualObjects([FakeSystemIdentity fakeIdentity1].gaiaId.ToNSString(),
                         gaiaIds[0], @"Wrong gaia id in %@",
                         kLastShownAccountGaiaIdVersionKey);
}

@end
