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

#import <PassKit/PassKit.h>

#import <memory>

#import "base/functional/bind.h"
#import "base/test/ios/wait_util.h"
#import "ios/chrome/browser/download/model/download_test_util.h"
#import "ios/chrome/browser/infobars/ui_bundled/banners/infobar_banner_constants.h"
#import "ios/chrome/browser/shared/model/utils/mime_type_util.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_test_case.h"
#import "ios/chrome/test/scoped_eg_synchronization_disabler.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "net/test/embedded_test_server/http_request.h"
#import "net/test/embedded_test_server/http_response.h"
#import "ui/base/l10n/l10n_util_mac.h"

using base::test::ios::kWaitForDownloadTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;

namespace {

// Returns matcher for PassKit error infobar.
id<GREYMatcher> PassKitErrorInfobarLabels() {
  return grey_allOf(
      grey_accessibilityID(kInfobarBannerLabelsStackViewIdentifier),
      grey_accessibilityLabel(
          l10n_util::GetNSString(IDS_IOS_GENERIC_PASSKIT_ERROR)),
      nil);
}

// PassKit landing page and download request handler.
std::unique_ptr<net::test_server::HttpResponse> GetResponse(
    const net::test_server::HttpRequest& request) {
  auto result = std::make_unique<net::test_server::BasicHttpResponse>();
  result->set_code(net::HTTP_OK);

  if (request.GetURL().GetPath() == "/single") {
    result->set_content("<a id='bad' href='/single-bad'>Bad</a>"
                        "<a id='good' href='/single-good'>Good</a>");
  } else if (request.GetURL().GetPath() == "/bundle") {
    result->set_content("<a id='bad' href='/bundle-bad'>Bad</a>"
                        "<a id='good' href='/bundle-good'>Good</a>"
                        "<a id='semi' href='/bundle-semi'>Semi</a>");
  } else if (request.GetURL().GetPath() == "/single-bad") {
    result->AddCustomHeader("Content-Type", kPkPassMimeType);
    result->set_content("corrupted");
  } else if (request.GetURL().GetPath() == "/single-good") {
    result->AddCustomHeader("Content-Type", kPkPassMimeType);
    result->set_content(testing::GetTestFileContents(testing::kPkPassFilePath));
  } else if (request.GetURL().GetPath() == "/bundle-bad") {
    result->AddCustomHeader("Content-Type", kPkBundledPassMimeType);
    // Returning a valid pass unzipped is invalid for a bundled pass.
    result->set_content(testing::GetTestFileContents(testing::kPkPassFilePath));
  } else if (request.GetURL().GetPath() == "/bundle-good") {
    result->AddCustomHeader("Content-Type", kPkBundledPassMimeType);
    result->set_content(
        testing::GetTestFileContents(testing::kBundledPkPassFilePath));
  } else if (request.GetURL().GetPath() == "/bundle-semi") {
    result->AddCustomHeader("Content-Type", kPkBundledPassMimeType);
    result->set_content(
        testing::GetTestFileContents(testing::kSemiValidBundledPkPassFilePath));
  }

  return result;
}

}  // namespace

// Tests PassKit file download.
@interface PassKitEGTest : ChromeTestCase
@end

@implementation PassKitEGTest {
}

- (void)setUp {
  [super setUp];

  self.testServer->RegisterRequestHandler(base::BindRepeating(&GetResponse));
  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
}

// Tests that Chrome presents PassKit error infobar if pkpass file cannot be
// parsed.
- (void)testPassKitParsingError {
  [ChromeEarlGrey loadURL:self.testServer->GetURL("/single")];
  [ChromeEarlGrey waitForWebStateContainingText:"Bad"];
  [ChromeEarlGrey tapWebStateElementWithID:@"bad"];

  bool infobarShown = WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
    NSError* error = nil;
    [[EarlGrey selectElementWithMatcher:PassKitErrorInfobarLabels()]
        assertWithMatcher:grey_notNil()
                    error:&error];
    return (error == nil);
  });
  GREYAssert(infobarShown, @"PassKit error infobar was not shown");
}

// Tests that Chrome PassKit dialog is shown for sucessfully downloaded pkpass
// file.
- (void)testPassKitDownload {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Wallet app is not supported on iPads.");
  }

  [ChromeEarlGrey loadURL:self.testServer->GetURL("/single")];
  [ChromeEarlGrey waitForWebStateContainingText:"Good"];
  [ChromeEarlGrey tapWebStateElementWithID:@"good"];

  // PKAddPassesViewController UI is rendered out of host process so EarlGrey
  // matcher can not find PassKit Dialog UI.
  // EG2 test can use XCUIApplication API to check for PassKit dialog UI
  // presentation.
  XCUIApplication* app = [[XCUIApplication alloc] init];
  XCUIElement* title = nil;
  title = app.staticTexts[@"Toy Town"];
  GREYAssert(
      [title waitForExistenceWithTimeout:kWaitForDownloadTimeout.InSecondsF()],
      @"PassKit dialog UI was not presented");
}

// Tests that Chrome PassKit dialog is shown for sucessfully downloaded bundle
// pkpasses file.
- (void)testBundlePassKitDownload {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Wallet app is not supported on iPads.");
  }

  [ChromeEarlGrey loadURL:self.testServer->GetURL("/bundle")];
  [ChromeEarlGrey waitForWebStateContainingText:"Good"];
  [ChromeEarlGrey tapWebStateElementWithID:@"good"];

  {
    ScopedSynchronizationDisabler synchronizationDisabler;
    // PKAddPassesViewController UI is rendered out of host process so EarlGrey
    // matcher can not find PassKit Dialog UI.
    // EG2 test can use XCUIApplication API to check for PassKit dialog UI
    // presentation.
    XCUIApplication* app = [[XCUIApplication alloc] init];
    XCUIElement* title = nil;
    title = app.staticTexts[@"Toy Town"];
    GREYAssert(
        [title
            waitForExistenceWithTimeout:kWaitForDownloadTimeout.InSecondsF()],
        @"PassKit dialog UI was not presented");

    // It is flaky to swipe to show the other pass, so check that there is the
    // title saying there are 2 passes.
    title = app.staticTexts[@"2 Passes"];
    GREYAssert(
        [title
            waitForExistenceWithTimeout:kWaitForDownloadTimeout.InSecondsF()],
        @"There aren't two passes.");
  }
}

// Tests that Chrome PassKit dialog is shown for sucessfully downloaded
// semi-valid bundle pkpasses file (containing one good pkpass and a non-valid
// one).
- (void)testSemiValidBundlePassKitDownload {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Wallet app is not supported on iPads.");
  }

  [ChromeEarlGrey loadURL:self.testServer->GetURL("/bundle")];
  [ChromeEarlGrey waitForWebStateContainingText:"Good"];
  [ChromeEarlGrey tapWebStateElementWithID:@"semi"];

  {
    ScopedSynchronizationDisabler synchronizationDisabler;
    // PKAddPassesViewController UI is rendered out of host process so EarlGrey
    // matcher can not find PassKit Dialog UI.
    // EG2 test can use XCUIApplication API to check for PassKit dialog UI
    // presentation.
    XCUIApplication* app = [[XCUIApplication alloc] init];
    XCUIElement* title = nil;
    title = app.staticTexts[@"Toy Town"];
    GREYAssert(
        [title
            waitForExistenceWithTimeout:kWaitForDownloadTimeout.InSecondsF()],
        @"PassKit dialog UI was not presented");

    // Swipe left, nothing should happen.
    [title swipeLeft];
    GREYAssertTrue(title.hittable, @"PassKit dialog UI was not scrolled");
  }
}

// Tests that Chrome presents PassKit error infobar if bundle pkpass file cannot
// be parsed.
- (void)testInvalidBundlePassKitDownload {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Wallet app is not supported on iPads.");
  }

  [ChromeEarlGrey loadURL:self.testServer->GetURL("/bundle")];
  [ChromeEarlGrey waitForWebStateContainingText:"Good"];
  [ChromeEarlGrey tapWebStateElementWithID:@"bad"];

  bool infobarShown = WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
    NSError* error = nil;
    [[EarlGrey selectElementWithMatcher:PassKitErrorInfobarLabels()]
        assertWithMatcher:grey_notNil()
                    error:&error];
    return (error == nil);
  });
  GREYAssert(infobarShown, @"PassKit error infobar was not shown");
}

@end
