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

#import <TargetConditionals.h>

#import "base/ios/ios_util.h"
#import "base/strings/strcat.h"
#import "base/strings/sys_string_conversions.h"
#import "components/autofill/core/common/autofill_debug_features.h"
#import "components/autofill/core/common/autofill_features.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/authentication/test/signin_earl_grey.h"
#import "ios/chrome/browser/autofill/autofill_ai/public/autofill_ai_constants.h"
#import "ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h"
#import "ios/chrome/browser/infobars/ui_bundled/infobar_earl_grey_ui_test_util.h"
#import "ios/chrome/browser/settings/ui_bundled/autofill/autofill_settings_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/earl_grey/chrome_actions.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_matchers_app_interface.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "ui/base/l10n/l10n_util.h"

namespace {

NSString* const kOwnerName = @"autofilltestuser";
NSString* const kRedressNumber = @"1234567";
NSString* const kPassportOwnerName = @"Pippi Långstrump";
NSString* const kPassportNumber = @"LR1234567";
NSString* const kPassportEntityType = @"Passport";

NSString* PassportSuggestionAccessibilityLabel() {
  constexpr std::u16string_view kDots = u"\u2022\u2060\u2006\u2060";

  NSString* last_four =
      [kPassportNumber substringFromIndex:[kPassportNumber length] - 4];
  std::u16string last_four_u16 = base::SysNSStringToUTF16(last_four);

  std::u16string obfuscated_number =
      base::StrCat({kDots, kDots, kDots, kDots, last_four_u16});
  NSString* obfuscated_number_ns = base::SysUTF16ToNSString(obfuscated_number);

  return [NSString stringWithFormat:@"%@, %@ · %@", obfuscated_number_ns,
                                    kPassportEntityType, kPassportOwnerName];
}

// Those values are generated by using
// autofill::CalculateFieldSignatureForField and
// autofill::CalculateFormSignature.
constexpr std::string_view kRedressNumberOverrideParam =
    "17560148746471652940_2743594224_195-"
    "17560148746471652940_1667168476_7";

constexpr std::string_view kPassportOverrideParam =
    "4641858130178050027_57480302_169-"
    "4641858130178050027_1260605411_7-"
    "4641858130178050027_2806930637_170-"
    "4641858130178050027_1520818241_172-"
    "4641858130178050027_2813548763_171";

// Save button on the infobar.
id<GREYMatcher> infoBarSaveButton() {
  return chrome_test_util::ButtonWithAccessibilityLabel(
      l10n_util::GetNSString(IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_OK_BUTTON_LABEL));
}

// UITableView to show the new entity and the old entity if there is one.
id<GREYMatcher> entitiesView() {
  return grey_accessibilityID(kAutofillAISaveEntityTableViewId);
}

// Save button on the save entity view controller.
id<GREYMatcher> saveEntityButton() {
  return chrome_test_util::ButtonWithAccessibilityLabel(
      l10n_util::GetNSString(IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_OK_BUTTON_LABEL));
}

// Taps the infobar save button.
void TapInfoBarSaveButton() {
  [[EarlGrey selectElementWithMatcher:infoBarSaveButton()]
      performAction:grey_tap()];
}

// Taps the save entity button.
void TapSaveEntityButton() {
  [[EarlGrey selectElementWithMatcher:saveEntityButton()]
      performAction:grey_tap()];
}

// Verifies if the view to show the entity is visible.
void VerifyEntityVisibility(bool is_visible) {
  [[EarlGrey selectElementWithMatcher:entitiesView()]
      assertWithMatcher:is_visible ? grey_sufficientlyVisible() : grey_nil()];
}

// Label within a table view cell in "Addresses and more" settings.
id<GREYMatcher> GetMatcherForLabel(NSString* label) {
  return grey_allOf(
      grey_accessibilityLabel(label),
      grey_ancestor(grey_accessibilityID(kAutofillProfileTableViewID)),
      grey_interactable(), nil);
}

// Verifies if a cell with the given label is visible.
void VerifyCellVisibility(NSString* entity_label, bool is_visible) {
  [[EarlGrey selectElementWithMatcher:GetMatcherForLabel(entity_label)]
      assertWithMatcher:is_visible ? grey_sufficientlyVisible()
                                   : grey_notVisible()];
}

// Verifies the flow of saving a new entity from the infobar banner.
void VerifySaveNewEntityFlow() {
  // Tap the infobar save button.
  TapInfoBarSaveButton();

  // Verify the save entity UI is shown.
  VerifyEntityVisibility(true);

  // Tap the save entity button.
  TapSaveEntityButton();

  // Verify the save entity UI is gone.
  VerifyEntityVisibility(false);
}

}  // namespace

@interface AutofillAIEGTest : ChromeTestCase
@end

@implementation AutofillAIEGTest

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config;
  config.features_disabled.push_back(kYourSavedInfoSettingsPageIos);
  config.features_enabled.push_back(
      autofill::features::kAutofillAiWithDataSchema);
  config.features_enabled.push_back(
      autofill::features::kAutofillAiReauthRequired);

  if ([self isRunningTest:@selector(testSubmitRedressForm)] ||
      [self isRunningTest:@selector(testSavePassportForm)]) {
    config.features_enabled.push_back(
        autofill::features::debug::kAutofillAiForceOptIn);

    std::string overrideParam =
        std::string([self isRunningTest:@selector(testSubmitRedressForm)]
                        ? kRedressNumberOverrideParam
                        : kPassportOverrideParam);

    config.features_enabled_and_params.push_back(
        {autofill::features::debug::kAutofillOverridePredictions,
         {{"spec", overrideParam}}});

    config.features_enabled.push_back(
        autofill::features::kAutofillAiAlwaysTriggerServerModel);
  }

  if ([self isRunningTest:@selector(testWalletFailureShowsErrorDialog)]) {
    config.features_enabled.push_back(
        autofill::features::debug::kAutofillAiForceOptIn);

    config.features_enabled_and_params.push_back(
        {autofill::features::debug::kAutofillOverridePredictions,
         {{"spec", std::string(kPassportOverrideParam)}}});

    config.features_enabled.push_back(
        autofill::features::kAutofillAiAlwaysTriggerServerModel);

    config.features_enabled_and_params.push_back(
        {autofill::features::debug::kFakeWalletApiResponses,
         {{"simulate_failure", "true"}}});

    // Also need to enable the feature flag for wallet private passes.
    config.features_enabled.push_back(
        autofill::features::kAutofillAiWalletPrivatePasses);
  }

  return config;
}

- (void)setUp {
  [super setUp];

  if (!self.testServer->Started()) {
    GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
  }

  // Enhanced Autofill only works for signed in users.
  [SigninEarlGrey signinWithFakeIdentity:[FakeSystemIdentity fakeIdentity1]];
}

#pragma mark - Helper methods

// Returns the GURL for the redress number page.
- (GURL)redressNumberPageURL {
  return self.testServer->GetURL("/redress_number.html");
}

// Loads redress number page on localhost.
- (void)loadRedressNumberPage {
  // Loads redress number page.
  [ChromeEarlGrey loadURL:[self redressNumberPageURL]];
  [ChromeEarlGrey waitForWebStateContainingText:"Redress Number"];
}

// Loads simple passport page on localhost.
- (void)loadPassportPage {
  [ChromeEarlGrey loadURL:self.testServer->GetURL("/passport_form.html")];
  [ChromeEarlGrey waitForWebStateContainingText:"Passport"];
}

#pragma mark - Tests

// Tests the infobar banner shows and can be tapped. Once tapped, the detailed
// save entity UI is shown.
- (void)testSaveEntityUI {
  // TODO(crbug.com/516443975): Re-enable this flaky test on iOS below 26.
  if (!base::ios::IsRunningOnIOS26OrLater()) {
    EARL_GREY_TEST_DISABLED(@"Flaky on iOS below 26.");
  }

  // Simulate a trigger for the infobar.
  [AutofillAppInterface showAutofillAiSaveEntityBubble];

  // Verify the infobar banner shows and can be tapped. Once tapped, the
  // detailed save entity UI is shown, and save button can be tapped.
  VerifySaveNewEntityFlow();
}

// Tests entity deletion from "Addresses and more" in Settings.
- (void)testEntityDeletion {
  GREYAssertTrue(
      [AutofillAppInterface saveRedressNumberEntityWithName:kOwnerName
                                                     number:kRedressNumber],
      @"Failed to create an entity.");
  // Open "Addresses and more" from settings.
  [ChromeEarlGreyUI openSettingsMenu];
  [ChromeEarlGreyUI
      tapSettingsMenuButton:chrome_test_util::AddressesAndMoreButton()];

  // Verify the entity just saved is visible in settings.
  VerifyCellVisibility(kOwnerName, true);

  // Tap the Toolbar Edit button to enter edit mode.
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::SettingsToolbarEditButton()]
      performAction:grey_tap()];

  // Select the cell containing the entity.
  [[EarlGrey selectElementWithMatcher:GetMatcherForLabel(kOwnerName)]
      performAction:grey_tap()];

  // Tap the delete button in the bottom toolbar.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          SettingsBottomToolbarDeleteButton()]
      performAction:grey_tap()];

  // Tap the delete action sheet button to confirm deletion.
  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ActionSheetItemWithAccessibilityLabelId(
                     IDS_IOS_DELETE_ACTION_TITLE)] performAction:grey_tap()];

  // Ensure it's deleted.
  VerifyCellVisibility(kOwnerName, false);
}

// Tests submitting a redress form.
// TODO(crbug.com/506106151): Fix and re-enable on iOS 18 devices.
- (void)testSubmitRedressForm {
#if !TARGET_OS_SIMULATOR
  if (base::ios::IsRunningOnIOS18OrLater()) {
    EARL_GREY_TEST_SKIPPED(
        @"Disabled on iOS 18 devices due to crbug.com/506106151");
  }
#endif
  [self loadRedressNumberPage];

  // Filling is done in JavaScript to avoid flakiness.
  [ChromeEarlGrey tapWebStateElementWithID:@"fill_redress_number"];

  [ChromeEarlGrey submitWebStateFormWithID:"redress_form"];

  // Wait for the infobar banner to appear.
  [InfobarEarlGreyUI waitUntilInfobarBannerVisibleOrTimeout:YES];
}

// Tests saving a new passport from a web form, verifying that it appears in
// Settings and then deleting it.
// TODO(crbug.com/506106151): Fix and re-enable on iOS 18 devices.
- (void)testSavePassportForm {
#if !TARGET_OS_SIMULATOR
  if (base::ios::IsRunningOnIOS18OrLater()) {
    EARL_GREY_TEST_SKIPPED(
        @"Disabled on iOS 18 devices due to crbug.com/506106151");
  }
#endif
  [self loadPassportPage];

  // Tap on fill button.
  [ChromeEarlGrey tapWebStateElementWithID:@"fill_passport_form"];

  // Tap submit button.
  [ChromeEarlGrey tapWebStateElementWithID:@"submit-button"];

  // Wait for the infobar banner to appear.
  [InfobarEarlGreyUI waitUntilInfobarBannerVisibleOrTimeout:YES];

  // Verify it is a passport infobar.
  [[EarlGrey selectElementWithMatcher:
                 grey_text(l10n_util::GetNSString(
                     IDS_IOS_AUTOFILL_AI_SAVE_PASSPORT_ENTITY_DIALOG_TITLE))]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Tap the infobar save button to open the detailed UI.
  TapInfoBarSaveButton();

  // Save new passport. (Wait for and tap the save button).
  id<GREYMatcher> saveButton =
      grey_accessibilityID(kAutofillAISaveEntitySaveButtonId);
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:saveButton];

  // Verify it is a passport sheet.
  [[EarlGrey selectElementWithMatcher:
                 grey_text(l10n_util::GetNSString(
                     IDS_IOS_AUTOFILL_AI_SAVE_PASSPORT_ENTITY_DIALOG_TITLE))]
      assertWithMatcher:grey_sufficientlyVisible()];

  [[EarlGrey selectElementWithMatcher:saveButton] performAction:grey_tap()];

  // Navigate to Settings -> "Addresses and more" menu.
  [ChromeEarlGreyUI openSettingsMenu];
  [ChromeEarlGreyUI
      tapSettingsMenuButton:chrome_test_util::AddressesAndMoreButton()];

  // Verify cell visibility of owner name.
  VerifyCellVisibility(kOwnerName, true);

  // Verify it is displayed as a passport.
  id<GREYMatcher> passportCell = grey_allOf(
      grey_kindOfClassName(@"UITableViewCell"),
      grey_descendant(grey_text(kOwnerName)),
      grey_descendant(grey_text(
          l10n_util::GetNSString(IDS_AUTOFILL_AI_PASSPORT_ENTITY_NAME))),
      nil);
  [[EarlGrey selectElementWithMatcher:passportCell]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Tap the Toolbar Edit button to enter edit mode.
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::SettingsToolbarEditButton()]
      performAction:grey_tap()];

  // Select the cell containing the entity.
  [[EarlGrey selectElementWithMatcher:GetMatcherForLabel(kOwnerName)]
      performAction:grey_tap()];

  // Tap the delete button in the bottom toolbar.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          SettingsBottomToolbarDeleteButton()]
      performAction:grey_tap()];

  // Tap the delete action sheet button to confirm deletion.
  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ActionSheetItemWithAccessibilityLabelId(
                     IDS_IOS_DELETE_ACTION_TITLE)] performAction:grey_tap()];

  // Ensure it's deleted.
  VerifyCellVisibility(kOwnerName, false);
}

// Tests that a wallet failure shows an error dialog when attempting to fill.
// TODO(crbug.com/506106151): Fix and re-enable on iOS 18 devices.
- (void)testWalletFailureShowsErrorDialog {
#if !TARGET_OS_SIMULATOR
  if (base::ios::IsRunningOnIOS18OrLater()) {
    EARL_GREY_TEST_SKIPPED(
        @"Disabled on iOS 18 devices due to crbug.com/506106151");
  }
#endif
  // Add server wallet passport entity.
  NSString* uuid = [AutofillAppInterface saveServerWalletPassportEntity];

  [self loadPassportPage];

  // Tap on the passport number field to trigger suggestions.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:chrome_test_util::TapWebElementWithId("passportNumber")];

  // Wait for the suggestion chip to appear.
  NSString* expectedLabel = PassportSuggestionAccessibilityLabel();
  id<GREYMatcher> suggestion_chip = grey_allOf(
      grey_accessibilityLabel(expectedLabel), grey_sufficientlyVisible(), nil);

  // Wait for it to appear.
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:suggestion_chip];

  // Tap it.
  [[EarlGrey selectElementWithMatcher:suggestion_chip]
      performAction:grey_tap()];

  // Verify that the error dialog from AutofillAiErrorDialogMediator is
  // generated and visible.
  id<GREYMatcher> errorDialogTitle = grey_text(
      l10n_util::GetNSString(IDS_IOS_AUTOFILL_AI_WALLET_FETCH_FAILURE_TITLE));

  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:errorDialogTitle];

  [[EarlGrey selectElementWithMatcher:errorDialogTitle]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Cleanup.
  [AutofillAppInterface removeEntityWithUUID:uuid];
}

@end
